Skip to content

Add fast-check property suite; fix two escaping bugs it found - #113

Merged
sjsyrek merged 4 commits into
mainfrom
feat/property-based-testing
Jul 31, 2026
Merged

Add fast-check property suite; fix two escaping bugs it found#113
sjsyrek merged 4 commits into
mainfrom
feat/property-based-testing

Conversation

@sjsyrek

@sjsyrek sjsyrek commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a fast-check property-based test suite (tests/property/) enforcing round-trip laws across all 11 format parsers plus the preservation, ICU, batch-splitting, message-preprocess, and validator layers — and fixes the two real bugs the suite found during evaluation: TOML file corruption from unescaped U+2028/U+2029 and silent leading-space loss in .properties.

Changes Made

  • Fixed TOML U+2028/U+2029 corruption (d0dfd35) — a translation containing a Unicode line/paragraph separator round-tripped fine on the first sync, silently appended duplicate keys on the second (JS . in the entry-line regex excludes line terminators, so the written line was re-classified as new), and made the third sync throw on parse. encodeTomlString now escapes both characters; literal-string values gaining one fall back to double quotes.
  • Fixed .properties leading-space loss (158486d) — the writer emitted leading spaces raw and the value parser strips unescaped whitespace after the separator, so a translation beginning with a space lost it on every subsequent read. The writer now escapes each leading space (\ ); the reader already handled the escape.
  • Added tests/property/ (64deabd) — parameterized laws over every parser in the registry: translated values survive reconstruct/extract intact (L1), re-applying the same translations is byte-identical (L2), and identity syncs are fixed points (L3). PO and XLIFF, whose extract reads the source slot while reconstruct writes the target, get idempotence + source-preservation laws instead. Plus identity/multiset laws for placeholder preservation, segment-stability laws for the ICU parser, and a directed pin of the current (un-normalized) cache-key behavior.
  • Extended the suite (5081f77) — translateBatch chunking (≤ 50 texts per call), dedup, duplicate-position index mapping, and call-count arithmetic against a fake client; positional invariants for the expandPlurals → detectIcu → writebackPlurals pipeline; validator totality and guaranteed detection of dropped/added placeholders.
  • Added fast-check as a devDependency — the only new dependency; properties run via plain fc.assert inside it(), no jest integration layer.

Test Coverage

  • 42 new tests: 39 in tests/property/ (24 fc properties × 200 seeded-random cases each ≈ 5,000 generated law checks per run) and 3 regression tests in tests/unit/formats-roundtrip.test.ts shipped with the fixes.
  • Knobs: FC_NUM_RUNS deepens a run (2000 ≈ 7s); FC_SEED/FC_PATH replay a recorded counterexample exactly.
  • Full suite: 276 suites / 6,140 tests green in 35s (+1.6s vs. before this branch).

Backward Compatibility

Maintained: no API, CLI, or config surface changes.
Same behavior: TOML/properties output is unchanged for all values that do not contain U+2028/U+2029 or leading spaces; for values that do, output changes from silently corrupting/lossy to correct (files written by the buggy path are read back correctly).
Breaking changes: None.

Size: Medium

Two one-function fixes plus a self-contained new test directory; no production code paths restructured.

🤖 Generated with Claude Code

sjsyrek and others added 4 commits July 31, 2026 10:17
A translation containing a Unicode line or paragraph separator was
written raw into the double-quoted value. The entry-line scan on the
following sync then failed to match the line (JavaScript's dot excludes
line terminators), classified it as unrecognized, and re-appended the
key as a duplicate — so the sync after that could not parse the file
at all. Literal-string values now also fall back to double quotes when
a translation gains one of these characters.

Found by the fast-check property suite; closes cli-0jkg.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The value parser strips unescaped whitespace after the separator, so a
translation beginning with a space silently lost it on the next read.
The writer now escapes each leading space; leading tabs, trailing
spaces, and newlines were already handled.

Found by the fast-check property suite; closes cli-ekmg.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…on utils

Round-trip laws parameterized over all 11 format parsers: translated
values survive reconstruct/extract, re-applying the same translations
is a no-op, and identity syncs are fixed points. PO and XLIFF, whose
extract reads the source slot while reconstruct writes the target,
get idempotence and source-preservation laws instead. Preservation
utilities and ICU parsing get identity and segment-stability laws.

200 seeded-random cases per law by default; FC_NUM_RUNS deepens a run,
FC_SEED/FC_PATH replay a recorded counterexample.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…nd validator

translateBatch: chunks never exceed TRANSLATE_BATCH_SIZE, unique texts
are each sent once, every input position (duplicates included) maps to
its own translation, and the call count matches the chunk arithmetic.

Message preprocess: expandPlurals/detectIcu/writebackPlurals preserve
the positional invariants the pipeline documents — original texts stay
an unchanged prefix, each plural slot points at its own appended value,
and writeback touches exactly the slots that have results.

Validator: total on arbitrary input, identity is never a structural
error, and dropped or added placeholders are always flagged.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sjsyrek
sjsyrek merged commit 4e6c166 into main Jul 31, 2026
5 of 6 checks passed
@sjsyrek
sjsyrek deleted the feat/property-based-testing branch July 31, 2026 14:44
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