From d28560458e55136f9c139de3c4cf5fe744371461 Mon Sep 17 00:00:00 2001 From: Kris Nye Date: Fri, 7 Aug 2026 23:34:09 -0700 Subject: [PATCH 1/2] fix(testing): allow Conformance.runSpec with zero case modules Features with empty State still ship a one-line spec.test.ts that calls runSpec. When discovery finds no cases, Vitest previously failed the file with "No test suite found". Register a no-op sentinel instead so empty discovery is a valid outcome. Co-authored-by: Cursor --- .../conformance/run-spec.empty-cases.test.ts | 14 +++++ .../conformance/run-spec.empty.test.ts | 11 ++++ .../src/testing/conformance/run-spec.test.ts | 32 ++++++++++ .../data/src/testing/conformance/run-spec.ts | 62 ++++++++++++++++--- 4 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 packages/data/src/testing/conformance/run-spec.empty-cases.test.ts create mode 100644 packages/data/src/testing/conformance/run-spec.empty.test.ts create mode 100644 packages/data/src/testing/conformance/run-spec.test.ts diff --git a/packages/data/src/testing/conformance/run-spec.empty-cases.test.ts b/packages/data/src/testing/conformance/run-spec.empty-cases.test.ts new file mode 100644 index 00000000..ef9c9ed9 --- /dev/null +++ b/packages/data/src/testing/conformance/run-spec.empty-cases.test.ts @@ -0,0 +1,14 @@ +// © 2026 Adobe. MIT License. See /LICENSE for details. +import { Conformance } from "../index.js"; + +// Module with `cases: []` opens an empty `describe` and used to leave the file +// suite-less. Empty discovery registers a no-op so Vitest still collects a suite. +Conformance.runSpec({ + state: { create: () => ({ n: 0 }) }, + transitions: { + "./empty-cases.ts": { + noop: (state: { n: number }) => state, + cases: [], + }, + }, +}); diff --git a/packages/data/src/testing/conformance/run-spec.empty.test.ts b/packages/data/src/testing/conformance/run-spec.empty.test.ts new file mode 100644 index 00000000..6777f6e1 --- /dev/null +++ b/packages/data/src/testing/conformance/run-spec.empty.test.ts @@ -0,0 +1,11 @@ +// © 2026 Adobe. MIT License. See /LICENSE for details. +import { Conformance } from "../index.js"; + +// Features with empty State and no pure transforms still call `runSpec` from a +// dedicated `spec.test.ts`. Discovery with zero case modules must not leave the +// file suite-less (Vitest: "No test suite found"). This file is the regression: +// it is only this call — if empty discovery failed, vitest would fail the file. +Conformance.runSpec({ + state: { create: () => ({}) }, + transitions: {}, +}); diff --git a/packages/data/src/testing/conformance/run-spec.test.ts b/packages/data/src/testing/conformance/run-spec.test.ts new file mode 100644 index 00000000..324b0ccd --- /dev/null +++ b/packages/data/src/testing/conformance/run-spec.test.ts @@ -0,0 +1,32 @@ +// © 2026 Adobe. MIT License. See /LICENSE for details. +import { Conformance } from "../index.js"; + +// Non-empty discovery: one pure transition with co-located cases still pairs and +// asserts as before (empty-discovery path must not break populated specs). +const increment = ( + state: { n: number }, + { by }: { by: number }, +): { n: number } => ({ n: state.n + by }); + +Conformance.runSpec({ + state: { create: () => ({ n: 0 }) }, + transitions: { + "./increment.ts": { + increment, + cases: [ + { + name: "adds the delta over State.create()", + before: {}, + args: { by: 2 }, + after: { n: 2 }, + }, + { + name: "honors a non-default before delta", + before: { n: 10 }, + args: { by: 1 }, + after: { n: 11 }, + }, + ], + }, + }, +}); diff --git a/packages/data/src/testing/conformance/run-spec.ts b/packages/data/src/testing/conformance/run-spec.ts index a3fe0a53..04db0418 100644 --- a/packages/data/src/testing/conformance/run-spec.ts +++ b/packages/data/src/testing/conformance/run-spec.ts @@ -23,13 +23,36 @@ export interface SpecRunConfig { readonly label?: (path: string, fnName: string | undefined) => string; } +type InvalidSuite = { + readonly kind: "invalid"; + readonly path: string; + readonly label: string; + readonly exportNames: readonly string[]; +}; + +type CasesSuite = { + readonly kind: "cases"; + readonly label: string; + readonly fn: (...args: unknown[]) => unknown; + readonly cases: readonly unknown[]; +}; + +type Suite = InvalidSuite | CasesSuite; + // The single pure-spec test for every transform AND derivation in a `data/state/` // folder. It auto-discovers each file that exports `cases`, requires that file to // export exactly its function plus `cases`, and dispatches on case shape: a `value` // case checks a derivation `(state) => value`; otherwise a transition `(state, // args) => state`, whose declared `effects` on injected services are also asserted. // A service-injected transition is async, so results are awaited uniformly. +// +// Features with empty `State` and no pure transforms still keep a one-line +// `spec.test.ts` that calls `runSpec`. When discovery finds zero case modules (or +// only empty `cases: []` arrays), Vitest would otherwise fail the file with +// "No test suite found" — so we register a single no-op so empty discovery is a +// valid outcome, not an error. export const runSpec = (config: SpecRunConfig): void => { + const suites: Suite[] = []; for (const [path, module] of Object.entries(config.transitions)) { const exportNames = Object.keys(module); if (!exportNames.includes("cases")) continue; @@ -40,22 +63,43 @@ export const runSpec = (config: SpecRunConfig): void => { const label = config.label ? config.label(path, fnName) : `State.${fnName ?? path}`; - describe(label, () => { - if (exportNames.length !== 2 || functionNames.length !== 1) { + if (exportNames.length !== 2 || functionNames.length !== 1) { + suites.push({ kind: "invalid", path, label, exportNames }); + continue; + } + const cases = module["cases"] as readonly unknown[]; + // Empty `cases: []` is not a suite (and would open a Vitest describe with no + // tests, which fails even when this file has other suites). + if (cases.length === 0) continue; + suites.push({ + kind: "cases", + label, + fn: module[functionNames[0]!] as (...args: unknown[]) => unknown, + cases, + }); + } + + if (suites.length === 0) { + it("has no pure transitions to specify", () => { + // intentional no-op — empty discovery is a valid feature shape + }); + return; + } + + for (const suite of suites) { + describe(suite.label, () => { + if (suite.kind === "invalid") { it("exports exactly its function and `cases`", () => { throw new Error( - `${path} exports [${exportNames.join(", ")}] — expected one function + cases`, + `${suite.path} exports [${suite.exportNames.join(", ")}] — expected one function + cases`, ); }); return; } - // Runtime invariant: a participating file exports one function and its cases. - const fn = module[functionNames[0]] as (...args: unknown[]) => unknown; - const cases = module["cases"] as readonly unknown[]; - for (const testCase of cases) { + for (const testCase of suite.cases) { if (isDerivationCase(testCase)) { it(testCase.name, () => - assert(fn(testCase.input), testCase.value, config.match), + assert(suite.fn(testCase.input), testCase.value, config.match), ); continue; } @@ -75,7 +119,7 @@ export const runSpec = (config: SpecRunConfig): void => { ...(config.state?.create() ?? {}), ...(tc.before as Record), }; - const result = (await fn(before, args)) as Record; + const result = (await suite.fn(before, args)) as Record; assert( { ...before, ...result }, { ...before, ...(tc.after as Record) }, From 4dccf4df5b9574dd7567320db71b20fd2e0d2bf9 Mon Sep 17 00:00:00 2001 From: Kris Nye Date: Fri, 7 Aug 2026 23:43:39 -0700 Subject: [PATCH 2/2] chore: bump monorepo to 0.9.94 Co-authored-by: Cursor --- package.json | 2 +- packages/data-ai/.claude-plugin/plugin.json | 2 +- packages/data-ai/package.json | 2 +- packages/data-gpu-hopper/package.json | 2 +- packages/data-gpu-samples/package.json | 2 +- packages/data-gpu/package.json | 2 +- packages/data-lit-space-rock-game/package.json | 2 +- packages/data-lit-tictactoe/package.json | 2 +- packages/data-lit-todo/package.json | 2 +- packages/data-lit/package.json | 2 +- packages/data-p2p-tictactoe/package.json | 2 +- packages/data-persistence/package.json | 2 +- packages/data-react-hello/package.json | 2 +- packages/data-react-pixie/package.json | 2 +- packages/data-react/package.json | 2 +- packages/data-solid-dashboard/package.json | 2 +- packages/data-solid/package.json | 2 +- packages/data-sync/package.json | 2 +- packages/data/package.json | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 2e671f75..e97fed44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "data-monorepo", - "version": "0.9.93", + "version": "0.9.94", "private": true, "engines": { "node": ">=24" diff --git a/packages/data-ai/.claude-plugin/plugin.json b/packages/data-ai/.claude-plugin/plugin.json index 24b24366..c697bb5a 100644 --- a/packages/data-ai/.claude-plugin/plugin.json +++ b/packages/data-ai/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "adobe-data-ai", - "version": "0.9.93", + "version": "0.9.94", "description": "Architecture skills for @adobe/data — data-oriented modelling, archetype iteration, hot-path performance, and related conventions.", "author": { "name": "Adobe" diff --git a/packages/data-ai/package.json b/packages/data-ai/package.json index 0e3074e4..e1c8179b 100644 --- a/packages/data-ai/package.json +++ b/packages/data-ai/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-ai", - "version": "0.9.93", + "version": "0.9.94", "description": "Cross-agent architecture skills for @adobe/data — installable as a Claude Code plugin or copied into any Agent-Skills-compatible agent (Cursor, Codex).", "type": "module", "private": false, diff --git a/packages/data-gpu-hopper/package.json b/packages/data-gpu-hopper/package.json index 9765465f..a1234a05 100644 --- a/packages/data-gpu-hopper/package.json +++ b/packages/data-gpu-hopper/package.json @@ -1,6 +1,6 @@ { "name": "data-gpu-hopper", - "version": "0.9.93", + "version": "0.9.94", "description": "Hopper sample - real-time ECS game rendered as colored cubes via @adobe/data-gpu", "type": "module", "private": true, diff --git a/packages/data-gpu-samples/package.json b/packages/data-gpu-samples/package.json index 7132f5fd..f5b67813 100644 --- a/packages/data-gpu-samples/package.json +++ b/packages/data-gpu-samples/package.json @@ -1,6 +1,6 @@ { "name": "data-gpu-samples", - "version": "0.9.93", + "version": "0.9.94", "description": "WebGPU samples built on @adobe/data-gpu", "type": "module", "private": true, diff --git a/packages/data-gpu/package.json b/packages/data-gpu/package.json index c5cd1109..c31512eb 100644 --- a/packages/data-gpu/package.json +++ b/packages/data-gpu/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-gpu", - "version": "0.9.93", + "version": "0.9.94", "description": "Adobe data WebGPU plugins and types for graphics and compute", "type": "module", "private": false, diff --git a/packages/data-lit-space-rock-game/package.json b/packages/data-lit-space-rock-game/package.json index 249f61df..23d72670 100644 --- a/packages/data-lit-space-rock-game/package.json +++ b/packages/data-lit-space-rock-game/package.json @@ -1,6 +1,6 @@ { "name": "data-lit-space-rock-game", - "version": "0.9.93", + "version": "0.9.94", "description": "Space Rock Game sample - real-time ECS game with Lit and @adobe/data", "type": "module", "private": true, diff --git a/packages/data-lit-tictactoe/package.json b/packages/data-lit-tictactoe/package.json index b17cb3d1..f5b8806b 100644 --- a/packages/data-lit-tictactoe/package.json +++ b/packages/data-lit-tictactoe/package.json @@ -1,6 +1,6 @@ { "name": "data-lit-tictactoe", - "version": "0.9.93", + "version": "0.9.94", "description": "Tic-Tac-Toe sample - Lit web components with @adobe/data-lit and AgenticService", "type": "module", "private": true, diff --git a/packages/data-lit-todo/package.json b/packages/data-lit-todo/package.json index a1240ecf..92034056 100644 --- a/packages/data-lit-todo/package.json +++ b/packages/data-lit-todo/package.json @@ -1,6 +1,6 @@ { "name": "data-lit-todo", - "version": "0.9.93", + "version": "0.9.94", "description": "Todo application - Lit web components with @adobe/data ECS", "type": "module", "private": true, diff --git a/packages/data-lit/package.json b/packages/data-lit/package.json index 4df97351..ed872a09 100644 --- a/packages/data-lit/package.json +++ b/packages/data-lit/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-lit", - "version": "0.9.93", + "version": "0.9.94", "description": "Adobe data Lit bindings - hooks, elements, decorators", "type": "module", "private": false, diff --git a/packages/data-p2p-tictactoe/package.json b/packages/data-p2p-tictactoe/package.json index 897827d3..164f6c65 100644 --- a/packages/data-p2p-tictactoe/package.json +++ b/packages/data-p2p-tictactoe/package.json @@ -1,6 +1,6 @@ { "name": "data-p2p-tictactoe", - "version": "0.9.93", + "version": "0.9.94", "description": "Serverless P2P tic-tac-toe — WebRTC DataChannel + @adobe/data-sync", "type": "module", "private": true, diff --git a/packages/data-persistence/package.json b/packages/data-persistence/package.json index 013161ed..b1c99f7c 100644 --- a/packages/data-persistence/package.json +++ b/packages/data-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-persistence", - "version": "0.9.93", + "version": "0.9.94", "description": "Worker-based incremental persistence layer for @adobe/data ECS over OPFS (browser) and node:fs (server).", "type": "module", "sideEffects": false, diff --git a/packages/data-react-hello/package.json b/packages/data-react-hello/package.json index c6ed3fdf..5609d23c 100644 --- a/packages/data-react-hello/package.json +++ b/packages/data-react-hello/package.json @@ -1,6 +1,6 @@ { "name": "data-react-hello", - "version": "0.9.93", + "version": "0.9.94", "description": "Hello World sample - click counter using @adobe/data-react", "type": "module", "private": true, diff --git a/packages/data-react-pixie/package.json b/packages/data-react-pixie/package.json index 3859c042..9c85c23d 100644 --- a/packages/data-react-pixie/package.json +++ b/packages/data-react-pixie/package.json @@ -1,6 +1,6 @@ { "name": "data-react-pixie", - "version": "0.9.93", + "version": "0.9.94", "description": "PixiJS React sample - ECS sprites (bunny, fox) with @adobe/data-react", "type": "module", "private": true, diff --git a/packages/data-react/package.json b/packages/data-react/package.json index 6db84df7..1ce6dbb9 100644 --- a/packages/data-react/package.json +++ b/packages/data-react/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-react", - "version": "0.9.93", + "version": "0.9.94", "description": "Adobe data React bindings — hooks and context for ECS database", "type": "module", "private": false, diff --git a/packages/data-solid-dashboard/package.json b/packages/data-solid-dashboard/package.json index 4a7b505b..ebd903ac 100644 --- a/packages/data-solid-dashboard/package.json +++ b/packages/data-solid-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "data-solid-dashboard", - "version": "0.9.93", + "version": "0.9.94", "description": "Mini dashboard sample — multiple components sharing one @adobe/data ECS database with SolidJS", "type": "module", "private": true, diff --git a/packages/data-solid/package.json b/packages/data-solid/package.json index 55cd34bf..213187b2 100644 --- a/packages/data-solid/package.json +++ b/packages/data-solid/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-solid", - "version": "0.9.93", + "version": "0.9.94", "description": "Adobe data SolidJS bindings — context and provider for ECS database", "type": "module", "private": false, diff --git a/packages/data-sync/package.json b/packages/data-sync/package.json index 31f2a680..3aa72e75 100644 --- a/packages/data-sync/package.json +++ b/packages/data-sync/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data-sync", - "version": "0.9.93", + "version": "0.9.94", "description": "Multi-user real-time synchronisation for @adobe/data ECS — server, client, and in-process loopback.", "type": "module", "sideEffects": false, diff --git a/packages/data/package.json b/packages/data/package.json index d04b8478..b82a6da5 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/data", - "version": "0.9.93", + "version": "0.9.94", "description": "Adobe data oriented programming library", "type": "module", "sideEffects": false,