@adobe/data-testing 0.9.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/dist/conformance/discover.d.ts +7 -0
  4. package/dist/conformance/discover.js +48 -0
  5. package/dist/conformance/discover.js.map +1 -0
  6. package/dist/conformance/effects.type-test.d.ts +1 -0
  7. package/dist/conformance/effects.type-test.js +39 -0
  8. package/dist/conformance/effects.type-test.js.map +1 -0
  9. package/dist/conformance/entity-ref.d.ts +5 -0
  10. package/dist/conformance/entity-ref.js +28 -0
  11. package/dist/conformance/entity-ref.js.map +1 -0
  12. package/dist/conformance/public.d.ts +7 -0
  13. package/dist/conformance/public.js +7 -0
  14. package/dist/conformance/public.js.map +1 -0
  15. package/dist/conformance/record-effects.d.ts +16 -0
  16. package/dist/conformance/record-effects.js +93 -0
  17. package/dist/conformance/record-effects.js.map +1 -0
  18. package/dist/conformance/resolve.d.ts +3 -0
  19. package/dist/conformance/resolve.js +8 -0
  20. package/dist/conformance/resolve.js.map +1 -0
  21. package/dist/conformance/run-actions.d.ts +14 -0
  22. package/dist/conformance/run-actions.js +34 -0
  23. package/dist/conformance/run-actions.js.map +1 -0
  24. package/dist/conformance/run-computeds.d.ts +14 -0
  25. package/dist/conformance/run-computeds.js +45 -0
  26. package/dist/conformance/run-computeds.js.map +1 -0
  27. package/dist/conformance/run-feature.d.ts +30 -0
  28. package/dist/conformance/run-feature.js +61 -0
  29. package/dist/conformance/run-feature.js.map +1 -0
  30. package/dist/conformance/run-spec.d.ts +10 -0
  31. package/dist/conformance/run-spec.empty-cases.test.d.ts +1 -0
  32. package/dist/conformance/run-spec.empty-cases.test.js +14 -0
  33. package/dist/conformance/run-spec.empty-cases.test.js.map +1 -0
  34. package/dist/conformance/run-spec.empty.test.d.ts +1 -0
  35. package/dist/conformance/run-spec.empty.test.js +11 -0
  36. package/dist/conformance/run-spec.empty.test.js.map +1 -0
  37. package/dist/conformance/run-spec.js +83 -0
  38. package/dist/conformance/run-spec.js.map +1 -0
  39. package/dist/conformance/run-spec.test.d.ts +1 -0
  40. package/dist/conformance/run-spec.test.js +28 -0
  41. package/dist/conformance/run-spec.test.js.map +1 -0
  42. package/dist/conformance/run-transactions.d.ts +13 -0
  43. package/dist/conformance/run-transactions.js +31 -0
  44. package/dist/conformance/run-transactions.js.map +1 -0
  45. package/dist/conformance/types.d.ts +29 -0
  46. package/dist/conformance/types.js +3 -0
  47. package/dist/conformance/types.js.map +1 -0
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.js +9 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/match/assert.d.ts +2 -0
  52. package/dist/match/assert.js +19 -0
  53. package/dist/match/assert.js.map +1 -0
  54. package/dist/match/match.d.ts +10 -0
  55. package/dist/match/match.js +82 -0
  56. package/dist/match/match.js.map +1 -0
  57. package/dist/match/match.test.d.ts +1 -0
  58. package/dist/match/match.test.js +42 -0
  59. package/dist/match/match.test.js.map +1 -0
  60. package/dist/match/matchers.d.ts +2 -0
  61. package/dist/match/matchers.js +14 -0
  62. package/dist/match/matchers.js.map +1 -0
  63. package/dist/match/public.d.ts +3 -0
  64. package/dist/match/public.js +5 -0
  65. package/dist/match/public.js.map +1 -0
  66. package/package.json +46 -0
