"""Tests for the XML writer (Excel data -> GX Works3 XML).""" from __future__ import annotations from pathlib import Path from mitsubishi_label_converter.models import ( GlobalVarSection, ProjectMetadata, StructMember, Variable, ) from mitsubishi_label_converter.parser import ( parse_global_vars, parse_project_metadata, ) from mitsubishi_label_converter.xml_writer import write_xml def make_sections() -> list[GlobalVarSection]: return [ GlobalVarSection( name="Global", retain=False, variables=[ Variable("Always_ON", "SM400", "BOOL", "", line_number=3), Variable("version", "D450", "STRING[12]", ""), Variable( "PLC_FEL_STA", "", "ST_Din_STA", "", line_number=39, members=( StructMember("Value", "D0.0"), StructMember("Status", "D1"), ), ), Variable( "Switch_status", "SD200", "INT", "CPU switch status (0: RUN, 1: STOP)", line_number=14, ), ], ), GlobalVarSection( name="Global", retain=True, variables=[ Variable( "PulseTot", "D600", "WORD", "", line_number=31, retain=True ), ], ), ] def make_metadata() -> ProjectMetadata: return ProjectMetadata( file_header={ "companyName": "MITSUBISHI ELECTRIC CORPORATION", "companyURL": "http://www.mitsubishielectric.com", "productName": "GX Works3", "productVersion": "1.126G", "creationDateTime": "2026-10-01T20:59:19+02:00", }, content_header={ "name": "21014JON-B14_20260930", "modificationDateTime": "2026-10-01T20:57:58+02:00", }, project_add_data={"projectPlcType": "FX5U"}, ) def test_full_round_trip(tmp_path: Path) -> None: """XML -> sections -> XML: the parsed data must be identical.""" out = tmp_path / "regenerated.xml" sections = make_sections() write_xml(sections, make_metadata(), out) # The regenerated file must parse back to the same sections. reparsed = parse_global_vars(out) assert reparsed == sections meta = parse_project_metadata(out) assert meta.file_header == make_metadata().file_header assert meta.content_header == make_metadata().content_header assert meta.project_add_data == make_metadata().project_add_data def test_output_is_utf16_le(tmp_path: Path) -> None: out = tmp_path / "regenerated.xml" write_xml(make_sections(), make_metadata(), out) raw = out.read_bytes() # UTF-16 LE BOM assert raw[:2] == b"\xff\xfe" # XML declaration must declare utf-16 assert raw.decode("utf-16").startswith('') def test_retain_flag_written(tmp_path: Path) -> None: out = tmp_path / "regenerated.xml" write_xml(make_sections(), make_metadata(), out) text = out.read_text(encoding="utf-16") assert '' in text # the plain section: retain attribute must NOT be present assert '' in text def test_member_addresses_written(tmp_path: Path) -> None: out = tmp_path / "regenerated.xml" write_xml(make_sections(), make_metadata(), out) text = out.read_text(encoding="utf-16") assert 'member name="Value" address="D0.0"' in text assert 'member name="Status" address="D1"' in text def test_comment_written(tmp_path: Path) -> None: out = tmp_path / "regenerated.xml" write_xml(make_sections(), make_metadata(), out) reparsed = parse_global_vars(out) switch_status = reparsed[0].variables[3] assert switch_status.comment == "CPU switch status (0: RUN, 1: STOP)" def test_line_numbers_written(tmp_path: Path) -> None: out = tmp_path / "regenerated.xml" write_xml(make_sections(), make_metadata(), out) reparsed = parse_global_vars(out) assert [v.line_number for v in reparsed[0].variables] == [3, None, 39, 14] def test_real_file_full_round_trip(tmp_path: Path) -> None: """The real asset: XML -> sections -> XML -> sections must be stable.""" asset = Path(__file__).parents[1] / "assets" / "Global.xml" if not asset.is_file(): import pytest pytest.skip("assets/Global.xml not present") original_sections = parse_global_vars(asset) original_meta = parse_project_metadata(asset) out = tmp_path / "regenerated.xml" write_xml(original_sections, original_meta, out) reparsed_sections = parse_global_vars(out) reparsed_meta = parse_project_metadata(out) assert reparsed_sections == original_sections assert reparsed_meta.file_header == original_meta.file_header assert reparsed_meta.content_header == original_meta.content_header assert reparsed_meta.project_add_data == original_meta.project_add_data