@@ -1529,3 +1529,159 @@ def test_user_sees_complex_product_sections_with_extra_fields_preserved(self, mo
15291529 assert product ["Custom Field 1" ] == "Custom Value 1"
15301530 assert product ["Custom Field 2" ] == "Custom Value 2"
15311531 assert product ["Custom Field 3" ] == "Custom Value 3"
1532+
1533+ def test_user_sees_notes_field_always_last_in_component (self , model_instance ) -> None :
1534+ """
1535+ User sees the Notes field displayed at the bottom of every component editor section.
1536+
1537+ GIVEN: A component where "Notes" appears before other fields
1538+ WHEN: The system processes the component data
1539+ THEN: "Notes" is moved to be the very last field in that component
1540+ AND: All other field values and their order are preserved
1541+ """
1542+ # Arrange: Component with Notes appearing before connection data
1543+ existing_components = {
1544+ "Telemetry" : {
1545+ "Notes" : "SiK radio on Telem1" ,
1546+ "Product" : {"Manufacturer" : "RFDesign" , "Model" : "RFD900x" },
1547+ "FC Connection" : {"Type" : "SERIAL1" , "Protocol" : "MAVLink2" },
1548+ }
1549+ }
1550+
1551+ # Act
1552+ result = model_instance ._reorder_components (existing_components )
1553+
1554+ # Assert: Notes is last
1555+ fields = list (result ["Telemetry" ].keys ())
1556+ assert fields [- 1 ] == "Notes" , "Notes should be the last field in the component"
1557+ assert result ["Telemetry" ]["Notes" ] == "SiK radio on Telem1"
1558+ # Other fields are preserved in their original relative order
1559+ original_non_notes_fields = [key for key in existing_components ["Telemetry" ] if key != "Notes" ]
1560+ reordered_non_notes_fields = [key for key in fields if key != "Notes" ]
1561+ assert reordered_non_notes_fields == original_non_notes_fields
1562+
1563+ def test_user_sees_notes_field_last_when_already_at_end (self , model_instance ) -> None :
1564+ """
1565+ User sees Notes remain last when it is already the last field.
1566+
1567+ GIVEN: A component where "Notes" is already the last field
1568+ WHEN: The system processes the component data
1569+ THEN: "Notes" stays last and all other fields are untouched
1570+ """
1571+ # Arrange: Notes already last
1572+ existing_components = {
1573+ "GNSS Receiver" : {
1574+ "Product" : {"Manufacturer" : "u-blox" , "Model" : "NEO-M8N" },
1575+ "FC Connection" : {"Type" : "SERIAL3" , "Protocol" : "AUTO" },
1576+ "Notes" : "Primary GPS" ,
1577+ }
1578+ }
1579+
1580+ # Act
1581+ result = model_instance ._reorder_components (existing_components )
1582+
1583+ # Assert
1584+ fields = list (result ["GNSS Receiver" ].keys ())
1585+ assert fields [- 1 ] == "Notes"
1586+ assert result ["GNSS Receiver" ]["Notes" ] == "Primary GPS"
1587+
1588+ def test_user_sees_components_without_notes_field_unaffected (self , model_instance ) -> None :
1589+ """
1590+ User sees components that have no Notes field remain completely unaffected by Notes ordering.
1591+
1592+ GIVEN: Components with no Notes field
1593+ WHEN: The system processes the component data
1594+ THEN: All component fields are preserved without modification
1595+ """
1596+ # Arrange
1597+ existing_components = {
1598+ "Motors" : {
1599+ "Product" : {"Manufacturer" : "T-Motor" , "Model" : "F60 Pro IV" },
1600+ "Specifications" : {"Poles" : 14 },
1601+ },
1602+ "Propellers" : {
1603+ "Product" : {"Manufacturer" : "HQProp" , "Model" : "5.1x4.6" },
1604+ "Specifications" : {"Diameter_inches" : 5.1 },
1605+ },
1606+ }
1607+
1608+ # Act
1609+ result = model_instance ._reorder_components (existing_components )
1610+
1611+ # Assert: No Notes key injected, all fields unchanged
1612+ assert "Notes" not in result ["Motors" ]
1613+ assert "Notes" not in result ["Propellers" ]
1614+ assert list (result ["Motors" ].keys ()) == ["Product" , "Specifications" ]
1615+ assert list (result ["Propellers" ].keys ()) == ["Product" , "Specifications" ]
1616+
1617+ def test_user_sees_notes_last_across_all_components_simultaneously (self , model_instance ) -> None :
1618+ """
1619+ User sees Notes consistently last in every component when multiple components are processed together.
1620+
1621+ GIVEN: Multiple components each with Notes in different positions
1622+ WHEN: The system processes all component data at once
1623+ THEN: Notes is the last field in every component that has it
1624+ AND: Components without Notes are unaffected
1625+ """
1626+ # Arrange: Multiple components with Notes in varying positions
1627+ existing_components = {
1628+ "Flight Controller" : {
1629+ "Notes" : "flight controller notes" ,
1630+ "Product" : {"Manufacturer" : "Holybro" , "Model" : "Pixhawk 6C" },
1631+ "Firmware" : {"Type" : "ArduCopter" , "Version" : "4.6.x" },
1632+ },
1633+ "Battery" : {
1634+ "Product" : {"Manufacturer" : "Tattu" },
1635+ "Notes" : "battery notes" ,
1636+ "Specifications" : {"Chemistry" : "Lipo" },
1637+ },
1638+ "ESC" : {
1639+ "Product" : {"Manufacturer" : "Mamba" },
1640+ "FC->ESC Connection" : {"Type" : "Main Out" , "Protocol" : "DShot600" },
1641+ "ESC->FC Telemetry" : {"Type" : "Main Out" , "Protocol" : "BDShot" },
1642+ "Notes" : "esc notes" ,
1643+ },
1644+ "Motors" : {
1645+ "Specifications" : {"Poles" : 14 },
1646+ # No Notes field
1647+ },
1648+ }
1649+
1650+ # Act
1651+ result = model_instance ._reorder_components (existing_components )
1652+
1653+ # Assert: Notes is last in every component that has it
1654+ for component_name in ["Flight Controller" , "Battery" , "ESC" ]:
1655+ fields = list (result [component_name ].keys ())
1656+ assert fields [- 1 ] == "Notes" , f"Notes should be last in { component_name } , got: { fields } "
1657+
1658+ # Motors has no Notes and should be unaffected
1659+ assert "Notes" not in result ["Motors" ]
1660+
1661+ def test_system_moves_notes_last_during_update_json_structure (self , model_instance ) -> None :
1662+ """
1663+ System moves Notes to be the last field during a full update_json_structure call.
1664+
1665+ GIVEN: A stored vehicle_components.json where Notes appears before connection data
1666+ WHEN: update_json_structure is called on load
1667+ THEN: Notes is the last field in the affected component after migration
1668+ """
1669+ # Arrange: Inject raw data with Notes in the middle
1670+ model_instance ._data = {
1671+ "Format version" : 1 ,
1672+ "Components" : {
1673+ "RC Receiver" : {
1674+ "Notes" : "FrSky receiver" ,
1675+ "Product" : {"Manufacturer" : "FrSky" , "Model" : "R-XSR" },
1676+ "FC Connection" : {"Type" : "RCin/SBUS" , "Protocol" : "SBUS" },
1677+ }
1678+ },
1679+ }
1680+
1681+ # Act
1682+ model_instance .update_json_structure ()
1683+
1684+ # Assert
1685+ rc_fields = list (model_instance ._data ["Components" ]["RC Receiver" ].keys ())
1686+ assert rc_fields [- 1 ] == "Notes" , f"Notes should be last after update_json_structure, got: { rc_fields } "
1687+ assert model_instance ._data ["Components" ]["RC Receiver" ]["Notes" ] == "FrSky receiver"
0 commit comments