@@ -0,0 +1,14 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { Conformance } from "../index.js";
3
+ // Module with `cases: []` opens an empty `describe` and used to leave the file
4
+ // suite-less. Empty discovery registers a no-op so Vitest still collects a suite.
5
+ Conformance.runSpec({
6
+ state: { create: () => ({ n: 0 }) },
7
+ transitions: {
8
+ "./empty-cases.ts": {
9
+ noop: (state) => state,
10
+ cases: [],
11
+ },
12
+ },
13
+ });
14
+ //# sourceMappingURL=run-spec.empty-cases.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-spec.empty-cases.test.js","sourceRoot":"","sources":["../../src/conformance/run-spec.empty-cases.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,+EAA+E;AAC/E,kFAAkF;AAClF,WAAW,CAAC,OAAO,CAAC;IAClB,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;IACnC,WAAW,EAAE;QACX,kBAAkB,EAAE;YAClB,IAAI,EAAE,CAAC,KAAoB,EAAE,EAAE,CAAC,KAAK;YACrC,KAAK,EAAE,EAAE;SACV;KACF;CACF,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { Conformance } from "../index.js";
3
+ // Features with empty State and no pure transforms still call `runSpec` from a
4
+ // dedicated `spec.test.ts`. Discovery with zero case modules must not leave the
5
+ // file suite-less (Vitest: "No test suite found"). This file is the regression:
6
+ // it is only this call — if empty discovery failed, vitest would fail the file.
7
+ Conformance.runSpec({
8
+ state: { create: () => ({}) },
9
+ transitions: {},
10
+ });
11
+ //# sourceMappingURL=run-spec.empty.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-spec.empty.test.js","sourceRoot":"","sources":["../../src/conformance/run-spec.empty.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,+EAA+E;AAC/E,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,WAAW,CAAC,OAAO,CAAC;IAClB,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE;IAC7B,WAAW,EAAE,EAAE;CAChB,CAAC,CAAC"}
@@ -0,0 +1,83 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { describe, it } from "vitest";
3
+ import { assert } from "../match/assert.js";
4
+ import { adaptArgs } from "./entity-ref.js";
5
+ import { recordArgServices, expectEffects } from "./record-effects.js";
6
+ const isDerivationCase = (c) => typeof c === "object" && c !== null && "value" in c;
7
+ // The single pure-spec test for every transform AND derivation in a `data/state/`
8
+ // folder. It auto-discovers each file that exports `cases`, requires that file to
9
+ // export exactly its function plus `cases`, and dispatches on case shape: a `value`
10
+ // case checks a derivation `(state) => value`; otherwise a transition `(state,
11
+ // args) => state`, whose declared `effects` on injected services are also asserted.
12
+ // A service-injected transition is async, so results are awaited uniformly.
13
+ //
14
+ // Features with empty `State` and no pure transforms still keep a one-line
15
+ // `spec.test.ts` that calls `runSpec`. When discovery finds zero case modules (or
16
+ // only empty `cases: []` arrays), Vitest would otherwise fail the file with
17
+ // "No test suite found" — so we register a single no-op so empty discovery is a
18
+ // valid outcome, not an error.
19
+ export const runSpec = (config) => {
20
+ const suites = [];
21
+ for (const [path, module] of Object.entries(config.transitions)) {
22
+ const exportNames = Object.keys(module);
23
+ if (!exportNames.includes("cases"))
24
+ continue;
25
+ const functionNames = exportNames.filter((key) => typeof module[key] === "function");
26
+ const fnName = functionNames.length === 1 ? functionNames[0] : undefined;
27
+ const label = config.label
28
+ ? config.label(path, fnName)
29
+ : `State.${fnName ?? path}`;
30
+ if (exportNames.length !== 2 || functionNames.length !== 1) {
31
+ suites.push({ kind: "invalid", path, label, exportNames });
32
+ continue;
33
+ }
34
+ const cases = module["cases"];
35
+ // Empty `cases: []` is not a suite (and would open a Vitest describe with no
36
+ // tests, which fails even when this file has other suites).
37
+ if (cases.length === 0)
38
+ continue;
39
+ suites.push({
40
+ kind: "cases",
41
+ label,
42
+ fn: module[functionNames[0]],
43
+ cases,
44
+ });
45
+ }
46
+ if (suites.length === 0) {
47
+ it("has no pure transitions to specify", () => {
48
+ // intentional no-op — empty discovery is a valid feature shape
49
+ });
50
+ return;
51
+ }
52
+ for (const suite of suites) {
53
+ describe(suite.label, () => {
54
+ if (suite.kind === "invalid") {
55
+ it("exports exactly its function and `cases`", () => {
56
+ throw new Error(`${suite.path} exports [${suite.exportNames.join(", ")}] — expected one function + cases`);
57
+ });
58
+ return;
59
+ }
60
+ for (const testCase of suite.cases) {
61
+ if (isDerivationCase(testCase)) {
62
+ it(testCase.name, () => assert(suite.fn(testCase.input), testCase.value, config.match));
63
+ continue;
64
+ }
65
+ const tc = testCase;
66
+ it(tc.name, async () => {
67
+ // Unwrap `entity(specId)` markers to their data-id for the pure spec, then
68
+ // wrap injected services so their calls are recorded.
69
+ const { args, calls } = recordArgServices(adaptArgs(tc.args));
70
+ // Case `before` is a delta over the feature default; `after` a writes patch.
71
+ const before = {
72
+ ...(config.state?.create() ?? {}),
73
+ ...tc.before,
74
+ };
75
+ const result = (await suite.fn(before, args));
76
+ assert({ ...before, ...result }, { ...before, ...tc.after }, config.match);
77
+ expectEffects(calls, tc.effects);
78
+ });
79
+ }
80
+ });
81
+ }
82
+ };
83
+ //# sourceMappingURL=run-spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-spec.js","sourceRoot":"","sources":["../../src/conformance/run-spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { Conformance } from "../index.js";
3
+ // Non-empty discovery: one pure transition with co-located cases still pairs and
4
+ // asserts as before (empty-discovery path must not break populated specs).
5
+ const increment = (state, { by }) => ({ n: state.n + by });
6
+ Conformance.runSpec({
7
+ state: { create: () => ({ n: 0 }) },
8
+ transitions: {
9
+ "./increment.ts": {
10
+ increment,
11
+ cases: [
12
+ {
13
+ name: "adds the delta over State.create()",
14
+ before: {},
15
+ args: { by: 2 },
16
+ after: { n: 2 },
17
+ },
18
+ {
19
+ name: "honors a non-default before delta",
20
+ before: { n: 10 },
21
+ args: { by: 1 },
22
+ after: { n: 11 },
23
+ },
24
+ ],
25
+ },
26
+ },
27
+ });
28
+ //# sourceMappingURL=run-spec.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-spec.test.js","sourceRoot":"","sources":["../../src/conformance/run-spec.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,iFAAiF;AACjF,2EAA2E;AAC3E,MAAM,SAAS,GAAG,CAChB,KAAoB,EACpB,EAAE,EAAE,EAAkB,EACP,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAE1C,WAAW,CAAC,OAAO,CAAC;IAClB,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;IACnC,WAAW,EAAE;QACX,gBAAgB,EAAE;YAChB,SAAS;YACT,KAAK,EAAE;gBACL;oBACE,IAAI,EAAE,oCAAoC;oBAC1C,MAAM,EAAE,EAAE;oBACV,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;oBACf,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE;iBAChB;gBACD;oBACE,IAAI,EAAE,mCAAmC;oBACzC,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;oBACjB,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;oBACf,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;iBACjB;aACF;SACF;KACF;CACF,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Entity } from "@adobe/data/ecs";
2
+ import type { MatchOptions } from "../match/match.js";
3
+ export interface TransactionRunConfig<Store, State> {
4
+ readonly createStore: () => Store;
5
+ readonly fromState: (store: Store, before: State) => ReadonlyMap<unknown, Entity> | void;
6
+ readonly toState: (store: Store) => State;
7
+ readonly transitions: Record<string, Record<string, unknown>>;
8
+ readonly transactions: Record<string, unknown>;
9
+ readonly initial?: State;
10
+ readonly seedContext?: (store: Store, before: State, args: unknown) => void;
11
+ readonly match?: MatchOptions;
12
+ }
13
+ export declare function runTransactions<Store, State>(config: TransactionRunConfig<Store, State>): void;
@@ -0,0 +1,31 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { describe, it } from "vitest";
3
+ import { assert } from "../match/assert.js";
4
+ import { adaptArgs } from "./entity-ref.js";
5
+ import { discoverTransitions, discoverOps } from "./discover.js";
6
+ import { resolver } from "./resolve.js";
7
+ // The single conformance test for every ecs transaction, proving
8
+ // `toState(apply(fromState(before), args)) ≡ after` for each shared case.
9
+ export function runTransactions(config) {
10
+ const transitions = discoverTransitions(config.transitions);
11
+ for (const [name, transaction] of discoverOps(config.transactions)) {
12
+ const paired = transitions.get(name);
13
+ if (!paired)
14
+ continue; // infrastructure / system-dispatched — no transition to conform to
15
+ describe(`${name} transaction conforms`, () => {
16
+ for (const testCase of paired.cases) {
17
+ it(testCase.name, () => {
18
+ // Case `before` is a delta over the feature default.
19
+ const before = { ...(config.initial ?? {}), ...testCase.before };
20
+ const store = config.createStore();
21
+ const resolve = resolver(config.fromState(store, before));
22
+ config.seedContext?.(store, before, testCase.args);
23
+ transaction(store, adaptArgs(testCase.args, resolve));
24
+ // `after` is a writes patch — compare `toState` against it merged over `before`.
25
+ assert(config.toState(store), { ...before, ...testCase.after }, config.match);
26
+ });
27
+ }
28
+ });
29
+ }
30
+ }
31
+ //# sourceMappingURL=run-transactions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-transactions.js","sourceRoot":"","sources":["../../src/conformance/run-transactions.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AA4BxC,iEAAiE;AACjE,0EAA0E;AAC1E,MAAM,UAAU,eAAe,CAAe,MAA0C;IACtF,MAAM,WAAW,GAAG,mBAAmB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC5D,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,WAAW,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;QACnE,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM;YAAE,SAAS,CAAC,mEAAmE;QAC1F,QAAQ,CAAC,GAAG,IAAI,uBAAuB,EAAE,GAAG,EAAE;YAC5C,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACpC,EAAE,CAAC,QAAQ,CAAC,IAAc,EAAE,GAAG,EAAE;oBAC/B,qDAAqD;oBACrD,MAAM,MAAM,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,GAAI,QAAQ,CAAC,MAAiB,EAAW,CAAC;oBACtF,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;oBACnC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;oBAC1D,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;oBAClD,WAA+C,CAAC,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;oBAC3F,iFAAiF;oBACjF,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,GAAI,MAAiB,EAAE,GAAI,QAAQ,CAAC,KAAgB,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;gBACxG,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ type MethodKeys<T> = {
2
+ [K in keyof T]-?: T[K] extends (...a: never[]) => unknown ? K : never;
3
+ }[keyof T];
4
+ type IsService<T> = T extends readonly unknown[] ? false : T extends (...a: never[]) => unknown ? false : T extends object ? [MethodKeys<T>] extends [never] ? false : true : false;
5
+ export type ServiceCall<S> = {
6
+ [M in keyof S]-?: S[M] extends (...a: infer A) => unknown ? readonly [M, ...A] : never;
7
+ }[keyof S];
8
+ export type Effects<Args> = {
9
+ readonly [K in keyof Args as IsService<Args[K]> extends true ? K : never]?: readonly ServiceCall<Args[K]>[] | ReadonlySet<ServiceCall<Args[K]>>;
10
+ };
11
+ type ArgsOf<F extends (...args: never[]) => unknown> = Parameters<F> extends [unknown, infer Args, ...unknown[]] ? Args : void;
12
+ export type Case<State, Args> = {
13
+ readonly name: string;
14
+ readonly before: Partial<State>;
15
+ readonly after: Partial<State>;
16
+ readonly effects?: Effects<Args>;
17
+ } & ([Args] extends [void] ? {
18
+ readonly args?: undefined;
19
+ } : {
20
+ readonly args: Args;
21
+ });
22
+ export type Cases<State, F extends (...args: never[]) => unknown> = readonly Case<State, ArgsOf<F>>[];
23
+ export type DerivationCase<Input, Value> = {
24
+ readonly name: string;
25
+ readonly input: Input;
26
+ readonly value: Value;
27
+ };
28
+ export type DerivationCases<F extends (...args: never[]) => unknown> = readonly DerivationCase<Parameters<F>[0], ReturnType<F>>[];
29
+ export {};
@@ -0,0 +1,3 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/conformance/types.ts"],"names":[],"mappings":"AAAA,uDAAuD"}
@@ -0,0 +1,2 @@
1
+ export * as Match from "./match/public.js";
2
+ export * as Conformance from "./conformance/public.js";
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ // Shared test-only utilities for the spec↔ecs conformance pattern. Two namespaces:
3
+ // Match — tolerant, matcher-aware value comparison (framework-agnostic).
4
+ // Conformance — the case types, effect recording, id resolution, and the
5
+ // spec/transaction/action/computed runner drivers.
6
+ // Import only from `*.test.ts`; `sideEffects: false` keeps it out of app builds.
7
+ export * as Match from "./match/public.js";
8
+ export * as Conformance from "./conformance/public.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,mFAAmF;AACnF,iFAAiF;AACjF,2EAA2E;AAC3E,mEAAmE;AACnE,iFAAiF;AACjF,OAAO,KAAK,KAAK,MAAM,mBAAmB,CAAC;AAC3C,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { type MatchOptions } from "./match.js";
2
+ export declare const assert: (actual: unknown, expected: unknown, options?: MatchOptions) => void;
@@ -0,0 +1,19 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { matches } from "./match.js";
3
+ const show = (value) => {
4
+ try {
5
+ return JSON.stringify(value) ?? String(value);
6
+ }
7
+ catch {
8
+ return String(value);
9
+ }
10
+ };
11
+ // Throwing assertion built on `matches` — a mismatch throws an `Error` the test
12
+ // runner reports. Framework-agnostic (no `expect` import), so it works under any
13
+ // runner; the message shows both sides.
14
+ export const assert = (actual, expected, options) => {
15
+ if (!matches(actual, expected, options)) {
16
+ throw new Error(`match failed:\n actual: ${show(actual)}\n expected: ${show(expected)}`);
17
+ }
18
+ };
19
+ //# sourceMappingURL=assert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert.js","sourceRoot":"","sources":["../../src/match/assert.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,OAAO,EAAqB,MAAM,YAAY,CAAC;AAExD,MAAM,IAAI,GAAG,CAAC,KAAc,EAAU,EAAE;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC,CAAC;AAEF,gFAAgF;AAChF,iFAAiF;AACjF,wCAAwC;AACxC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,MAAe,EAAE,QAAiB,EAAE,OAAsB,EAAQ,EAAE;IACzF,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,CAAC,MAAM,CAAC,iBAAiB,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC/F,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface MatchOptions {
2
+ readonly unordered?: ReadonlySet<string>;
3
+ readonly tolerance?: number;
4
+ }
5
+ declare const REF: unique symbol;
6
+ export declare const ref: (label: string) => {
7
+ readonly [REF]: string;
8
+ };
9
+ export declare const matches: (actual: unknown, expected: unknown, options?: MatchOptions) => boolean;
10
+ export {};
@@ -0,0 +1,82 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ // A `ref(label)` on the EXPECTED side asserts id CORRESPONDENCE without pinning
3
+ // the value: the first occurrence of a label binds to whatever actual value sits
4
+ // there; later occurrences of the same label must equal that binding, and two
5
+ // labels can never bind the same actual (a bijection). This checks that ecs ids
6
+ // line up structurally — e.g. a `selectedId` points at the entity a case means —
7
+ // even though the ecs assigns ids from its own space. For an id a case does not
8
+ // care about, use `anyNumber` instead.
9
+ const REF = Symbol.for("@adobe/data-testing:ref");
10
+ export const ref = (label) => ({ [REF]: label });
11
+ const isRef = (value) => typeof value === "object" && value !== null && REF in value;
12
+ // An asymmetric matcher (this module's `anyNumber`/`anyString`, or vitest's
13
+ // `expect.any(...)`): honored on the EXPECTED side so a case asserts a shape it
14
+ // does not pin. Recognised structurally, so no test framework is imported.
15
+ const isMatcher = (value) => typeof value === "object" &&
16
+ value !== null &&
17
+ typeof value.asymmetricMatch === "function";
18
+ const quantize = (n, tolerance) => {
19
+ const factor = 1 / tolerance;
20
+ return Math.round(Math.fround(n) * factor) / factor + 0; // `+ 0` normalises `-0` to `0`
21
+ };
22
+ // Multiset (order-independent) match: greedy pairing, sufficient for concrete
23
+ // values. Elements compare with the ordered, matcher-aware path; `ref` bindings
24
+ // do not cross element boundaries here (ids in bags are `anyNumber`, not refs).
25
+ const matchesUnordered = (actual, expected, options) => {
26
+ if (actual.length !== expected.length)
27
+ return false;
28
+ const used = new Array(actual.length).fill(false);
29
+ return expected.every((exp) => {
30
+ const index = actual.findIndex((act, i) => !used[i] && matchesWith(act, exp, options, new Map()));
31
+ if (index < 0)
32
+ return false;
33
+ used[index] = true;
34
+ return true;
35
+ });
36
+ };
37
+ const matchesWith = (actual, expected, options, bindings) => {
38
+ if (isRef(expected)) {
39
+ const label = expected[REF];
40
+ if (bindings.has(label))
41
+ return Object.is(bindings.get(label), actual);
42
+ for (const bound of bindings.values())
43
+ if (Object.is(bound, actual))
44
+ return false; // injective
45
+ bindings.set(label, actual);
46
+ return true;
47
+ }
48
+ if (isMatcher(expected))
49
+ return expected.asymmetricMatch(actual);
50
+ if (typeof expected === "number" && typeof actual === "number") {
51
+ const tolerance = options.tolerance ?? 0.01;
52
+ return quantize(actual, tolerance) === quantize(expected, tolerance);
53
+ }
54
+ if (Array.isArray(expected)) {
55
+ if (!Array.isArray(actual) || actual.length !== expected.length)
56
+ return false;
57
+ return expected.every((exp, index) => matchesWith(actual[index], exp, options, bindings));
58
+ }
59
+ if (expected !== null && typeof expected === "object") {
60
+ if (actual === null || typeof actual !== "object" || Array.isArray(actual))
61
+ return false;
62
+ const expectedKeys = Object.keys(expected);
63
+ const actualKeys = Object.keys(actual);
64
+ if (expectedKeys.length !== actualKeys.length)
65
+ return false;
66
+ return expectedKeys.every((key) => {
67
+ const exp = expected[key];
68
+ const act = actual[key];
69
+ if (options.unordered?.has(key) && Array.isArray(exp) && Array.isArray(act)) {
70
+ return matchesUnordered(act, exp, options);
71
+ }
72
+ return matchesWith(act, exp, options, bindings);
73
+ });
74
+ }
75
+ return Object.is(actual, expected);
76
+ };
77
+ // Tolerant structural comparison: honors asymmetric matchers and `ref`
78
+ // correspondence on the expected side, absorbs float noise, and compares arrays
79
+ // in order except where `options.unordered` names a multiset collection. Pure
80
+ // and framework-agnostic — `assert` wraps it for a throwing test assertion.
81
+ export const matches = (actual, expected, options = {}) => matchesWith(actual, expected, options, new Map());
82
+ //# sourceMappingURL=match.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/match/match.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAcvD,gFAAgF;AAChF,iFAAiF;AACjF,8EAA8E;AAC9E,gFAAgF;AAChF,iFAAiF;AACjF,gFAAgF;AAChF,uCAAuC;AACvC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;AAClD,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,KAAa,EAA8B,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACrF,MAAM,KAAK,GAAG,CAAC,KAAc,EAAuC,EAAE,CACpE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,CAAC;AAE9D,4EAA4E;AAC5E,gFAAgF;AAChF,2EAA2E;AAC3E,MAAM,SAAS,GAAG,CAAC,KAAc,EAA0D,EAAE,CAC3F,OAAO,KAAK,KAAK,QAAQ;IACzB,KAAK,KAAK,IAAI;IACd,OAAQ,KAAuC,CAAC,eAAe,KAAK,UAAU,CAAC;AAEjF,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,SAAiB,EAAU,EAAE;IACxD,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;IAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,+BAA+B;AAC1F,CAAC,CAAC;AAEF,8EAA8E;AAC9E,gFAAgF;AAChF,gFAAgF;AAChF,MAAM,gBAAgB,GAAG,CACvB,MAA0B,EAC1B,QAA4B,EAC5B,OAAqB,EACZ,EAAE;IACX,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACpD,MAAM,IAAI,GAAG,IAAI,KAAK,CAAU,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3D,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;QAClG,IAAI,KAAK,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAClB,MAAe,EACf,QAAiB,EACjB,OAAqB,EACrB,QAA8B,EACrB,EAAE;IACX,IAAI,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;QACvE,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM,EAAE;YAAE,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;gBAAE,OAAO,KAAK,CAAC,CAAC,YAAY;QAC/F,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,SAAS,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IACjE,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;QAC5C,OAAO,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,KAAK,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC9E,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC5F,CAAC;IACD,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACtD,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;QACzF,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,QAAkB,CAAC,CAAC;QACrD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,MAAgB,CAAC,CAAC;QACjD,IAAI,YAAY,CAAC,MAAM,KAAK,UAAU,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC5D,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YAChC,MAAM,GAAG,GAAI,QAAoC,CAAC,GAAG,CAAC,CAAC;YACvD,MAAM,GAAG,GAAI,MAAkC,CAAC,GAAG,CAAC,CAAC;YACrD,IAAI,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC5E,OAAO,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;YAC7C,CAAC;YACD,OAAO,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACrC,CAAC,CAAC;AAEF,uEAAuE;AACvE,gFAAgF;AAChF,8EAA8E;AAC9E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAAe,EAAE,QAAiB,EAAE,UAAwB,EAAE,EAAW,EAAE,CACjG,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { describe, it, expect } from "vitest";
3
+ import { matches, ref, anyNumber, anyString } from "./public.js";
4
+ describe("Match.matches", () => {
5
+ it("compares plain structures deeply", () => {
6
+ expect(matches({ a: 1, b: [2, 3] }, { a: 1, b: [2, 3] })).toBe(true);
7
+ expect(matches({ a: 1 }, { a: 2 })).toBe(false);
8
+ expect(matches({ a: 1, b: 2 }, { a: 1 })).toBe(false); // extra key on actual
9
+ });
10
+ it("absorbs F32/f64 and trig float noise onto the tolerance grid", () => {
11
+ expect(matches(Math.fround(0.1), 0.1)).toBe(true);
12
+ expect(matches(3e-15, 0)).toBe(true);
13
+ expect(matches(0.5, 0.5001)).toBe(true); // within 0.01
14
+ expect(matches(0.5, 0.52)).toBe(false); // outside 0.01
15
+ expect(matches(-0, 0)).toBe(true);
16
+ });
17
+ it("honors anyNumber / anyString and vitest-style asymmetric matchers", () => {
18
+ expect(matches({ id: 42, name: "x" }, { id: anyNumber, name: anyString })).toBe(true);
19
+ expect(matches({ id: "no" }, { id: anyNumber })).toBe(false);
20
+ expect(matches(7, expect.any(Number))).toBe(true);
21
+ });
22
+ it("compares arrays in order by default, as multisets when named", () => {
23
+ expect(matches([1, 2, 3], [1, 2, 3])).toBe(true);
24
+ expect(matches([1, 2, 3], [3, 2, 1])).toBe(false);
25
+ const opts = { unordered: new Set(["bag"]) };
26
+ expect(matches({ bag: [1, 2, 3] }, { bag: [3, 1, 2] }, opts)).toBe(true);
27
+ expect(matches({ bag: [1, 2] }, { bag: [1, 2, 3] }, opts)).toBe(false);
28
+ });
29
+ describe("ref — id correspondence up to renaming", () => {
30
+ it("binds a label to the first actual and requires later ones to match", () => {
31
+ // Same ecs id in two places must be the same actual value.
32
+ expect(matches({ sel: 100, items: [{ id: 100 }] }, { sel: ref("a"), items: [{ id: ref("a") }] })).toBe(true);
33
+ // A dangling reference (sel points at an id no item has) fails.
34
+ expect(matches({ sel: 999, items: [{ id: 100 }] }, { sel: ref("a"), items: [{ id: ref("a") }] })).toBe(false);
35
+ });
36
+ it("is injective — two labels cannot bind the same actual", () => {
37
+ expect(matches([5, 6], [ref("a"), ref("b")])).toBe(true);
38
+ expect(matches([5, 5], [ref("a"), ref("b")])).toBe(false);
39
+ });
40
+ });
41
+ });
42
+ //# sourceMappingURL=match.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.test.js","sourceRoot":"","sources":["../../src/match/match.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const anyNumber: number;
2
+ export declare const anyString: string;
@@ -0,0 +1,14 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ // Asymmetric matchers for a value a case does not pin — chiefly an entity `id`
3
+ // the ecs assigns from its own id-space, so the spec and the ecs projection
4
+ // satisfy the same case without agreeing on the value. Plain `{ asymmetricMatch }`
5
+ // objects (no test-framework dependency), recognised by `matches` and
6
+ // interchangeable with vitest's `expect.any(Number)` / `expect.any(String)`.
7
+ const numberMatcher = { asymmetricMatch: (actual) => typeof actual === "number" };
8
+ const stringMatcher = { asymmetricMatch: (actual) => typeof actual === "string" };
9
+ // Typed as the value each stands in for (as vitest types `expect.any`), so it
10
+ // slots into a pinned `number` / `string` field of a case's expected value;
11
+ // `matches` recognises the object at runtime by its `asymmetricMatch` method.
12
+ export const anyNumber = numberMatcher;
13
+ export const anyString = stringMatcher;
14
+ //# sourceMappingURL=matchers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.js","sourceRoot":"","sources":["../../src/match/matchers.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,+EAA+E;AAC/E,4EAA4E;AAC5E,mFAAmF;AACnF,sEAAsE;AACtE,6EAA6E;AAC7E,MAAM,aAAa,GAAG,EAAE,eAAe,EAAE,CAAC,MAAe,EAAW,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;AACpG,MAAM,aAAa,GAAG,EAAE,eAAe,EAAE,CAAC,MAAe,EAAW,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;AAEpG,8EAA8E;AAC9E,4EAA4E;AAC5E,8EAA8E;AAC9E,MAAM,CAAC,MAAM,SAAS,GAAG,aAAkC,CAAC;AAC5D,MAAM,CAAC,MAAM,SAAS,GAAG,aAAkC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { matches, ref, type MatchOptions } from "./match.js";
2
+ export { assert } from "./assert.js";
3
+ export { anyNumber, anyString } from "./matchers.js";
@@ -0,0 +1,5 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ export { matches, ref } from "./match.js";
3
+ export { assert } from "./assert.js";
4
+ export { anyNumber, anyString } from "./matchers.js";
5
+ //# sourceMappingURL=public.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.js","sourceRoot":"","sources":["../../src/match/public.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,OAAO,EAAE,GAAG,EAAqB,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@adobe/data-testing",
3
+ "version": "0.9.96",
4
+ "description": "Conformance-testing utilities (Match + Conformance runners) for @adobe/data ECS features",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "private": false,
8
+ "publishConfig": {
9
+ "registry": "https://registry.npmjs.org/",
10
+ "access": "public"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "dependencies": {
18
+ "@adobe/data": "0.9.96"
19
+ },
20
+ "peerDependencies": {
21
+ "vitest": "^1.6.0"
22
+ },
23
+ "peerDependenciesMeta": {
24
+ "vitest": {
25
+ "optional": true
26
+ }
27
+ },
28
+ "devDependencies": {
29
+ "typescript": "^5.8.3",
30
+ "vitest": "^1.6.0"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "import": "./dist/index.js",
35
+ "types": "./dist/index.d.ts"
36
+ }
37
+ },
38
+ "scripts": {
39
+ "build": "cp ../../LICENSE . && tsc -b",
40
+ "dev": "tsc -b -w --preserveWatchOutput",
41
+ "clean": "rm -rf dist node_modules",
42
+ "link": "pnpm build && pnpm link --global",
43
+ "test": "vitest --run --passWithNoTests",
44
+ "typecheck": "tsc -b"
45
+ }
46
+ }