test(document-schema.js): reach 100% mutation score - #1256
Merged
Merged
Conversation
…nary for the unbounded half parseRangeReference computed endRaw with the same separatorIndex-based ternary as startRaw, but when separatorIndex is -1, ref.slice(separatorIndex + 1) already equals ref.slice(0) (the whole string) -- the value the ternary's other branch would have picked anyway. It was never a genuine second condition, just a restatement of what slice's own arithmetic already produces.
…ion, not a regex capture rgbHexToColor matched the whole (optionally hashed) string against one regex with a capture group, then re-checked the capture for undefined even though a successful match always populates it. Splitting the '#' strip into a plain startsWith/slice and validating the remaining six digits against their own pattern removes the redundant capture-group check and needs no match-array indexing at all.
…record guard isGroupWrapper checked isRecord(value.node) before validating the rest of a wrapper's shape, but every one of its nine call sites only invokes it after value.node has already passed a real Zod object schema's own safeParse -- which can only succeed against a genuine non-null, non-array record. The guard could never actually reject anything at any real call site, so it was dead defensive code rather than a load-bearing check.
…hared structural walks extentOf, childWrappers, and every rebuildXGroup function had a dedicated arm per MintWrapper kind (shape, slide, draw page, section, construct) even though every one of those arms did the identical structural walk -- recurse into anything carrying its own node and children, collect a bare paragraph leaf, skip everything else. Only the anchor-group arm (heading/list) genuinely differs, since it alone contributes its own paragraph. Replacing the per-kind arms with one generic walk removes mutation-equivalent branches that could never be observed to differ, and widens FlowChild to cover a slide's and draw page's own child vocabularies so the same walk serves every wrapper kind. Also removes the ref-defined check from every rebuildXGroup's `unchanged` computation: a wrapper only carries a ref when its own candidate search minted an entry, which requires at least two matching paragraph/run positions inside its own extent to actually get their keys stripped -- so a minted ref always implies at least one descendant actually changed, making the ref check redundant with the children-equality check beside it. paragraphTuple/runTuple drop their per-key presence check: both are only ever called with a key list already proven (by commonParagraphKeys/commonRunKeys) to be present on every paragraph or run in scope, so the check could never fire. commonRunKeys drops its empty-extent early return for the same reason: an empty runs array already makes every downstream loop over it a no-op. The plan()/mint() length and switch-arm guards removed are equivalent for the identical reason -- an empty extent or an empty children array already makes the code that would have been skipped behave as a no-op on its own.
…uard Array.prototype.includes compares by strict equality against IMAGE_FORMATS's own string elements, so it can only return true for a value that already is one of those strings -- a non-string value never satisfies it either way, making the preceding typeof check redundant. Also adds coverage for isContentBlock/isContentConstructStart/ isRunConstructExtent rejecting a partly-invalid array, a wrong-kind discriminant, and an out-of-range run bound respectively, and for the hand-authored content-json-schema-defs fragments' own field-level shape.
…aries Covers the stack re-check a pop loop needs when a single open heading/list item shrinks to zero, and when two open items in a row must both pop -- the boundary where a wrong stack-top index would stop after only one pop instead of continuing. Also covers a non-paragraph leaf attaching inside a single open list item's own children (both at a section root and inside a shape), and pins ConstructMarkerImbalanceError's own name and message text for both imbalance kinds.
…evel boundaries Adds coverage for StyleEntry/StyleParagraphProperties/StyleRunProperties and the ban-list fields (frames, sourcePath, styleId, list) they reject, plus numeric-range and enum-membership boundaries on the fields each schema accepts.
…ries Covers the outermost-first overlay order (a nested ref's key wins over an ancestor's), a ref that resolves to no table entry, and resolution across every wrapper kind's own extent boundary.
Covers exact-rational and dimension-vector field validation on the math expression grammar, and the MathML round-trip helpers' own malformed-input rejection paths.
Covers documentFromJson's rejection of malformed input at each schema boundary and the parse-error messages it surfaces.
canonicalise's stable-key-ordering and NaN/undefined-normalisation behaviour was previously exercised only incidentally through callers (factor-styles' candidate grouping); this pins the function's own contract directly.
Pins the style-property-merging helpers' gap-fill-never-overwrite contract directly, previously exercised only incidentally through flatten's own resolution tests.
…ants activate
@stryker-mutator/vitest-runner activates a non-static mutant inside a
beforeAll() hook, which runs strictly after a test file's top-level
imports have already evaluated. A mutation inside a top-level
z.object({...}) or object-literal declaration therefore executes
before the activation switch is ever set, so it can never be observed
regardless of what a covering test asserts.
Confirmed directly: CONTENT_DEFS.Color's r field mutated to {}
reproducibly survived Stryker's sandbox while the identical edit,
applied by hand and run through plain vitest run, failed the exact
test Stryker itself says covers it -- proving the miss was this
activation-timing gap, not a missing test.
ignoreStatic: false routes static mutants through the synchronous
"static" activation mode instead, set before any test file's own
imports run, at the cost of re-running each static mutant's whole
related suite (measured at ~92% of this package's dry-run time, since
57% of its mutants are static by construction -- it is almost entirely
schema declarations). breakThreshold stays provisional pending a full
run under the corrected configuration.
…guard getMathMlJsonSchemas() registers exactly the three ids mathMlDef ever looks up before calling z.toJSONSchema(), so the registry's conversion result always carries an entry for each — the undefined branch could never be hit by any real input, only by a mutation flipping the lookup itself, which the existing tests already catch via the resulting value.
…ive checks Remove MintedEntry's firstVisit field and the visit-index bookkeeping that fed it: entries.values() already yields ascending pre-order-visit order for free, since each entry is inserted into the Map at the moment plan() first mints it and Map iteration is insertion order, so the recorded index was a duplicate of information the Map already carries. Also strip the throwing guards in assertHeadingAnchor and assertListAnchor. PARAGRAPH_STYLE_KEYS, the only source a strip's key list is ever drawn from, never contains headingLevel or list, so stripping can never remove either grouping signal and no real input could ever reach the throw. Add a regression test pinning that a paragraph gains no style property when only a nested descendant's style changed.
…anchor type Replace assertHeadingAnchor/assertListAnchor, two runtime no-op assertion functions used only to re-narrow rebuildParagraph's loosened ContentParagraph return back to a heading/list anchor's own required type, with a generic rebuildParagraph<P extends ContentParagraph> that preserves the input's exact type through the round trip. The two assertion functions carried no runtime check, since the narrowing they performed can never actually fail per the module's own documented invariant, so their bodies were true no-ops. A Stryker BlockStatement mutant reducing an already-inert body to an empty block is therefore unobservable by any test by construction, not a missing test — removing the no-op function entirely is the fix, not trying to make it fail differently.
A clean run now kills or times out every mutant across the package with zero survived and zero no-coverage, confirming the provisional threshold can be replaced with the real gate.
Mearman
marked this pull request as ready for review
September 12, 2026 22:13
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Mearman
enabled auto-merge (rebase)
September 12, 2026 22:14
Contributor
|
🎉 This PR is included in version 7.11.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Works through document-schema.js's mutation survivors from its original 22% baseline: removes several redundant defensive checks that made a mutant provably unobservable (a hex-colour capture-group re-check, an isImageFormat typeof guard, isGroupWrapper's unreachable node-record guard, factor-styles' per-kind wrapper dispatch, factor-styles' assertHeadingAnchor/assertListAnchor no-op assertion functions, content-json-schema-defs' mathMlDef undefined guard), collapses the wrapper walk to one generic structural recursion shared by every MintWrapper kind, makes rebuildParagraph/stripParagraphKeys generic so a heading/list anchor's own required type survives the round trip with no re-narrowing needed, and adds targeted unit coverage across a1/canonicalise/color/content/decompose/definitions/flatten/math/mathml/package-node/schema-io/style for the boundaries that were previously reachable but untested.
Also flips ignoreStatic to false for this package specifically: its own vitest-runner activation switch is set inside a beforeAll() hook, which runs after a test file's top-level imports already evaluated, so a mutation inside a top-level z.object({...}) declaration could never be observed under the default ignoreStatic:true, independent of test quality.
A full
stryker run stryker.config.tsnow reports 100.00 across every file with 0 survived and 0 no-coverage mutants, and breakThreshold is locked at 100.