vitest-snap

Selectors

Reference for all built-in selectors.

Overview

A selector is a string that points to one or more nodes inside an object or array. Selectors are accepted by filters and redactions to identify which parts of the snapshot to transform.

// remove a nested field
new ExcludeFilter(".user.password");

// redact every token in an array
new ReplacedRedaction(".tokens[]", "[TOKEN]");

// sort a deeply-nested array
new SortedRedaction(".**.tags");

Selectors compose: segments are simply concatenated, so .user.address[0].street is read as key user → key address → index 0 → key street.

Syntax

SelectorMatches
.keyProperty key on an object
["key"]Same, bracket notation
.a.b.cNested path
[n]Array item at index n
[-1]Last array item
[]All array items
[start:end]Array slice (negative indices supported)
.*All keys of an object (wildcard)
.**All descendants (recursive)
.**.keyAll key fields anywhere in the tree

TypeScript support

Selector<T> is a template-literal type that computes every valid path for a given type. Pass your data type as the generic argument and the selector string becomes type-checked:

type User = { name: string; address: { city: string; zip: string } };

// valid selectors inferred from User
new ExcludeFilter<User>(".address.city"); // ✓
new ExcludeFilter<User>(".address"); // ✓
new ExcludeFilter<User>(".missing"); // ✗ type error

Alternatively, let TypeScript infer the type from the value passed to a matcher:

const user = { name: "Alice", address: { city: "Paris", zip: "75001" } };

await expect(user).toJsonSnapshot({
  name: "user",
  redactions: [
    new ReplacedRedaction(".address.zip", "[ZIP]"), // type-checked against User
  ],
});

The type is resolved recursively up to a depth of 8 levels. For unknown or any inputs the selector falls back to an unconstrained string, accepting any valid segment sequence.

NoInfer and type inference

Selector arguments use NoInfer<Selector<T>> internally, which prevents TypeScript from inferring T from the selector string itself. This forces T to be resolved from the object being tested, keeping the constraint meaningful.

Examples

// replace a top-level secret field
await expect({ name: "Alice", password: "s3cr3t" }).toJsonSnapshot({
  name: "user",
  redactions: [new ReplacedRedaction(".password", "[REDACTED]")],
});
// snapshot: { "name": "Alice", "password": "[REDACTED_1]" }
// replace every item in an array
await expect({ tokens: ["abc123", "def456"] }).toJsonSnapshot({
  name: "tokens",
  redactions: [new ReplacedRedaction(".tokens[]", "[TOKEN]")],
});
// snapshot: { "tokens": ["[TOKEN_1]", "[TOKEN_2]"] }
// replace a password at any depth in the object
await expect(deeplyNestedUser).toJsonSnapshot({
  name: "nested-user",
  redactions: [new ReplacedRedaction(".**.password", "[REDACTED]")],
});
// exclude a whole nested section
await expect(user).toJsonSnapshot({
  name: "user-public",
  filters: [new ExcludeFilter(".address")],
});
// sort a deeply-nested array of tags
await expect(post).toJsonSnapshot({
  name: "post",
  redactions: [new SortedRedaction(".**.tags")],
});

Last updated on

On this page