skills/vitest-snap-redact-data/SKILL.md
Apply redactions to replace or stabilize values before snapshotting: ValueRedaction(predicate, replaced, counting), ReplacedRedaction(selector, replaced, counting), SortedRedaction(selector). Covers counting behaviour ([Tag] → [Tag_1], [Tag_2]), deepest-first traversal order, default Date redaction override (custom array replaces it), .** selector root replacement risk, and Selector<T> typed paths. Use when configuring the redactions option on any matcher.
npx skillsauth add Odonno/vitest-snap vitest-snap-redact-dataInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
import {
ValueRedaction,
ReplacedRedaction,
SortedRedaction,
} from "vitest-snap";
await expect(data).toJsonSnapshot({
redactions: [
new ValueRedaction((_, v) => v instanceof Date, "[Date]"),
new ReplacedRedaction(".password", "[REDACTED]"),
new SortedRedaction(".tags"),
],
});
// ValueRedaction — replace any node where predicate returns true
new ValueRedaction(
predicate: (path: string, value: unknown) => boolean,
replaced: string,
counting?: boolean, // default: true
)
// ReplacedRedaction — replace all nodes matched by selector
new ReplacedRedaction(
selector: Selector<T>,
replaced: string,
counting?: boolean, // default: true
)
// SortedRedaction — sort array at matched path (ascending, default JS comparison)
new SortedRedaction(selector: Selector<T>)
When counting=true (default), the replacement string gets _1, _2, … suffixes per occurrence:
// { createdAt: new Date(), updatedAt: new Date() }
// → { "createdAt": "[Date_1]", "updatedAt": "[Date_2]" }
new ValueRedaction((_, v) => v instanceof Date, "[Date]");
// disable counting — all occurrences get the same literal string
new ReplacedRedaction(".id", "[ID]", false);
// → all .id values become "[ID]" (no suffix)
import { ValueRedaction, ReplacedRedaction } from "vitest-snap";
await expect(data).toJsonSnapshot({
redactions: [
new ValueRedaction((_, v) => v instanceof Date, "[Date]"), // re-add default
new ReplacedRedaction(".password", "[REDACTED]"),
],
});
type User = { name: string; sessions: { token: string }[] };
new ReplacedRedaction<User>(".sessions[].token", "[TOKEN]"); // ✓ type-checked
new ReplacedRedaction<User>(".tok", "[TOKEN]"); // ✗ compile-time error
Redactions apply deepest-first (post-order). Nested nodes are replaced before their parents.
This means a parent-level selector like .** will overwrite all previously redacted children.
| Pattern | Meaning |
| --------------- | ---------------------------------------------- |
| .prop | Named property |
| .prop[] | All elements of array property |
| .prop[].child | Named child of each array element |
| .** | Deep wildcard — all descendants including root |
Wrong:
await expect(data).toJsonSnapshot({
redactions: [new ReplacedRedaction(".password", "[REDACTED]")],
// Dates no longer redacted — raw Date strings in snapshot → flaky tests
});
Correct:
import { ValueRedaction, ReplacedRedaction } from "vitest-snap";
await expect(data).toJsonSnapshot({
redactions: [
new ValueRedaction((_, v) => v instanceof Date, "[Date]"),
new ReplacedRedaction(".password", "[REDACTED]"),
],
});
Providing redactions: [...] completely replaces the default [new ValueRedaction(isDate, "[Date]")].
Dates become raw serialized strings in the snapshot, causing flaky tests on every run.
Source: src/options/index.ts — DEFAULT_OPTIONS
Wrong:
// expects "[Date]" in snapshot — actual value is "[Date_1]"
new ValueRedaction((_, v) => v instanceof Date, "[Date]");
Correct:
// pass counting=false to get the literal string
new ValueRedaction((_, v) => v instanceof Date, "[Date]", false);
counting=true (default) transforms "[Date]" into "[Date_1]", "[Date_2]", etc.
Asserting the literal replacement string will always see mismatches on the first occurrence.
Source: src/redactions/counting.ts; src/redactions/index.ts
Wrong:
// intending to redact all nodes — actually replaces the root
new ReplacedRedaction(".**", "[REDACTED]");
// result: "[REDACTED_N]" — entire object gone
Correct:
// use ValueRedaction with a predicate to target specific value types
new ValueRedaction((_, v) => typeof v === "string", "[REDACTED]");
The .** deep segment matches ALL nodes including the root (path=[]). With deepest-first
ordering, the root is replaced last, overwriting the entire result.
Source: src/functions/object.ts — match deep case
Wrong:
// ".sesions[].token" is a silent no-op at runtime
new ReplacedRedaction(".sesions[].token", "[TOKEN]");
Correct:
type User = { sessions: { token: string }[] };
new ReplacedRedaction<User>(".sessions[].token", "[TOKEN]");
Without the generic argument, a typo becomes a silent runtime no-op instead of a compile-time error.
Source: src/selectors/index.ts
Wrong:
new SortedRedaction(".tag"); // .tag is a string, not an array
Correct:
new SortedRedaction(".tags"); // ensure selector points to an array
sortAtPath returns the value unchanged when it is not an array. No error is thrown; a typo
silently produces an unsorted snapshot.
Source: src/functions/object.ts — sortAtPath
| Class | Mutates input | Counting default | Silent on miss |
| ------------------- | ------------- | ---------------- | --------------------- |
| ValueRedaction | No (clones) | true | N/A (predicate-based) |
| ReplacedRedaction | No (clones) | true | Yes |
| SortedRedaction | No (clones) | N/A | Yes |
Redactions within the redactions array are applied sequentially in array order, each on the
result of the previous. Within each redaction, nodes are visited deepest-first.
input → redaction[0].apply() → redaction[1].apply() → ... → serialized snapshot
tools
Write snapshot tests with toJsonSnapshot, toYamlSnapshot, toMarkdownSnapshot, toTextSnapshot. Covers SnapOptions (dir, name, fileExtension, args, indent), auto-naming from test name, parametric snapshots with test.each and args, Markdown table rendering rules, and yaml peer dependency for toYamlSnapshot. Use when writing any vitest-snap matcher call.
testing
Install vitest-snap, register matchers via side-effect import in Vitest setupFiles, configure global defaults with configureGlobalSnapOptions. Covers option merge order (defaults → global → per-call), yaml peer dependency for toYamlSnapshot, UndefinedFilter/Date redaction defaults, and snapshot file migration when changing global dir.
testing
End-to-end guide for adding vitest-snap to an existing Vitest project: install package, register matchers in setupFiles via side-effect import, write first toJsonSnapshot call, understand auto-naming and built-in defaults (UndefinedFilter, Date redaction), then add a filter or redaction. Use when bootstrapping vitest-snap from scratch.
testing
Apply filters to remove nodes before snapshotting: UndefinedFilter (default), NullFilter, ExcludeFilter(selector), IncludeFilter(selector[]). Covers Selector<T> typed path DSL, bare selector string shorthand for IncludeFilter, adjacent IncludeFilter auto-merge via regroupFilters, default filters override behaviour (custom array replaces UndefinedFilter), and array slot preservation semantics. Use when configuring the filters option on any matcher.