@adobe/data-testing 0.10.19 → 0.10.20
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.
- package/dist/conformance/adapt-spec.d.ts +8 -0
- package/dist/conformance/adapt-spec.js +45 -0
- package/dist/conformance/adapt-spec.js.map +1 -0
- package/dist/conformance/build-double.d.ts +2 -0
- package/dist/conformance/build-double.js +32 -0
- package/dist/conformance/build-double.js.map +1 -0
- package/dist/conformance/feature-spec.d.ts +42 -0
- package/dist/conformance/feature-spec.js +53 -0
- package/dist/conformance/feature-spec.js.map +1 -0
- package/dist/conformance/feature-spec.type-test.d.ts +111 -0
- package/dist/conformance/feature-spec.type-test.js +95 -0
- package/dist/conformance/feature-spec.type-test.js.map +1 -0
- package/dist/conformance/feature-types.d.ts +95 -0
- package/dist/conformance/feature-types.js +2 -0
- package/dist/conformance/feature-types.js.map +1 -0
- package/dist/conformance/public.d.ts +5 -4
- package/dist/conformance/public.js +10 -7
- package/dist/conformance/public.js.map +1 -1
- package/dist/conformance/run-spec.empty-cases.test.js +2 -2
- package/dist/conformance/run-spec.empty-cases.test.js.map +1 -1
- package/dist/conformance/run-spec.empty.test.js +2 -2
- package/dist/conformance/run-spec.empty.test.js.map +1 -1
- package/dist/conformance/run-spec.test.js +2 -2
- package/dist/conformance/run-spec.test.js.map +1 -1
- package/dist/conformance/types.d.ts +0 -19
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Schema } from "@adobe/data/schema";
|
|
2
|
+
type AnyFn = (...args: never[]) => unknown;
|
|
3
|
+
type LooseCaseModule = {
|
|
4
|
+
readonly args?: Schema;
|
|
5
|
+
readonly cases: readonly Record<string, unknown>[];
|
|
6
|
+
};
|
|
7
|
+
export declare const adaptCases: (fns: Readonly<Record<string, AnyFn>>, cases: Readonly<Record<string, LooseCaseModule>>, services: Readonly<Record<string, () => object>> | undefined, injectServices: boolean) => Record<string, Record<string, unknown>>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { buildDouble } from "./build-double.js";
|
|
2
|
+
// Synthesize this case's recording doubles from the feature's shape-only `services`
|
|
3
|
+
// templates and the case's `responses`, then merge them into `args` where the
|
|
4
|
+
// transform expects each injected service. ALL registered services are injected (a
|
|
5
|
+
// superset): a service a transform calls but the case neither schedules nor asserts
|
|
6
|
+
// must still be present, and an unused one is harmless (its recorded calls aren't
|
|
7
|
+
// asserted). The test-only `responses` channel is dropped — it is not a fn arg.
|
|
8
|
+
const injectDoubles = (testCase, services) => {
|
|
9
|
+
const responses = testCase["responses"];
|
|
10
|
+
const args = testCase["args"];
|
|
11
|
+
const built = {};
|
|
12
|
+
for (const [name, template] of Object.entries(services)) {
|
|
13
|
+
built[name] = buildDouble(template, responses?.[name]);
|
|
14
|
+
}
|
|
15
|
+
const { responses: _responses, args: _args, ...rest } = testCase;
|
|
16
|
+
return { ...rest, args: { ...built, ...(args ?? {}) } };
|
|
17
|
+
};
|
|
18
|
+
// Adapt a manifest's `{ fns, cases }` into the `transitions` map shape the
|
|
19
|
+
// discovery-based runners (`runSpec` / `runFeature`) already consume: one entry per
|
|
20
|
+
// fn, `{ [fnName]: fn, cases: <envelope> }`. Derivation modules pass through as
|
|
21
|
+
// `{ cases }`; transition modules keep their optional `args` schema, and — when
|
|
22
|
+
// `injectServices` — gain per-case recording doubles in `args`. This is the entire
|
|
23
|
+
// bridge from inert, data-only cases to the unchanged runners: the pure spec and the
|
|
24
|
+
// ecs action side adapt WITH services (each side builds its own fresh doubles, so
|
|
25
|
+
// their FIFO schedules never cross); the ecs transaction side adapts WITHOUT (a
|
|
26
|
+
// transaction is a pure store mutation and never receives services).
|
|
27
|
+
export const adaptCases = (fns, cases, services, injectServices) => {
|
|
28
|
+
const out = {};
|
|
29
|
+
for (const [name, fn] of Object.entries(fns)) {
|
|
30
|
+
const module = cases[name];
|
|
31
|
+
const list = module.cases;
|
|
32
|
+
const first = list[0];
|
|
33
|
+
if (first !== undefined && "value" in first) {
|
|
34
|
+
out[`./${name}`] = { [name]: fn, cases: { cases: list } };
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const adapted = injectServices && services ? list.map((testCase) => injectDoubles(testCase, services)) : list;
|
|
38
|
+
out[`./${name}`] = {
|
|
39
|
+
[name]: fn,
|
|
40
|
+
cases: module.args ? { args: module.args, cases: adapted } : { cases: adapted },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=adapt-spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapt-spec.js","sourceRoot":"","sources":["../../src/conformance/adapt-spec.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAiB,MAAM,mBAAmB,CAAC;AAQ/D,oFAAoF;AACpF,8EAA8E;AAC9E,mFAAmF;AACnF,oFAAoF;AACpF,kFAAkF;AAClF,gFAAgF;AAChF,MAAM,aAAa,GAAG,CACpB,QAAiC,EACjC,QAAgD,EACvB,EAAE;IAC3B,MAAM,SAAS,GAAG,QAAQ,CAAC,WAAW,CAAmD,CAAC;IAC1F,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAuB,CAAC;IACpD,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxD,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC;IACjE,OAAO,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;AAC1D,CAAC,CAAC;AAEF,2EAA2E;AAC3E,oFAAoF;AACpF,gFAAgF;AAChF,gFAAgF;AAChF,mFAAmF;AACnF,qFAAqF;AACrF,kFAAkF;AAClF,gFAAgF;AAChF,qEAAqE;AACrE,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,GAAoC,EACpC,KAAgD,EAChD,QAA4D,EAC5D,cAAuB,EACkB,EAAE;IAC3C,MAAM,GAAG,GAA4C,EAAE,CAAC;IACxD,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAwC,CAAC;QAC7D,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;YAC5C,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;YAC1D,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,cAAc,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9G,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,GAAG;YACjB,CAAC,IAAI,CAAC,EAAE,EAAE;YACV,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE;SAChF,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
// Build a recording double for one service from a SHAPE-ONLY template and a case's
|
|
3
|
+
// response schedule. The template — the feature's registered fake — is called once
|
|
4
|
+
// purely to enumerate the service's members (types are erased at runtime, and Proxy
|
|
5
|
+
// is banned on these paths, so the method names must come from a real value); its
|
|
6
|
+
// own behavior is never used. Every value-returning call drains that method's FIFO;
|
|
7
|
+
// a `void` method (or any method with no schedule) returns `undefined`. Draining an
|
|
8
|
+
// EXHAUSTED schedule throws, so a transform that calls a method more times than the
|
|
9
|
+
// case scheduled is a hard failure — the determinism a conformance oracle needs
|
|
10
|
+
// (the old fakes cycled, silently masking an over-call).
|
|
11
|
+
//
|
|
12
|
+
// The returned double still gets wrapped by `recordCalls` (call recording) on its
|
|
13
|
+
// way into a case's args, exactly like the live fakes it replaces — so effect
|
|
14
|
+
// assertion is unchanged; only the RETURNS now come from the case, not the fake.
|
|
15
|
+
export const buildDouble = (template, responses) => {
|
|
16
|
+
const shape = template();
|
|
17
|
+
const queues = new Map(Object.entries(responses ?? {}).map(([method, schedule]) => [method, [...schedule]]));
|
|
18
|
+
return Object.fromEntries(Object.entries(shape).map(([key, value]) => typeof value !== "function"
|
|
19
|
+
? [key, value] // serviceName and other non-method members pass through
|
|
20
|
+
: [
|
|
21
|
+
key,
|
|
22
|
+
(..._args) => {
|
|
23
|
+
const queue = queues.get(key);
|
|
24
|
+
if (!queue)
|
|
25
|
+
return undefined; // void method, or an unscheduled call
|
|
26
|
+
if (queue.length === 0)
|
|
27
|
+
throw new Error(`response schedule exhausted for "${key}"`);
|
|
28
|
+
return queue.shift();
|
|
29
|
+
},
|
|
30
|
+
]));
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=build-double.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-double.js","sourceRoot":"","sources":["../../src/conformance/build-double.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAMvD,mFAAmF;AACnF,mFAAmF;AACnF,oFAAoF;AACpF,kFAAkF;AAClF,oFAAoF;AACpF,oFAAoF;AACpF,oFAAoF;AACpF,gFAAgF;AAChF,yDAAyD;AACzD,EAAE;AACF,kFAAkF;AAClF,8EAA8E;AAC9E,iFAAiF;AACjF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAsB,EAAE,SAA+B,EAAU,EAAE;IAC7F,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,MAAM,GAAG,IAAI,GAAG,CACpB,MAAM,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CACrF,CAAC;IACF,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CACzC,OAAO,KAAK,KAAK,UAAU;QACzB,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,wDAAwD;QACvE,CAAC,CAAC;YACE,GAAG;YACH,CAAC,GAAG,KAAgB,EAAW,EAAE;gBAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC9B,IAAI,CAAC,KAAK;oBAAE,OAAO,SAAS,CAAC,CAAC,sCAAsC;gBACpE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;oBAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,GAAG,GAAG,CAAC,CAAC;gBACpF,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC;YACvB,CAAC;SACF,CACN,CACF,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Database } from "@adobe/data/ecs";
|
|
2
|
+
import type { MatchOptions } from "../match/match.js";
|
|
3
|
+
import type { CaseModule, HasDerivations, HydrateExact, HydrationKeys, ProjectionShape, ProjectionStore, ProjectionValue, RequiredServices } from "./feature-types.js";
|
|
4
|
+
import type { Projection } from "./run-feature.js";
|
|
5
|
+
import type { SchemaSource } from "./refify.js";
|
|
6
|
+
type AnyFn = (...args: never[]) => unknown;
|
|
7
|
+
export interface FeatureSpec<State extends object, Fns extends Record<string, AnyFn>, StoreT> {
|
|
8
|
+
readonly state: {
|
|
9
|
+
create(): State;
|
|
10
|
+
readonly samples?: readonly State[];
|
|
11
|
+
};
|
|
12
|
+
readonly fns: Fns;
|
|
13
|
+
readonly cases: {
|
|
14
|
+
readonly [K in keyof Fns]: CaseModule<State, Fns[K]>;
|
|
15
|
+
};
|
|
16
|
+
readonly services?: Readonly<Record<string, () => object>>;
|
|
17
|
+
readonly plugin: Database.Plugin;
|
|
18
|
+
readonly computedPlugin?: Database.Plugin;
|
|
19
|
+
readonly projection: Projection<StoreT, State>;
|
|
20
|
+
readonly hydrate?: readonly string[];
|
|
21
|
+
readonly match?: MatchOptions;
|
|
22
|
+
readonly ops?: {
|
|
23
|
+
readonly transactions?: Record<string, unknown>;
|
|
24
|
+
readonly actions?: Record<string, unknown>;
|
|
25
|
+
readonly computeds?: Record<string, unknown>;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export declare const feature: <State extends object, Fns extends Record<string, AnyFn>, P extends ProjectionShape<State>, C extends { readonly [K in keyof Fns]: CaseModule<State, Fns[K]>; }, const H extends readonly Extract<keyof Fns, string>[] = readonly []>(spec: Omit<FeatureSpec<State, Fns, ProjectionStore<P>>, "projection" | "cases" | "hydrate" | "services" | "computedPlugin"> & {
|
|
29
|
+
readonly projection: P;
|
|
30
|
+
readonly cases: C;
|
|
31
|
+
readonly hydrate?: H;
|
|
32
|
+
} & RequiredServices<Fns> & (HasDerivations<C> extends true ? {
|
|
33
|
+
readonly computedPlugin: Database.Plugin;
|
|
34
|
+
} : {
|
|
35
|
+
readonly computedPlugin?: Database.Plugin;
|
|
36
|
+
}) & ([Exclude<keyof C, keyof Fns>] extends [never] ? unknown : {
|
|
37
|
+
readonly __casesError: "a case module names something that is not a conformed fn";
|
|
38
|
+
readonly extra: Exclude<keyof C, keyof Fns>;
|
|
39
|
+
}) & HydrateExact<H, HydrationKeys<Fns, ProjectionValue<P>>>) => FeatureSpec<State, Fns, ProjectionStore<P>>;
|
|
40
|
+
export declare const checkSpec: <State extends object, Fns extends Record<string, AnyFn>, StoreT>(spec: FeatureSpec<State, Fns, StoreT>) => void;
|
|
41
|
+
export declare const checkFeature: <State extends object, Fns extends Record<string, AnyFn>, StoreT extends SchemaSource>(spec: FeatureSpec<State, Fns, StoreT>) => void;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { runSpec } from "./run-spec.js";
|
|
2
|
+
import { runFeature } from "./run-feature.js";
|
|
3
|
+
import { adaptCases } from "./adapt-spec.js";
|
|
4
|
+
// Author a feature's conformance manifest. Purely a typed identity — it exists so the
|
|
5
|
+
// generics infer from the argument and arm the compile-time guards. `P` (projection)
|
|
6
|
+
// and `C` (the concrete case modules) are captured, not widened, so their real types
|
|
7
|
+
// feed the checks:
|
|
8
|
+
// • `cases` coverage — `C extends { [K in keyof Fns]: CaseModule }` requires a module
|
|
9
|
+
// for every fn (missing → error); the `NoExtraCases` factor rejects a module whose
|
|
10
|
+
// key is not a fn (extra → error).
|
|
11
|
+
// • `hydrate` exactness — must be EXACTLY the entity-list derivations (`HydrateExact`).
|
|
12
|
+
// • `services` requiredness — exactly the injected-service keys the fns declare, or
|
|
13
|
+
// absent when none (`RequiredServices`); a missing template is a compile error, not
|
|
14
|
+
// a runtime crash.
|
|
15
|
+
// • `computedPlugin` requiredness — required when any authored case module is a
|
|
16
|
+
// derivation (`HasDerivations<C>`), so a feature with derivations can't silently skip
|
|
17
|
+
// its computed conformance by omitting the plugin.
|
|
18
|
+
export const feature = (spec) =>
|
|
19
|
+
// Runtime invariant the checker can't see: the constrained param IS this FeatureSpec
|
|
20
|
+
// (`P` widens to the stored `Projection`, `C` to the `cases` mapped type, `H` to
|
|
21
|
+
// `readonly string[]`, the fake factories to `() => object`). The several requiredness
|
|
22
|
+
// factors are compile-only phantoms with no runtime shape, and together they defeat
|
|
23
|
+
// TS's structural-overlap check for a direct `as`, so this goes through `unknown`.
|
|
24
|
+
spec;
|
|
25
|
+
// The pure-spec suite for a manifest: adapt the inert cases into the discovery map
|
|
26
|
+
// `runSpec` consumes — WITH freshly-built recording doubles merged into each case's
|
|
27
|
+
// args from the `services` templates + `responses` — and run it unchanged. This
|
|
28
|
+
// test file's doubles are its own, independent of the ecs side's.
|
|
29
|
+
export const checkSpec = (spec) => {
|
|
30
|
+
runSpec({
|
|
31
|
+
state: spec.state,
|
|
32
|
+
transitions: adaptCases(spec.fns, spec.cases, spec.services, true),
|
|
33
|
+
plugin: spec.plugin,
|
|
34
|
+
match: spec.match,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
// The ecs conformance suite for a manifest: the same adapted, doubles-injected map
|
|
38
|
+
// drives `runFeature` unchanged (transactions ignore the injected services; only the
|
|
39
|
+
// actions consume each case's response schedule, exactly once). Fresh doubles again,
|
|
40
|
+
// so the pure and ecs runs never share FIFO state.
|
|
41
|
+
export const checkFeature = (spec) => {
|
|
42
|
+
runFeature({
|
|
43
|
+
state: spec.state,
|
|
44
|
+
transitions: adaptCases(spec.fns, spec.cases, spec.services, true),
|
|
45
|
+
plugin: spec.plugin,
|
|
46
|
+
computedPlugin: spec.computedPlugin,
|
|
47
|
+
projection: spec.projection,
|
|
48
|
+
hydrate: spec.hydrate,
|
|
49
|
+
match: spec.match,
|
|
50
|
+
ops: spec.ops,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=feature-spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-spec.js","sourceRoot":"","sources":["../../src/conformance/feature-spec.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAmC7C,sFAAsF;AACtF,qFAAqF;AACrF,qFAAqF;AACrF,mBAAmB;AACnB,wFAAwF;AACxF,uFAAuF;AACvF,uCAAuC;AACvC,0FAA0F;AAC1F,sFAAsF;AACtF,wFAAwF;AACxF,uBAAuB;AACvB,kFAAkF;AAClF,0FAA0F;AAC1F,uDAAuD;AACvD,MAAM,CAAC,MAAM,OAAO,GAAG,CAOrB,IASyD,EACZ,EAAE;AAC/C,qFAAqF;AACrF,iFAAiF;AACjF,uFAAuF;AACvF,oFAAoF;AACpF,mFAAmF;AACnF,IAA8D,CAAC;AAEjE,mFAAmF;AACnF,oFAAoF;AACpF,gFAAgF;AAChF,kEAAkE;AAClE,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,IAAqC,EAC/B,EAAE;IACR,OAAO,CAAC;QACN,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,WAAW,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;QAClE,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,mFAAmF;AACnF,qFAAqF;AACrF,qFAAqF;AACrF,mDAAmD;AACnD,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,IAAqC,EAC/B,EAAE;IACR,UAAU,CAAmC;QAC3C,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,WAAW,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;QAClE,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC,CAAC;AACL,CAAC,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { CaseModule, SpecCases, SpecDerivations } from "./feature-types.js";
|
|
2
|
+
type Foo = {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
};
|
|
5
|
+
type State = {
|
|
6
|
+
readonly entities: ReadonlyMap<number, Foo>;
|
|
7
|
+
readonly count: number;
|
|
8
|
+
};
|
|
9
|
+
declare const bump: (s: Pick<State, "count">, args: {
|
|
10
|
+
readonly by: number;
|
|
11
|
+
}) => Pick<State, "count">;
|
|
12
|
+
declare const listFoos: (s: Pick<State, "entities">) => readonly Foo[];
|
|
13
|
+
interface Store {
|
|
14
|
+
readonly componentSchemas: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export declare const ok: import("./feature-spec.js").FeatureSpec<State, {
|
|
17
|
+
bump: (s: Pick<State, "count">, args: {
|
|
18
|
+
readonly by: number;
|
|
19
|
+
}) => Pick<State, "count">;
|
|
20
|
+
listFoos: (s: Pick<State, "entities">) => readonly Foo[];
|
|
21
|
+
}, Store>;
|
|
22
|
+
export declare const missingCase: import("./feature-spec.js").FeatureSpec<State, {
|
|
23
|
+
bump: (s: Pick<State, "count">, args: {
|
|
24
|
+
readonly by: number;
|
|
25
|
+
}) => Pick<State, "count">;
|
|
26
|
+
listFoos: (s: Pick<State, "entities">) => readonly Foo[];
|
|
27
|
+
}, Store>;
|
|
28
|
+
export declare const extraCase: import("./feature-spec.js").FeatureSpec<State, {
|
|
29
|
+
bump: (s: Pick<State, "count">, args: {
|
|
30
|
+
readonly by: number;
|
|
31
|
+
}) => Pick<State, "count">;
|
|
32
|
+
listFoos: (s: Pick<State, "entities">) => readonly Foo[];
|
|
33
|
+
}, Store>;
|
|
34
|
+
export declare const missingHydrate: import("./feature-spec.js").FeatureSpec<State, {
|
|
35
|
+
bump: (s: Pick<State, "count">, args: {
|
|
36
|
+
readonly by: number;
|
|
37
|
+
}) => Pick<State, "count">;
|
|
38
|
+
listFoos: (s: Pick<State, "entities">) => readonly Foo[];
|
|
39
|
+
}, Store>;
|
|
40
|
+
export declare const extraHydrate: import("./feature-spec.js").FeatureSpec<State, {
|
|
41
|
+
bump: (s: Pick<State, "count">, args: {
|
|
42
|
+
readonly by: number;
|
|
43
|
+
}) => Pick<State, "count">;
|
|
44
|
+
listFoos: (s: Pick<State, "entities">) => readonly Foo[];
|
|
45
|
+
}, Store>;
|
|
46
|
+
interface Logger {
|
|
47
|
+
log: (msg: string) => void;
|
|
48
|
+
next: () => Promise<number>;
|
|
49
|
+
}
|
|
50
|
+
declare const record: (s: Pick<State, "count">, args: {
|
|
51
|
+
readonly by: number;
|
|
52
|
+
readonly logger: Logger;
|
|
53
|
+
}) => Pick<State, "count">;
|
|
54
|
+
export declare const recOk: SpecCases<State, typeof record>;
|
|
55
|
+
export declare const recBadArgsService: SpecCases<State, typeof record>;
|
|
56
|
+
export declare const recMissingArg: SpecCases<State, typeof record>;
|
|
57
|
+
export declare const recVoidResponse: SpecCases<State, typeof record>;
|
|
58
|
+
export declare const recWrongResponse: SpecCases<State, typeof record>;
|
|
59
|
+
export declare const recThrows: SpecCases<State, typeof record>;
|
|
60
|
+
export declare const recBoth: SpecCases<State, typeof record>;
|
|
61
|
+
declare const reset: (s: Pick<State, "count">) => Pick<State, "count">;
|
|
62
|
+
export declare const resetOk: CaseModule<State, typeof reset>;
|
|
63
|
+
declare const saveAsync: (s: Pick<State, "count">, a: {
|
|
64
|
+
readonly id: number;
|
|
65
|
+
}) => Promise<Pick<State, "count">>;
|
|
66
|
+
export declare const asyncOk: CaseModule<State, typeof saveAsync>;
|
|
67
|
+
export declare const arrOk: CaseModule<State, typeof listFoos>;
|
|
68
|
+
declare const idsOf: (s: State) => ReadonlySet<number>;
|
|
69
|
+
export declare const setOk: CaseModule<State, typeof idsOf>;
|
|
70
|
+
declare const total: (s: State) => number;
|
|
71
|
+
export declare const scalarOk: CaseModule<State, typeof total>;
|
|
72
|
+
export declare const wrongTransition: SpecCases<State, typeof reset>;
|
|
73
|
+
export declare const wrongDerivation: SpecDerivations<typeof total>;
|
|
74
|
+
export declare const mispair: CaseModule<State, typeof bump>;
|
|
75
|
+
declare const count: (s: State) => number;
|
|
76
|
+
export declare const residualGap: CaseModule<State, typeof count>;
|
|
77
|
+
export declare const missingComputedPlugin: import("./feature-spec.js").FeatureSpec<State, {
|
|
78
|
+
bump: (s: Pick<State, "count">, args: {
|
|
79
|
+
readonly by: number;
|
|
80
|
+
}) => Pick<State, "count">;
|
|
81
|
+
listFoos: (s: Pick<State, "entities">) => readonly Foo[];
|
|
82
|
+
}, Store>;
|
|
83
|
+
export declare const noComputedOk: import("./feature-spec.js").FeatureSpec<State, {
|
|
84
|
+
bump: (s: Pick<State, "count">, args: {
|
|
85
|
+
readonly by: number;
|
|
86
|
+
}) => Pick<State, "count">;
|
|
87
|
+
}, Store>;
|
|
88
|
+
export declare const servicesOk: import("./feature-spec.js").FeatureSpec<State, {
|
|
89
|
+
record: (s: Pick<State, "count">, args: {
|
|
90
|
+
readonly by: number;
|
|
91
|
+
readonly logger: Logger;
|
|
92
|
+
}) => Pick<State, "count">;
|
|
93
|
+
}, Store>;
|
|
94
|
+
export declare const missingServices: import("./feature-spec.js").FeatureSpec<State, {
|
|
95
|
+
record: (s: Pick<State, "count">, args: {
|
|
96
|
+
readonly by: number;
|
|
97
|
+
readonly logger: Logger;
|
|
98
|
+
}) => Pick<State, "count">;
|
|
99
|
+
}, Store>;
|
|
100
|
+
export declare const incompleteServices: import("./feature-spec.js").FeatureSpec<State, {
|
|
101
|
+
record: (s: Pick<State, "count">, args: {
|
|
102
|
+
readonly by: number;
|
|
103
|
+
readonly logger: Logger;
|
|
104
|
+
}) => Pick<State, "count">;
|
|
105
|
+
}, Store>;
|
|
106
|
+
export declare const extraServices: import("./feature-spec.js").FeatureSpec<State, {
|
|
107
|
+
bump: (s: Pick<State, "count">, args: {
|
|
108
|
+
readonly by: number;
|
|
109
|
+
}) => Pick<State, "count">;
|
|
110
|
+
}, Store>;
|
|
111
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { feature } from "./feature-spec.js";
|
|
2
|
+
// A transition (2 params) and an entity-list derivation (returns `readonly Foo[]`, so
|
|
3
|
+
// its ecs computed emits ids → it MUST be hydrated).
|
|
4
|
+
const bump = (s, args) => ({ count: s.count + args.by });
|
|
5
|
+
const listFoos = (s) => [...s.entities.values()];
|
|
6
|
+
const fns = { bump, listFoos };
|
|
7
|
+
// A concrete projection whose `toData` returns the entity value `Foo` — that return
|
|
8
|
+
// type is what tells the guard `listFoos` is an entity-list derivation.
|
|
9
|
+
const projection = {
|
|
10
|
+
fromState: (_store, _s) => { },
|
|
11
|
+
toState: (_store) => ({ entities: new Map(), count: 0 }),
|
|
12
|
+
toData: (_store, _e) => ({ name: "" }),
|
|
13
|
+
};
|
|
14
|
+
const bumpCases = {
|
|
15
|
+
cases: [{ name: "bumps", before: {}, args: { by: 1 }, after: { count: 1 } }],
|
|
16
|
+
};
|
|
17
|
+
const listFoosCases = {
|
|
18
|
+
cases: [{ name: "lists", input: { entities: new Map() }, value: [] }],
|
|
19
|
+
};
|
|
20
|
+
// POSITIVE — every fn has a case, and `hydrate` is exactly the entity-list derivation.
|
|
21
|
+
export const ok = feature({ state, fns, plugin, computedPlugin, projection, hydrate: ["listFoos"], cases: { bump: bumpCases, listFoos: listFoosCases } });
|
|
22
|
+
// NEGATIVE (cases coverage) — a conformed fn with no case module.
|
|
23
|
+
// @ts-expect-error — `cases` is missing `listFoos`
|
|
24
|
+
export const missingCase = feature({ state, fns, plugin, computedPlugin, projection, hydrate: ["listFoos"], cases: { bump: bumpCases } });
|
|
25
|
+
// NEGATIVE (cases coverage) — a case module for a name that is not a conformed fn.
|
|
26
|
+
// @ts-expect-error — `cases` has `bogus`, which is not in `fns`
|
|
27
|
+
export const extraCase = feature({ state, fns, plugin, computedPlugin, projection, hydrate: ["listFoos"], cases: { bump: bumpCases, listFoos: listFoosCases, bogus: bumpCases } });
|
|
28
|
+
// NEGATIVE (hydrate exactness) — an entity-list derivation left out of `hydrate`.
|
|
29
|
+
// @ts-expect-error — `hydrate` is missing the entity-list derivation `listFoos`
|
|
30
|
+
export const missingHydrate = feature({ state, fns, plugin, computedPlugin, projection, hydrate: [], cases: { bump: bumpCases, listFoos: listFoosCases } });
|
|
31
|
+
// NEGATIVE (hydrate exactness) — a non-entity-list fn listed in `hydrate`.
|
|
32
|
+
// @ts-expect-error — `hydrate` lists `bump`, which does not need hydration
|
|
33
|
+
export const extraHydrate = feature({ state, fns, plugin, computedPlugin, projection, hydrate: ["listFoos", "bump"], cases: { bump: bumpCases, listFoos: listFoosCases } });
|
|
34
|
+
const record = (s, args) => ({
|
|
35
|
+
count: s.count + args.by + (args.logger.log("x"), 0),
|
|
36
|
+
});
|
|
37
|
+
// POSITIVE — data-only `args` (the `logger` service is stripped), a scheduled return for
|
|
38
|
+
// the value method `next`, and the void `log` asserted in `effects`.
|
|
39
|
+
export const recOk = {
|
|
40
|
+
cases: [{ name: "ok", before: {}, args: { by: 1 }, responses: { logger: { next: [5] } }, after: { count: 6 }, effects: { logger: [["log", "x"]] } }],
|
|
41
|
+
};
|
|
42
|
+
// NEGATIVE — a live service instance in `args` (args are DATA only).
|
|
43
|
+
// @ts-expect-error — `logger` is a service; it must not appear in `args`
|
|
44
|
+
export const recBadArgsService = { cases: [{ name: "x", before: {}, args: { by: 1, logger: {} }, after: { count: 1 } }] };
|
|
45
|
+
// NEGATIVE — a required data arg omitted.
|
|
46
|
+
// @ts-expect-error — `args` is missing the required `by`
|
|
47
|
+
export const recMissingArg = { cases: [{ name: "x", before: {}, args: {}, after: { count: 1 } }] };
|
|
48
|
+
// NEGATIVE — scheduling a response for a `void` method (only value methods take one).
|
|
49
|
+
// @ts-expect-error — `log` is void; it cannot be scheduled in `responses`
|
|
50
|
+
export const recVoidResponse = { cases: [{ name: "x", before: {}, args: { by: 1 }, responses: { logger: { log: ["y"] } }, after: { count: 1 } }] };
|
|
51
|
+
// NEGATIVE — a scheduled response of the wrong payload type (`next` yields `number`).
|
|
52
|
+
// @ts-expect-error — `next` yields `number`, not `string`
|
|
53
|
+
export const recWrongResponse = { cases: [{ name: "x", before: {}, args: { by: 1 }, responses: { logger: { next: ["nope"] } }, after: { count: 1 } }] };
|
|
54
|
+
// POSITIVE — a `throws` case (no `after`/`effects`/`responses`).
|
|
55
|
+
export const recThrows = { cases: [{ name: "x", before: {}, args: { by: 1 }, throws: "boom" }] };
|
|
56
|
+
// NEGATIVE — `after` and `throws` are mutually exclusive.
|
|
57
|
+
// @ts-expect-error — a case is either an `after` expectation or a `throws`, never both
|
|
58
|
+
export const recBoth = { cases: [{ name: "x", before: {}, args: { by: 1 }, after: { count: 1 }, throws: true }] };
|
|
59
|
+
export const resetOk = resetCases;
|
|
60
|
+
export const asyncOk = asyncCases;
|
|
61
|
+
// POSITIVE — derivations returning an array (also a hydration key), a set, and a scalar.
|
|
62
|
+
export const arrOk = listFoosCases;
|
|
63
|
+
export const setOk = idsCases;
|
|
64
|
+
export const scalarOk = totalCases;
|
|
65
|
+
// NEGATIVE — wrong-arm annotation, caught by the envelope's content (not its arity).
|
|
66
|
+
// @ts-expect-error — a transition envelope cannot hold derivation-shaped `{ input, value }` cases
|
|
67
|
+
export const wrongTransition = { cases: [{ name: "x", input: { count: 0 }, value: 0 }] };
|
|
68
|
+
// @ts-expect-error — a derivation envelope cannot hold transition-shaped `{ before, after }` cases
|
|
69
|
+
export const wrongDerivation = { cases: [{ name: "x", before: {}, after: { count: 1 } }] };
|
|
70
|
+
// NEGATIVE — mis-pairing an array-derivation module into a transition fn's slot, caught
|
|
71
|
+
// by the `ReturnType`/args mismatch inside the envelope generics.
|
|
72
|
+
// @ts-expect-error — a derivation module for `listFoos` is not a valid case module for the transition `bump`
|
|
73
|
+
export const mispair = listFoosCases;
|
|
74
|
+
export const residualGap = resetCases;
|
|
75
|
+
// ── `computedPlugin` requiredness (needed iff the manifest has a derivation) ─────────
|
|
76
|
+
// `fns` above has `listFoos` (a derivation), so `ok` (which includes `computedPlugin`)
|
|
77
|
+
// is the POSITIVE. NEGATIVE — omit it with a derivation present:
|
|
78
|
+
// @ts-expect-error — `computedPlugin` is required because `listFoos` is a derivation
|
|
79
|
+
export const missingComputedPlugin = feature({ state, fns, plugin, projection, hydrate: ["listFoos"], cases: { bump: bumpCases, listFoos: listFoosCases } });
|
|
80
|
+
// POSITIVE — a transition-only feature may omit `computedPlugin` (no derivations).
|
|
81
|
+
const txFns = { bump };
|
|
82
|
+
export const noComputedOk = feature({ state, fns: txFns, plugin, projection, cases: { bump: txCases } });
|
|
83
|
+
const svcFns = { record };
|
|
84
|
+
// POSITIVE — the injected service `logger` has a template.
|
|
85
|
+
export const servicesOk = feature({ state, fns: svcFns, plugin, projection, services: { logger: () => logger }, cases: { record: recordCases } });
|
|
86
|
+
// NEGATIVE — a fn injects `logger`, but `services` is omitted (today: a runtime crash).
|
|
87
|
+
// @ts-expect-error — `services` is required because `record` injects `logger`
|
|
88
|
+
export const missingServices = feature({ state, fns: svcFns, plugin, projection, cases: { record: recordCases } });
|
|
89
|
+
// NEGATIVE — `services` omits the required `logger` template.
|
|
90
|
+
// @ts-expect-error — `services` is missing the `logger` template
|
|
91
|
+
export const incompleteServices = feature({ state, fns: svcFns, plugin, projection, services: {}, cases: { record: recordCases } });
|
|
92
|
+
// NEGATIVE — a service-free feature must not carry dead templates.
|
|
93
|
+
// @ts-expect-error — `bump` injects no service, so `services` must be absent
|
|
94
|
+
export const extraServices = feature({ state, fns: txFns, plugin, projection, services: { logger: () => logger }, cases: { bump: txCases } });
|
|
95
|
+
//# sourceMappingURL=feature-spec.type-test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-spec.type-test.js","sourceRoot":"","sources":["../../src/conformance/feature-spec.type-test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Schema } from "@adobe/data/schema";
|
|
2
|
+
import type { Effects } from "./types.js";
|
|
3
|
+
type MethodKeys<T> = {
|
|
4
|
+
[K in keyof T]-?: T[K] extends (...a: never[]) => unknown ? K : never;
|
|
5
|
+
}[keyof T];
|
|
6
|
+
type IsService<T> = T extends readonly unknown[] ? false : T extends (...a: never[]) => unknown ? false : T extends object ? [MethodKeys<T>] extends [never] ? false : true : false;
|
|
7
|
+
type ArgsOf<F> = F extends (...a: never[]) => unknown ? Parameters<F> extends [unknown, infer A, ...unknown[]] ? A : void : void;
|
|
8
|
+
type Payload<R> = R extends Promise<infer T> ? T : R extends AsyncGenerator<infer T, unknown, unknown> ? T : R;
|
|
9
|
+
type ScheduleOf<M> = M extends (...a: never[]) => infer R ? readonly Payload<R>[] : never;
|
|
10
|
+
type ValueMethodKeys<S> = {
|
|
11
|
+
[M in keyof S]-?: S[M] extends (...a: never[]) => infer R ? ([Payload<R>] extends [void] ? never : M) : never;
|
|
12
|
+
}[keyof S];
|
|
13
|
+
type ServiceResponses<S> = {
|
|
14
|
+
readonly [M in ValueMethodKeys<S>]?: ScheduleOf<S[M]>;
|
|
15
|
+
};
|
|
16
|
+
export type Responses<Args> = {
|
|
17
|
+
readonly [K in keyof Args as IsService<Args[K]> extends true ? K : never]?: ServiceResponses<Args[K]>;
|
|
18
|
+
};
|
|
19
|
+
type DataArgKeys<Args> = {
|
|
20
|
+
[K in keyof Args]-?: IsService<Args[K]> extends true ? never : K;
|
|
21
|
+
}[keyof Args];
|
|
22
|
+
type DataArgs<Args> = Pick<Args, DataArgKeys<Args>>;
|
|
23
|
+
export type SpecCase<State, Args> = ({
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly before: Partial<State>;
|
|
26
|
+
} & ({
|
|
27
|
+
readonly after: Partial<State>;
|
|
28
|
+
readonly effects?: Effects<Args>;
|
|
29
|
+
readonly responses?: Responses<Args>;
|
|
30
|
+
readonly throws?: undefined;
|
|
31
|
+
} | {
|
|
32
|
+
readonly throws: true | string;
|
|
33
|
+
readonly after?: undefined;
|
|
34
|
+
readonly effects?: undefined;
|
|
35
|
+
readonly responses?: undefined;
|
|
36
|
+
})) & ([keyof DataArgs<Args>] extends [never] ? {
|
|
37
|
+
readonly args?: undefined;
|
|
38
|
+
} : {
|
|
39
|
+
readonly args: DataArgs<Args>;
|
|
40
|
+
});
|
|
41
|
+
export type SpecDerivationCase<Input, Value> = {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly input: Input;
|
|
44
|
+
readonly value: Value;
|
|
45
|
+
};
|
|
46
|
+
export type SpecCases<State, F extends (...a: never[]) => unknown> = {
|
|
47
|
+
readonly args?: Schema;
|
|
48
|
+
readonly cases: readonly SpecCase<State, ArgsOf<F>>[];
|
|
49
|
+
};
|
|
50
|
+
export type SpecDerivations<F extends (...a: never[]) => unknown> = {
|
|
51
|
+
readonly cases: readonly SpecDerivationCase<Parameters<F>[0], ReturnType<F>>[];
|
|
52
|
+
};
|
|
53
|
+
export type ProjectionValue<P> = P extends {
|
|
54
|
+
toData?: (store: never, entity: never) => infer V;
|
|
55
|
+
} ? V : never;
|
|
56
|
+
export type ProjectionStore<P> = P extends {
|
|
57
|
+
toState: (store: infer S) => unknown;
|
|
58
|
+
} ? S : unknown;
|
|
59
|
+
export type ProjectionShape<State> = {
|
|
60
|
+
readonly fromState: (store: never, state: State) => unknown;
|
|
61
|
+
readonly toState: (store: never) => State;
|
|
62
|
+
readonly toData?: (store: never, entity: never) => unknown;
|
|
63
|
+
};
|
|
64
|
+
export type HydrationKeys<Fns, V> = [V] extends [never] ? never : {
|
|
65
|
+
[K in keyof Fns]: Fns[K] extends (...a: never[]) => infer R ? [R] extends [readonly V[]] ? K & string : [R] extends [ReadonlySet<V>] ? K & string : never : never;
|
|
66
|
+
}[keyof Fns];
|
|
67
|
+
export type HydrateExact<Provided extends readonly string[], Required extends string> = [
|
|
68
|
+
Exclude<Required, Provided[number]>
|
|
69
|
+
] extends [never] ? [Exclude<Provided[number], Required>] extends [never] ? unknown : {
|
|
70
|
+
readonly __hydrateError: "hydrate lists a computed that does not need hydration";
|
|
71
|
+
readonly unexpected: Exclude<Provided[number], Required>;
|
|
72
|
+
} : {
|
|
73
|
+
readonly __hydrateError: "hydrate is missing required entity-list computed(s)";
|
|
74
|
+
readonly missing: Exclude<Required, Provided[number]>;
|
|
75
|
+
};
|
|
76
|
+
export type ServiceArgKeys<Fns> = {
|
|
77
|
+
[K in keyof Fns]: {
|
|
78
|
+
[A in keyof ArgsOf<Fns[K]>]-?: IsService<ArgsOf<Fns[K]>[A]> extends true ? A : never;
|
|
79
|
+
}[keyof ArgsOf<Fns[K]>];
|
|
80
|
+
}[keyof Fns] & string;
|
|
81
|
+
export type ServiceForKey<Fns, K extends string> = {
|
|
82
|
+
[F in keyof Fns]: K extends keyof ArgsOf<Fns[F]> ? ArgsOf<Fns[F]>[K] : never;
|
|
83
|
+
}[keyof Fns];
|
|
84
|
+
export type RequiredServices<Fns> = [ServiceArgKeys<Fns>] extends [never] ? {
|
|
85
|
+
readonly services?: undefined;
|
|
86
|
+
} : {
|
|
87
|
+
readonly services: {
|
|
88
|
+
readonly [K in ServiceArgKeys<Fns>]: () => ServiceForKey<Fns, K>;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
export type HasDerivations<Cases> = true extends {
|
|
92
|
+
[K in keyof Cases]: Cases[K] extends SpecDerivations<(...a: never[]) => unknown> ? true : false;
|
|
93
|
+
}[keyof Cases] ? true : false;
|
|
94
|
+
export type CaseModule<State, F extends (...a: never[]) => unknown> = SpecCases<State, F> | SpecDerivations<F>;
|
|
95
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-types.js","sourceRoot":"","sources":["../../src/conformance/feature-types.ts"],"names":[],"mappings":""}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
1
|
+
export { feature, checkSpec, checkFeature, type FeatureSpec } from "./feature-spec.js";
|
|
2
|
+
export type { Responses, SpecCase, SpecCases, SpecDerivationCase, SpecDerivations, CaseModule, } from "./feature-types.js";
|
|
3
|
+
export type { Effects, ServiceCall } from "./types.js";
|
|
4
|
+
export { adaptCases } from "./adapt-spec.js";
|
|
4
5
|
export { runTransactions, type TransactionRunConfig } from "./run-transactions.js";
|
|
5
6
|
export { runActions, type ActionRunConfig } from "./run-actions.js";
|
|
6
7
|
export { runComputeds, type ComputedRunConfig } from "./run-computeds.js";
|
|
7
|
-
export
|
|
8
|
+
export type { Projection } from "./run-feature.js";
|
|
8
9
|
export { assertState } from "./assert-state.js";
|
|
9
10
|
export { expectThrows } from "./expect-throws.js";
|
|
10
11
|
export type { SchemaSource } from "./refify.js";
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
// The conformance manifest API — the standard surface. `feature` builds a feature's
|
|
3
|
+
// manifest; `checkSpec` runs the pure spec suite, `checkFeature` the ecs suite.
|
|
4
|
+
export { feature, checkSpec, checkFeature } from "./feature-spec.js";
|
|
5
|
+
// The escape hatch — the lower-level runners, for a feature whose ecs conformance needs
|
|
6
|
+
// per-surface `seedContext` or a custom concurrency/db (e.g. a user-scoped `userId`); it
|
|
7
|
+
// feeds them from its manifest with `adaptCases`. Ordinary features use `checkFeature`.
|
|
8
|
+
export { adaptCases } from "./adapt-spec.js";
|
|
3
9
|
export { runTransactions } from "./run-transactions.js";
|
|
4
10
|
export { runActions } from "./run-actions.js";
|
|
5
11
|
export { runComputeds } from "./run-computeds.js";
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
// up to an id-bijection, the same way the runners do.
|
|
12
|
+
// For custom conformance harnesses: compare two States up to an id-bijection, and assert
|
|
13
|
+
// a call throws, the same way the built-in runners do.
|
|
9
14
|
export { assertState } from "./assert-state.js";
|
|
10
|
-
// For custom conformance harnesses: assert a call throws, the same way a case's
|
|
11
|
-
// `throws` field does in the built-in runners.
|
|
12
15
|
export { expectThrows } from "./expect-throws.js";
|
|
13
16
|
//# sourceMappingURL=public.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.js","sourceRoot":"","sources":["../../src/conformance/public.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"public.js","sourceRoot":"","sources":["../../src/conformance/public.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,oFAAoF;AACpF,gFAAgF;AAChF,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAoB,MAAM,mBAAmB,CAAC;AAWvF,wFAAwF;AACxF,yFAAyF;AACzF,wFAAwF;AACxF,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAA6B,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,UAAU,EAAwB,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,YAAY,EAA0B,MAAM,oBAAoB,CAAC;AAG1E,yFAAyF;AACzF,uDAAuD;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
-
import {
|
|
2
|
+
import { runSpec } from "./run-spec.js";
|
|
3
3
|
// A module whose envelope holds `cases: []` opens an empty `describe` and used to
|
|
4
4
|
// leave the file suite-less. Empty discovery registers a no-op so Vitest still
|
|
5
5
|
// collects a suite.
|
|
6
|
-
|
|
6
|
+
runSpec({
|
|
7
7
|
state: { create: () => ({ n: 0 }) },
|
|
8
8
|
transitions: {
|
|
9
9
|
"./empty-cases.ts": {
|
|
@@ -1 +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,
|
|
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,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,kFAAkF;AAClF,+EAA+E;AAC/E,oBAAoB;AACpB,OAAO,CAAC;IACN,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,KAAK,EAAE,EAAE,EAAE;SACrB;KACF;CACF,CAAC,CAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
-
import {
|
|
2
|
+
import { runSpec } from "./run-spec.js";
|
|
3
3
|
// Features with empty State and no pure transforms still call `runSpec` from a
|
|
4
4
|
// dedicated `spec.test.ts`. Discovery with zero case modules must not leave the
|
|
5
5
|
// file suite-less (Vitest: "No test suite found"). This file is the regression:
|
|
6
6
|
// it is only this call — if empty discovery failed, vitest would fail the file.
|
|
7
|
-
|
|
7
|
+
runSpec({
|
|
8
8
|
state: { create: () => ({}) },
|
|
9
9
|
transitions: {},
|
|
10
10
|
});
|
|
@@ -1 +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,
|
|
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,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,+EAA+E;AAC/E,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,OAAO,CAAC;IACN,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE;IAC7B,WAAW,EAAE,EAAE;CAChB,CAAC,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
-
import {
|
|
2
|
+
import { runSpec } from "./run-spec.js";
|
|
3
3
|
// Non-empty discovery: one pure transition with co-located cases still pairs and
|
|
4
4
|
// asserts as before (empty-discovery path must not break populated specs).
|
|
5
5
|
const increment = (state, { by }) => ({ n: state.n + by });
|
|
@@ -9,7 +9,7 @@ const withdraw = (state, { amount }) => {
|
|
|
9
9
|
throw new Error(`insufficient funds: have ${state.n}, want ${amount}`);
|
|
10
10
|
return { n: state.n - amount };
|
|
11
11
|
};
|
|
12
|
-
|
|
12
|
+
runSpec({
|
|
13
13
|
state: { create: () => ({ n: 0 }) },
|
|
14
14
|
transitions: {
|
|
15
15
|
"./increment.ts": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-spec.test.js","sourceRoot":"","sources":["../../src/conformance/run-spec.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"run-spec.test.js","sourceRoot":"","sources":["../../src/conformance/run-spec.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,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,6EAA6E;AAC7E,MAAM,QAAQ,GAAG,CACf,KAAoB,EACpB,EAAE,MAAM,EAAsB,EACf,EAAE;IACjB,IAAI,MAAM,GAAG,KAAK,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,KAAK,CAAC,CAAC,UAAU,MAAM,EAAE,CAAC,CAAC;IAC7F,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC;AACjC,CAAC,CAAC;AAEF,OAAO,CAAC;IACN,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,iFAAiF;YACjF,wEAAwE;YACxE,KAAK,EAAE;gBACL,KAAK,EAAE;oBACL;wBACE,IAAI,EAAE,oCAAoC;wBAC1C,MAAM,EAAE,EAAE;wBACV,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;wBACf,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE;qBAChB;oBACD;wBACE,IAAI,EAAE,mCAAmC;wBACzC,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;wBACjB,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;wBACf,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;qBACjB;iBACF;aACF;SACF;QACD,eAAe,EAAE;YACf,QAAQ;YACR,KAAK,EAAE;gBACL,KAAK,EAAE;oBACL;wBACE,IAAI,EAAE,0BAA0B;wBAChC,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;wBACjB,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE;wBACnB,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE;qBAChB;oBACD;wBACE,IAAI,EAAE,yCAAyC;wBAC/C,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;wBACjB,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;wBACpB,MAAM,EAAE,IAAI;qBACb;oBACD;wBACE,IAAI,EAAE,sDAAsD;wBAC5D,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;wBACjB,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;wBACpB,MAAM,EAAE,oBAAoB;qBAC7B;iBACF;aACF;SACF;KACF;CACF,CAAC,CAAC"}
|
|
@@ -8,28 +8,9 @@ export type ServiceCall<S> = {
|
|
|
8
8
|
export type Effects<Args> = {
|
|
9
9
|
readonly [K in keyof Args as IsService<Args[K]> extends true ? K : never]?: readonly ServiceCall<Args[K]>[] | ReadonlySet<ServiceCall<Args[K]>>;
|
|
10
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
|
-
} & ({
|
|
16
|
-
readonly after: Partial<State>;
|
|
17
|
-
readonly effects?: Effects<Args>;
|
|
18
|
-
readonly throws?: undefined;
|
|
19
|
-
} | {
|
|
20
|
-
readonly throws: true | string;
|
|
21
|
-
readonly after?: undefined;
|
|
22
|
-
readonly effects?: undefined;
|
|
23
|
-
})) & ([Args] extends [void] ? {
|
|
24
|
-
readonly args?: undefined;
|
|
25
|
-
} : {
|
|
26
|
-
readonly args: Args;
|
|
27
|
-
});
|
|
28
|
-
export type Cases<State, F extends (...args: never[]) => unknown> = readonly Case<State, ArgsOf<F>>[];
|
|
29
11
|
export type DerivationCase<Input, Value> = {
|
|
30
12
|
readonly name: string;
|
|
31
13
|
readonly input: Input;
|
|
32
14
|
readonly value: Value;
|
|
33
15
|
};
|
|
34
|
-
export type DerivationCases<F extends (...args: never[]) => unknown> = readonly DerivationCase<Parameters<F>[0], ReturnType<F>>[];
|
|
35
16
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * as Match from "./match/public.js";
|
|
2
2
|
export * as Conformance from "./conformance/public.js";
|
|
3
|
-
export type {
|
|
3
|
+
export type { SpecCase, SpecCases, SpecDerivationCase, SpecDerivations, Responses, CaseModule, FeatureSpec, Effects, ServiceCall, } from "./conformance/public.js";
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
2
|
// Shared test-only utilities for the spec↔ecs conformance pattern. Two namespaces:
|
|
3
3
|
// Match — tolerant, matcher-aware value comparison (framework-agnostic).
|
|
4
|
-
// Conformance — the
|
|
5
|
-
//
|
|
6
|
-
// Import only from `*.
|
|
7
|
-
//
|
|
8
|
-
// `Conformance.derivations(fn, …)` call in a transform file is still a plain
|
|
9
|
-
// function call — an unannotated call is kept even when its result (the `cases`
|
|
10
|
-
// export) is never read. Every call site MUST be written
|
|
11
|
-
// `/*@__PURE__*/ Conformance.cases(...)` (see `data/state.md`), or the case
|
|
12
|
-
// fixtures — and this package's builder code — ship in the production bundle
|
|
13
|
-
// of any app that actually calls the transition. Verified against a Vite/Rollup
|
|
14
|
-
// production build: without the annotation the case data is retained; with it,
|
|
15
|
-
// gone.
|
|
4
|
+
// Conformance — the manifest API (`feature` / `checkSpec` / `checkFeature`), the case
|
|
5
|
+
// types, and the lower-level runner escape hatch.
|
|
6
|
+
// Import only from a feature's test tier (its `*.cases.ts`, `spec.ts`, `*.fake.ts`, and
|
|
7
|
+
// `*.test.ts`), which the package's project-reference wall keeps out of the runtime build.
|
|
16
8
|
export * as Match from "./match/public.js";
|
|
17
9
|
export * as Conformance from "./conformance/public.js";
|
|
18
10
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,mFAAmF;AACnF,iFAAiF;AACjF,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,mFAAmF;AACnF,iFAAiF;AACjF,wFAAwF;AACxF,kEAAkE;AAClE,wFAAwF;AACxF,2FAA2F;AAC3F,OAAO,KAAK,KAAK,MAAM,mBAAmB,CAAC;AAC3C,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/data-testing",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.20",
|
|
4
4
|
"description": "Conformance-testing utilities (Match + Conformance runners) for @adobe/data ECS features",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"LICENSE"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@adobe/data": "0.10.
|
|
18
|
+
"@adobe/data": "0.10.20"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"vitest": "^1.6.0"
|