Skip to content

test(printplay): #204 extract recto-verso back reorder to pure method + 9 tests - #500

Merged
jsboige merged 1 commit into
masterfrom
test/204-printandplay-recto-verso-contract
Jun 16, 2026
Merged

test(printplay): #204 extract recto-verso back reorder to pure method + 9 tests#500
jsboige merged 1 commit into
masterfrom
test/204-printandplay-recto-verso-contract

Conversation

@jsboige

@jsboige jsboige commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

What & why

The Print & Play PDF (PrintAndPlayDocument) prints card backs on the reverse of each sheet so a sheet can be flipped to read the back. To make each back line up behind its matching front when the sheet is turned along its horizontal edge, the backs of each grid row are reversed before rendering — a per-row reversal (not a full-array mirror), because a horizontal flip swaps left/right within each row while preserving row order.

This fragile alignment contract was inlined in Compose() with zero unit coverage:

var backCardsArray = pageBackImages.ToJaggedArray(nbColumns)
    .Select(row => row.Reverse().ToArray()).ToArray().Flatten();

It could only be exercised by rendering a full QuestPDF document and visually inspecting the back alignment. A regression here (e.g. reversing the whole array instead of per-row, or flipping rows too) produces printed sheets whose backs mismatch their fronts — unusable sheets, only caught at print time.

Change

Output-neutral extraction + additive test coverage. No behavior change.

  1. Extracted the inline composition into a pure, deterministic, generic static method:

    public static T[] ReorderBacksForRectoVerso<T>(IList<T> backs, int nbColumns)
        => backs.ToJaggedArray(nbColumns)
                .Select(row => row.Reverse().ToArray())
                .ToArray()
                .Flatten();

    The call site (Compose()) now reads ReorderBacksForRectoVerso(pageBackImages, nbColumns) — identical computation, just unit-testable.

  2. Added PrintAndPlayRectoVersoContractTests (9 tests) pinning the contract:

    • Headline — full rows: 6 backs / 3 cols → [B2,B1,B0,B5,B4,B3] (per-row reversal, row order preserved; explicitly rejects a full-array mirror [B5..B0]).
    • Trailing short row — the lone card on a partial row reverses to itself and stays at the tail.
    • Single column — horizontal flip is identity (1-wide row reverses to itself).
    • One short row / width-2 / empty / single-back — degenerate cases (no crash, correct identity).
    • byte[] grounding — proves the generic works for the production element type (card image bytes), not just string labels.
    • Cell-wise alignment semantic — rebuilds input + output grids and asserts output[row][col] == input[row][cols-1-col] for every cell (the precise within-row mirror).

The building blocks (ToJaggedArray, Flatten) are already pinned by UtilityExtensionsLayoutTests; this PR pins the Print&Play-specific composition — the gap that test file does not cover.

Verification

  • New class: 9/9 pass.
  • Full suite on clean master base: 291 passed / 0 failed / 5 skipped (5 skips = Freeplane GUI + others requiring interactive session) — no regression.
  • Diff: +1 pure static method (output-neutral) + 1 test file. No CSV, no config, no RowsetNb/rscount changes, no workflow/rules touched.

Scope

Dispatch #204 γ — test-coverage expansion on fragile pipeline contracts. Pivot sanctioned by dispatch qbw8vq ("sinon pivote PdfManager layout math"). Additive only; no production behavior change.

🤖 Worker po-2024

… + 9 tests

The Print & Play PDF reverses each grid ROW of card backs before rendering so
backs line up with fronts when the sheet is flipped along its horizontal edge.
This alignment contract was inlined in PrintAndPlayDocument.Compose() with
zero unit coverage — it could only be exercised by rendering a QuestPDF
document and visually inspecting the back alignment. A regression (a full-array
mirror instead of per-row, or flipping rows too) produces printed sheets whose
backs mismatch their fronts — unusable sheets caught only at print time.

Extracted output-neutral into the pure generic static
PrintAndPlayDocument.ReorderBacksForRectoVerso<T>(backs, nbColumns) (identical
computation: ToJaggedArray -> reverse each row -> Flatten) and pinned the
contract with 9 additive tests:
- full rows (per-row reversal, row order preserved)
- trailing short row (singleton reverses to itself, stays at tail)
- single column (horizontal flip is identity)
- one short row / width-2 / empty / single-back degenerate cases
- byte[] grounding (production element type)
- cell-wise alignment semantic: output[row][col] == input[row][cols-1-col]

Full suite on clean master base: 291 passed / 0 failed / 5 skipped (no regression).

