Skip to content

test(docx): give every value a node holds a fate, and name the known gaps - #938

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-value-type-ledger
Oct 21, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-value-type-ledger

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Why

DocxNodeFieldLedgerTest gives every field of every node, and every output option, a fate:
written, reported where the author set it, or nothing for Word to carry. It fails on a field with
no entry. It stopped at the field. The values those fields hold are where most of what a page draws
is said: a style's colour, a run's alignment, a border's width, a chart's legend, an enum's
constants. A part added to one of them passed the ledger with no decision, written by the PDF
backend at once and possibly lost from the Word file without a note.

What changed

  • DocxValueFieldLedgerTest (new) walks every node class's record components and
    DocumentOutputOptions' components, through generics, arrays, wildcards, type variables and
    sealed types. It collects each com.demcha.compose.* value type with its parts:

    • a record's components;
    • a final class's fields, its superclasses' included;
    • an enum's constants.

    Nodes stop the walk: they are the node ledger's. A type is read once, so a recursive bound
    cannot loop. The test fails on:

    • a value type or a part with no entry;
    • an entry for a part that is gone;
    • a held type any class may implement or extend (an unsealed interface, a non-final class);
    • a node type held that is neither DocumentNode nor a concrete class the node ledger lists, as
      one from another package would be.

    Today it holds 107 types and 421 parts: 149 written, 221 reported, 12 with nothing for Word to
    carry, 39 gaps.

  • A fourth fate, GAP: a case the export knows it loses and its report does not name yet.

    • Its note must open with one of the known cases, kept as shared constants in the node ledger;
      fields() refuses any other. A gap is a known defect, held so the ledger stays true while it
      is open.
    • The cases were traced in the export's code, each with a scenario:
      • a top below zero (TOP_PULL): dropped on a picture, a barcode or a block written as a table,
        and on another block, what the space above cannot give;
      • a bottom below zero (BOTTOM_PULL) on a list, a table, a row, a stack, a canvas, a shape
        container in the flow or a block with no paint of its own: owePendingSpacingAfter keeps
        only a positive amount, so the next block is not pulled up;
      • a table cell's or a panel's padding below zero, and a row's at its sides, written as none;
      • the space below a table ending a cell set at its middle or foot, named only for a shape
        container;
      • a panel's right border drawn up to its width and half a point short of its cell's edge, so
        the gridline does not cover it;
      • a cell's top or bottom margin that Word gives the whole row (a cell opening with a table, or
        spanning rows);
      • pictures in a list's items and markers, placed by the list's first line;
      • a text badge's verticalAlign, and one where a paragraph's lines are not read;
      • a drawing the page paints over a text or picture layer, written behind it;
      • a stack composed in a table cell writing its one layer at the cell's top left;
      • a layer set past its stack's right edge in a table cell;
      • with no layout, a stack's one layer moved down, and a row's fixed or weighted column beside
        an auto one;
      • a box shape written as a panel in a cell narrower than it.
  • DocxNodeFieldLedgerTest: 48 entries one of those cases loses are now gaps. 20 of them were
    WRITTEN; the rest were REPORTED for other cases, which their notes keep after the gap's.
    Three fields move from written to reported, which the report already names:

    • AlignNode.align and RowNode.arrangement: placeComposedAcross, nameTheColumnsLeftToWord
      and the no-layout note;
    • ParagraphNode.indentStrategy: paragraphLost and zoneParagraphLost.

    Fate, Entry and fields() are package-private, for the value ledger to share.

  • CHANGELOG (Tests).

No production code changes.

Verification

  • Fail-closed: eight sabotages, each run alone and each caught by the test named for it:

    • a type's entry removed;
    • a part removed;
    • an entry for no part;
    • a gap with no case;
    • a gap whose note opens with no known case;
    • and, in core installed, a new field on DocumentMetadata, a field of the open interface
      type NodeDefinition, and a node record nested in DocumentMetadata, outside the ledgered
      packages.
  • Corpus: a probe over the 62 corpus documents finds the conditions of several gaps:

    • a right border pulled in, 19 times, by 1.2 to 2.4pt;
    • a top below zero, in 10 documents;
    • a bottom below zero, on a shape container in 1 template;
    • verticalAlign in a shape container's paragraph, in 2 templates;
    • a drawing ordered after a text layer, in 1.

    None of the corpus's rows has cell margins Word evens. Each corpus case is measured in Word and
    closed in its own PR. The rest are outside the corpus.

  • ./mvnw -B -ntp clean verify -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing,:graph-compose-qa,:graph-compose-coverage -am
    → BUILD SUCCESS (render-docx 1989 tests, qa 1828). The JDK 17 checks pass: the knowledge
    pack, @Beta stability doc, claims and routes, and core javadoc with 0 warnings.

Lane: test (render-docx ledgers). No public API change.

…gaps

DocxNodeFieldLedgerTest gives every field of every node a fate and
stopped at the field: a part added to a value one holds - a style's
colour, a run's alignment, a border's width, an enum's constant -
passed it with no decision.

DocxValueFieldLedgerTest walks the value types from the node classes
and the output options, down to the last one they hold, through
generics, arrays and sealed types, and gives each of their 421 parts a
fate. It fails on a type or part with no entry, on an entry for a part
that is gone, on a type any class may implement or extend, and on a
node type held from outside the node ledger's packages.

Read against the export, some parts are lost in a case the report does
not name. Each is a gap, a fourth fate whose note opens with one of the
known cases: 39 parts of values and 48 node fields, among them a top
below zero, a panel's or a cell's padding below zero, a right border
drawn short of its cell's edge, a cell margin Word gives the whole row,
pictures in a list's items, a text badge's seat and a drawing painted
over a layer of text. Node entries that said those cases were written
now say so. A node's alignment, a row's arrangement and a paragraph's
indent strategy move from written to reported, which the report
already names.

No production code changes.
@DemchaAV
DemchaAV marked this pull request as draft October 21, 2026 20:17
owePendingSpacingAfter keeps only a positive amount, and a list, a
table, a row, a stack, a canvas, a shape container in the flow and a
block with no paint of its own hand it their bottom edge. A bottom below
zero then does not pull the next block up in Word, and the report says
nothing. It is now the gap BOTTOM_PULL, on DocumentInsets.bottom and on
the node entries it loses.

A panel's top below zero is dropped whole, as a table's is, and
TOP_PULL says so. A row's padding takes SPACE_BELOW_AT_THE_FOOT, as a
table's does, and a row's side padding below zero joins
PADDING_BELOW_ZERO. A page band's face is reported where it may not
travel, as a run's is. The node check asserts that DocumentNode is among
the nodes the walk reaches, so an empty walk cannot pass it.
@DemchaAV
DemchaAV marked this pull request as ready for review October 21, 2026 20:26
@DemchaAV
DemchaAV merged commit 3c17446 into 2.5-dev Oct 21, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-value-type-ledger branch October 21, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant