diff --git a/docs/plans/2026-07-27-md2doc.md b/docs/plans/2026-07-27-md2doc.md index 847aee0..8f1609f 100644 --- a/docs/plans/2026-07-27-md2doc.md +++ b/docs/plans/2026-07-27-md2doc.md @@ -610,20 +610,48 @@ def test_add_cell_shading_inserts_shd_in_tcPr(): def test_register_multilevel_numbering_creates_abstractNum_with_3_levels(): - """H3 Route B: register creates abstractNum with 3 w:lvl elements.""" + """H3 Route B: register creates ONE abstractNum with 3 w:lvl elements (ilvl 0/1/2). + + BEHAVIORAL: traces num_id → w:num → w:abstractNumId → w:abstractNum, then + asserts THAT abstractNum (not all 9 default abstractNums in the template) + has exactly the levels we registered. A `return 1` no-op mutation must FAIL + because no w:num with our id exists. + """ doc = Document() num_id = register_multilevel_numbering(doc, levels=3, kind="bullet") assert isinstance(num_id, int) - # Inspect numbering.xml part + tmp = Path(__file__).parent / "_tmp_num.docx" doc.save(tmp) try: with zipfile.ZipFile(tmp) as z: num_xml = z.read("word/numbering.xml") root = ET.fromstring(num_xml) - lvls = root.findall(f".//{{{W_NS}}}lvl") - # at least 3 levels in the abstractNum we just registered - assert len(lvls) >= 3, f"expected >=3 levels, found {len(lvls)}" + + # Find w:num matching our num_id + our_num = None + for num_el in root.findall(f"{{{W_NS}}}num"): + if num_el.get(f"{{{W_NS}}}numId") == str(num_id): + our_num = num_el + break + assert our_num is not None, f"No with numId={num_id} (function did nothing)" + + # Follow to abstractNumId + abstract_ref = our_num.find(f"{{{W_NS}}}abstractNumId") + assert abstract_ref is not None, "missing w:abstractNumId in our w:num" + abstract_id = abstract_ref.get(f"{{{W_NS}}}val") + + # Find that abstractNum and verify it has the 3 levels + our_abstract = None + for an in root.findall(f"{{{W_NS}}}abstractNum"): + if an.get(f"{{{W_NS}}}abstractNumId") == abstract_id: + our_abstract = an + break + assert our_abstract is not None, f"No with id={abstract_id}" + + lvls = our_abstract.findall(f"{{{W_NS}}}lvl") + ilvls = sorted(int(l.get(f"{{{W_NS}}}ilvl")) for l in lvls) + assert ilvls == [0, 1, 2], f"expected ilvl=[0,1,2] in OUR abstractNum, got {ilvls}" finally: tmp.unlink() @@ -1124,7 +1152,12 @@ def test_flat_bullet_list_produces_3_items_at_ilvl_0(): assert ilvls == [0, 0, 0], f"expected all ilvl=0, got {ilvls}" -def test_nested_bullet_list_uses_increasing_ilvl(): +def test_nested_bullet_list_uses_increasing_ilvl_and_resolves_to_multilevel_abstractNum(): + """BEHAVIORAL: not only must paragraphs have ilvl=[0,1,2], but the numId they + reference must resolve to an abstractNum that actually defines those levels. + A no-op register_multilevel_numbering (return 1) would leave paragraphs + pointing to default-template numId=1 (single-level), so the test must FAIL. + """ html = """