Dispatch #204 gamma (cont. po-2024) — pivot sanctioned by dispatch qbw8vq
("sinon pivote PdfManager layout math").

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@clusterManager-Myia

Copy link
Copy Markdown
Collaborator

[NanoClaw]

test(printplay): extract recto-verso back reorder to pure generic method + 8 tests.

Extraction: The inline backs.ToJaggedArray(nbColumns).Select(row => row.Reverse().ToArray()).ToArray().Flatten() is pulled into a generic ReorderBacksForRectoVerso<T> — output-neutral, call site preserves exact computation.

Tests: 8 Fact tests covering: full evenly-divisible rows, trailing short row, single column, short row within larger column count, even width-2, empty backs, single back, production byte[] element type, and a cell-wise alignment contract (output[row][col] == input[row][cols-1-col]). Well-documented doc comments explain the per-row reversal semantics vs full mirror.

No secrets, no issues. LGTM.

@jsboige
jsboige merged commit c2ed3bc into master Jun 16, 2026
3 checks passed
@jsboige
jsboige deleted the test/204-printandplay-recto-verso-contract branch June 16, 2026 17:09
jsboige added a commit that referenced this pull request Jun 17, 2026
…tests (#512)

The Print & Play PDF (PrintAndPlayDocument) computed four derived layout
quantities inline in Compose() with ZERO unit coverage: nbColumns,
nbRows, nbCardsPerPage, nbPages. A regression in any of them (rounding
instead of truncating columns, forgetting the header reserve, flooring
instead of ceiling the page count) silently changes how many sheets
print and how cards distribute across them, caught only by rendering and
eyeballing the PDF.

Extracts the arithmetic output-neutral into a pure, deterministic
ComputePageGeometry (no QuestPDF dependency, no I/O) + a
PrintPlayPageGeometry readonly struct. The call site preserves the exact
computation (verified by reading the old inline block before extraction),
so rendered output is byte-for-byte unchanged.

13 contract tests pin the layout contract additively (mirrors #500's
recto-verso reorder test conventions):
- columns: configured honored when >0; zero/negative fall back to floor
  division; floor-TRUNCATION not rounding (the fragile bit)
- rows: floor division of content height
- cardsPerPage = rows x columns
- pages: ceil(count/perPage); partial last sheet still prints; exact
  multiple; zero cards -> zero pages
- header reserves pageHeight/10 (matches ComposePage header band)
- margin subtracted from both dimensions

Gate-safe: additive tests + output-neutral extraction. The latent
divide-by-zero when cardsPerPage==0 (card larger than content area) is
preserved as-is, NOT guarded, and flagged in the XML doc for a separate
behavior-change PR.

Suite: 342 passed / 0 failed / 5 skipped (baseline 329 + 13 new).

Contributes to #204. Refs #500 (sibling recto-verso extraction).

Co-authored-by: Your <your.email@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Jun 17, 2026
… method + 9 tests (#521)

PdfManager.GenerateAlternateFaceAndBack (the #119 contract) assembles a single
PDF where each card's BACK is emitted immediately before its FRONT so that on a
recto-verso sheet each back lines up behind its matching front, and the ORIGINAL
CardSet order is preserved (so back-less Rules keep their place and appear first).
This ordering is a fragile contract with ZERO unit coverage — a regression
emitting front-then-back, dropping the back-less cards, or reordering by
back-presence silently misaligns every printed sheet, caught only by printing.

Extracts the path-sequencing output-neutral into a pure, deterministic
OrderImagesForAlternateFaceAndBack (static, no MagickImage, no I/O). The call
site emits the exact same path sequence (verified by reading the old inline
builder before extraction), so rendered output is byte-for-byte unchanged.

9 contract tests pin the contract additively (mirror #500 recto-verso reorder +
#512 page-grid geometry conventions):
- per-card: back-then-front when a back exists; front-only when no back;
  empty back == no back (the IsNullOrEmpty guard)
- ordering: original CardSet order preserved (mixed backs NOT grouped); a
  back-less head card stays first (the Rules-first #119 guarantee)
- counts: all-with-back doubles the slot count; all-without-back singles
- pairing: back immediately precedes its OWN front (indices 2i, 2i+1)
- degenerate: empty input emits nothing

Gate-safe: additive tests + output-neutral extraction. Suite 351/0/5
(baseline 342 + 9 new).

Contributes to #204. Refs #500/#512 (sibling recto-verso extractions), #119.

Co-authored-by: Your <your.email@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

2 participants