From eab9930758315e674cbcf71ba7883fed62b6af7e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 15:30:40 +0000 Subject: [PATCH] test(spec-parity): converge the non-optional enum-options casts onto the shared reader (#7025) 9 spec-parity test files in 7 packages cast an enum node NON-optionally to an options-bearing shape and read `.options` straight off it -- the SIXTH spelling of the Zod-internals reader hazard objectui#5872 catalogues, and the one objectui#6924's instrument (`options?:`) structurally cannot see. They converge onto `@object-ui/test-support`'s `enumOptions(node)`, the walk PR #7024 exported for objectui#6924's family. The decisive difference from that family is the FAILURE MODE, and it decides the shape of every conversion here. #6924's sites guarded with `Array.isArray(raw) ? [...raw] : []`, so a failed cast went quietly empty. These sites spread the cast result directly, so a failed cast THROWS. `enumOptions` deliberately answers `[]` rather than raising, so a bare conversion would have downgraded a loud throw into a silent empty vocabulary -- turning this population into #6924's, backwards. Every site therefore keeps its own non-vacuity duty: - 7 module-scope reads gain a throwing wrapper, modelled on the two sites that already solved this in PR #7024 (`types/spec-derived-unions.test.ts` and `examples/schema-catalog/.../component-fixture-declared-keys.test.ts`); - 2 in-test reads (`plugin-grid/spec-symbol-batch7`, `types/spec-subschema-parity`) already carried an explicit non-vacuity assertion, which is the sanctioned alternative, so they keep theirs. The four `expandableFamily.identity-*` files are textually identical across four packages -- #5872 class (1)'s sharpest shape, where a reviewer diffing one copy cannot see that the other three did not move -- and were converted as one unit. Verdict preservation measured, not assumed: all 9 readings return the identical array in the identical order before and after (FieldType n=49, ColumnSummarySchema n=11) against the installed pin. The retired spelling is deliberately not quoted in the new comments: the card's enumeration instrument is a grep for it, and a comment carrying the literal text would make every future re-derivation read a false positive. Out of scope, measured and left alone: `types/spec-subschema-parity.test.ts:161` casts to an `unwrap().def.options` shape -- a further spelling, and a #5872 class (2)/(4) shape rather than this one. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB --- .changeset/7025-nonoptional-enum-cast.md | 20 +++++++++++++ .../expandableFamily.identity-5874.test.ts | 28 +++++++++++++++++-- .../src/views/richtextSurfaceParity.test.tsx | 26 ++++++++++++++++- .../expandableFamily.identity-5692.test.ts | 28 +++++++++++++++++-- .../__tests__/autoLayout.wideSpelling.test.ts | 26 ++++++++++++++++- .../expandableFamily.identity-5874.test.tsx | 28 +++++++++++++++++-- packages/plugin-form/package.json | 1 + .../__tests__/autoLayout.wideSpelling.test.ts | 26 ++++++++++++++++- .../src/__tests__/spec-symbol-batch7.test.ts | 21 ++++++++++++-- packages/plugin-kanban/package.json | 1 + .../expandableFamily.identity-5874.test.tsx | 28 +++++++++++++++++-- .../__tests__/spec-subschema-parity.test.ts | 8 +++++- pnpm-lock.yaml | 6 ++++ 13 files changed, 229 insertions(+), 18 deletions(-) create mode 100644 .changeset/7025-nonoptional-enum-cast.md diff --git a/.changeset/7025-nonoptional-enum-cast.md b/.changeset/7025-nonoptional-enum-cast.md new file mode 100644 index 0000000000..cdf00e8bcf --- /dev/null +++ b/.changeset/7025-nonoptional-enum-cast.md @@ -0,0 +1,20 @@ +--- +--- + +Internal test-only change, no user-visible behaviour (objectui#7025). + +The SIXTH spelling of the Zod-internals reader hazard objectui#5872 catalogues: +9 spec-parity test files in 7 packages cast an enum node NON-optionally to an +options-bearing shape and read `.options` straight off it. They converge onto +`@object-ui/test-support`'s `enumOptions(node)` — the same walk objectui#6924 +converged the optional-cast family onto in PR #7024. + +Unlike that family, these sites failed LOUDLY (spreading `undefined` throws), and +the shared reader deliberately answers `[]` rather than raising. So every site +keeps its own non-vacuity duty: a throwing wrapper at the seven module-scope +reads, and the suite's own pre-existing non-vacuity assertion at the two in-test +reads. A bare conversion would have traded a loud failure for a silent empty +vocabulary — a regression, not a convergence. + +Only test files, two `devDependencies` edges and the lockfile change; no +package's shipped `dist/` and no public type moves. diff --git a/packages/app-shell/src/utils/expandableFamily.identity-5874.test.ts b/packages/app-shell/src/utils/expandableFamily.identity-5874.test.ts index b9dd40859f..29f68b01f9 100644 --- a/packages/app-shell/src/utils/expandableFamily.identity-5874.test.ts +++ b/packages/app-shell/src/utils/expandableFamily.identity-5874.test.ts @@ -54,6 +54,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { EXPANDABLE_FIELD_TYPES } from '@object-ui/core'; import { FieldType } from '@objectstack/spec/data'; +import { enumOptions } from '@object-ui/test-support'; import { ActionParamSchema } from '@objectstack/spec/ui'; import { resolveActionParams, @@ -61,9 +62,30 @@ import { type RawActionParam, } from './resolveActionParams'; -const SPEC_FIELD_TYPES: readonly string[] = [ - ...(FieldType as unknown as { options: readonly string[] }).options, -]; +/** + * The spec's own `FieldType` vocabulary. + * + * The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924); + * the THROW stays HERE, because the reader deliberately answers `[]` rather than + * raising and this read is module-scope. What it replaces was a NON-OPTIONAL + * cast of the enum node to an options-bearing shape, spread directly — which + * failed LOUDLY the moment the cast stopped holding (spreading `undefined` + * throws), so a bare `enumOptions` call here would have traded a loud failure + * for a silently empty vocabulary: every assertion below would then pass over + * nothing. That trade is the regression objectui#7025 exists to refuse. (The + * retired spelling is deliberately NOT quoted here: the card's enumeration + * instrument is a grep for it, and a comment carrying the literal text makes + * every future re-derivation of this population read a false positive.) + */ +const readSpecFieldTypes = (): readonly string[] => { + const options = enumOptions(FieldType); + if (options.length === 0) { + throw new Error('could not read FieldType.options from @objectstack/spec'); + } + return options; +}; + +const SPEC_FIELD_TYPES: readonly string[] = readSpecFieldTypes(); /** Every picker key `lookupExtras` copies off the resolved object field. */ const PICKER_KEYS = [ diff --git a/packages/app-shell/src/views/richtextSurfaceParity.test.tsx b/packages/app-shell/src/views/richtextSurfaceParity.test.tsx index 65b9b6b842..d87bf19113 100644 --- a/packages/app-shell/src/views/richtextSurfaceParity.test.tsx +++ b/packages/app-shell/src/views/richtextSurfaceParity.test.tsx @@ -27,11 +27,35 @@ */ import { describe, it, expect } from 'vitest'; import { FieldType } from '@objectstack/spec/data'; +import { enumOptions } from '@object-ui/test-support'; import { isWideFieldType as detailIsWide } from '@object-ui/plugin-detail'; import { isWideFieldType as formIsWide } from '@object-ui/plugin-form'; import { isSecondaryField } from './RecordDetailView'; -const SPEC_TYPES: readonly string[] = [...(FieldType as { options: readonly string[] }).options]; +/** + * The spec's own `FieldType` vocabulary. + * + * The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924); + * the THROW stays HERE, because the reader deliberately answers `[]` rather than + * raising and this read is module-scope. What it replaces was a NON-OPTIONAL + * cast of the enum node to an options-bearing shape, spread directly — which + * failed LOUDLY the moment the cast stopped holding (spreading `undefined` + * throws), so a bare `enumOptions` call here would have traded a loud failure + * for a silently empty vocabulary: the derived assertions below would then pass + * over nothing. That trade is the regression objectui#7025 exists to refuse. + * (The retired spelling is deliberately NOT quoted here: the card's enumeration + * instrument is a grep for it, and a comment carrying the literal text makes + * every future re-derivation of this population read a false positive.) + */ +const readSpecTypes = (): readonly string[] => { + const options = enumOptions(FieldType); + if (options.length === 0) { + throw new Error('could not read FieldType.options from @objectstack/spec'); + } + return options; +}; + +const SPEC_TYPES: readonly string[] = readSpecTypes(); /** * ONE fixture, read by all three rules. `body` is the field the card is about; diff --git a/packages/plugin-dashboard/src/__tests__/expandableFamily.identity-5692.test.ts b/packages/plugin-dashboard/src/__tests__/expandableFamily.identity-5692.test.ts index 08334294dc..ac44e58af3 100644 --- a/packages/plugin-dashboard/src/__tests__/expandableFamily.identity-5692.test.ts +++ b/packages/plugin-dashboard/src/__tests__/expandableFamily.identity-5692.test.ts @@ -71,12 +71,34 @@ import { fileURLToPath } from 'node:url'; import { describe, it, expect, vi } from 'vitest'; import { EXPANDABLE_FIELD_TYPES } from '@object-ui/core'; import { FieldType } from '@objectstack/spec/data'; +import { enumOptions } from '@object-ui/test-support'; import { isLookupType } from '../recordFields'; import { computeLookupExpand } from '../ObjectDataTable'; -const SPEC_FIELD_TYPES: readonly string[] = [ - ...(FieldType as unknown as { options: readonly string[] }).options, -]; +/** + * The spec's own `FieldType` vocabulary. + * + * The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924); + * the THROW stays HERE, because the reader deliberately answers `[]` rather than + * raising and this read is module-scope. What it replaces was a NON-OPTIONAL + * cast of the enum node to an options-bearing shape, spread directly — which + * failed LOUDLY the moment the cast stopped holding (spreading `undefined` + * throws), so a bare `enumOptions` call here would have traded a loud failure + * for a silently empty vocabulary: every assertion below would then pass over + * nothing. That trade is the regression objectui#7025 exists to refuse. (The + * retired spelling is deliberately NOT quoted here: the card's enumeration + * instrument is a grep for it, and a comment carrying the literal text makes + * every future re-derivation of this population read a false positive.) + */ +const readSpecFieldTypes = (): readonly string[] => { + const options = enumOptions(FieldType); + if (options.length === 0) { + throw new Error('could not read FieldType.options from @objectstack/spec'); + } + return options; +}; + +const SPEC_FIELD_TYPES: readonly string[] = readSpecFieldTypes(); /** The relations an ordinary dashboard table shows — the regression control. */ const ORDINARY_RELATIONS = ['lookup', 'master_detail', 'user'] as const; diff --git a/packages/plugin-detail/src/__tests__/autoLayout.wideSpelling.test.ts b/packages/plugin-detail/src/__tests__/autoLayout.wideSpelling.test.ts index 90dbc3d3f9..a14a8af6ed 100644 --- a/packages/plugin-detail/src/__tests__/autoLayout.wideSpelling.test.ts +++ b/packages/plugin-detail/src/__tests__/autoLayout.wideSpelling.test.ts @@ -20,10 +20,34 @@ */ import { describe, it, expect } from 'vitest'; import { FieldType } from '@objectstack/spec/data'; +import { enumOptions } from '@object-ui/test-support'; import { mapFieldTypeToFormType } from '@object-ui/fields'; import { isWideFieldType, applyAutoSpan } from '../autoLayout'; -const SPEC_TYPES: readonly string[] = [...(FieldType as { options: readonly string[] }).options]; +/** + * The spec's own `FieldType` vocabulary. + * + * The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924); + * the THROW stays HERE, because the reader deliberately answers `[]` rather than + * raising and this read is module-scope. What it replaces was a NON-OPTIONAL + * cast of the enum node to an options-bearing shape, spread directly — which + * failed LOUDLY the moment the cast stopped holding (spreading `undefined` + * throws), so a bare `enumOptions` call here would have traded a loud failure + * for a silently empty vocabulary: the derived assertions below would then pass + * over nothing. That trade is the regression objectui#7025 exists to refuse. + * (The retired spelling is deliberately NOT quoted here: the card's enumeration + * instrument is a grep for it, and a comment carrying the literal text makes + * every future re-derivation of this population read a false positive.) + */ +const readSpecTypes = (): readonly string[] => { + const options = enumOptions(FieldType); + if (options.length === 0) { + throw new Error('could not read FieldType.options from @objectstack/spec'); + } + return options; +}; + +const SPEC_TYPES: readonly string[] = readSpecTypes(); /** * The long-form / document family: the spec types whose value is a whole block diff --git a/packages/plugin-detail/src/__tests__/expandableFamily.identity-5874.test.tsx b/packages/plugin-detail/src/__tests__/expandableFamily.identity-5874.test.tsx index baee10d15d..7fc151c81d 100644 --- a/packages/plugin-detail/src/__tests__/expandableFamily.identity-5874.test.tsx +++ b/packages/plugin-detail/src/__tests__/expandableFamily.identity-5874.test.tsx @@ -66,12 +66,34 @@ import { render } from '@testing-library/react'; import React from 'react'; import { EXPANDABLE_FIELD_TYPES } from '@object-ui/core'; import { FieldType } from '@objectstack/spec/data'; +import { enumOptions } from '@object-ui/test-support'; import { HeaderHighlight } from '../HeaderHighlight'; import { RecordDetailDrawer } from '../RecordDetailDrawer'; -const SPEC_FIELD_TYPES: readonly string[] = [ - ...(FieldType as unknown as { options: readonly string[] }).options, -]; +/** + * The spec's own `FieldType` vocabulary. + * + * The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924); + * the THROW stays HERE, because the reader deliberately answers `[]` rather than + * raising and this read is module-scope. What it replaces was a NON-OPTIONAL + * cast of the enum node to an options-bearing shape, spread directly — which + * failed LOUDLY the moment the cast stopped holding (spreading `undefined` + * throws), so a bare `enumOptions` call here would have traded a loud failure + * for a silently empty vocabulary: every assertion below would then pass over + * nothing. That trade is the regression objectui#7025 exists to refuse. (The + * retired spelling is deliberately NOT quoted here: the card's enumeration + * instrument is a grep for it, and a comment carrying the literal text makes + * every future re-derivation of this population read a false positive.) + */ +const readSpecFieldTypes = (): readonly string[] => { + const options = enumOptions(FieldType); + if (options.length === 0) { + throw new Error('could not read FieldType.options from @objectstack/spec'); + } + return options; +}; + +const SPEC_FIELD_TYPES: readonly string[] = readSpecFieldTypes(); /** * The drawer hands its derived field list to `DetailView` as `schema.fields`. diff --git a/packages/plugin-form/package.json b/packages/plugin-form/package.json index c27f526da0..fe92c482ab 100644 --- a/packages/plugin-form/package.json +++ b/packages/plugin-form/package.json @@ -37,6 +37,7 @@ }, "devDependencies": { "@object-ui/data-objectstack": "workspace:*", + "@object-ui/test-support": "workspace:*", "@vitejs/plugin-react": "^6.0.5", "msw": "^2.15.0", "typescript": "^6.0.3", diff --git a/packages/plugin-form/src/__tests__/autoLayout.wideSpelling.test.ts b/packages/plugin-form/src/__tests__/autoLayout.wideSpelling.test.ts index 9dc6345e5a..40c7e9e8ed 100644 --- a/packages/plugin-form/src/__tests__/autoLayout.wideSpelling.test.ts +++ b/packages/plugin-form/src/__tests__/autoLayout.wideSpelling.test.ts @@ -19,11 +19,35 @@ */ import { describe, it, expect } from 'vitest'; import { FieldType } from '@objectstack/spec/data'; +import { enumOptions } from '@object-ui/test-support'; import { mapFieldTypeToFormType } from '@object-ui/fields'; import { isWideFieldType, resolveColSpan } from '../autoLayout'; import type { FormField } from '@object-ui/types'; -const SPEC_TYPES: readonly string[] = [...(FieldType as { options: readonly string[] }).options]; +/** + * The spec's own `FieldType` vocabulary. + * + * The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924); + * the THROW stays HERE, because the reader deliberately answers `[]` rather than + * raising and this read is module-scope. What it replaces was a NON-OPTIONAL + * cast of the enum node to an options-bearing shape, spread directly — which + * failed LOUDLY the moment the cast stopped holding (spreading `undefined` + * throws), so a bare `enumOptions` call here would have traded a loud failure + * for a silently empty vocabulary: the derived assertions below would then pass + * over nothing. That trade is the regression objectui#7025 exists to refuse. + * (The retired spelling is deliberately NOT quoted here: the card's enumeration + * instrument is a grep for it, and a comment carrying the literal text makes + * every future re-derivation of this population read a false positive.) + */ +const readSpecTypes = (): readonly string[] => { + const options = enumOptions(FieldType); + if (options.length === 0) { + throw new Error('could not read FieldType.options from @objectstack/spec'); + } + return options; +}; + +const SPEC_TYPES: readonly string[] = readSpecTypes(); /** The long-form / document family, spelled as the SPEC spells it. */ const WIDE_SPEC_TYPES = ['textarea', 'markdown', 'html', 'richtext'] as const; diff --git a/packages/plugin-grid/src/__tests__/spec-symbol-batch7.test.ts b/packages/plugin-grid/src/__tests__/spec-symbol-batch7.test.ts index 3dadc4d88c..e90d63f11b 100644 --- a/packages/plugin-grid/src/__tests__/spec-symbol-batch7.test.ts +++ b/packages/plugin-grid/src/__tests__/spec-symbol-batch7.test.ts @@ -36,15 +36,32 @@ import { MULTI_CAPABLE_TYPES, MULTI_OPTION_TYPES, } from '@objectstack/spec/data'; +import { enumOptions } from '@object-ui/test-support'; import { SUPPORTED_SUMMARY_TYPES, type ColumnSummarySetting, type ColumnSummaryType } from '../useColumnSummary'; import { hasMultiValueShape } from '../hooks/multiValueFields'; -const specSummaryOptions = (ColumnSummarySchema as unknown as { options: readonly string[] }).options; +/** + * The spec's column-summary vocabulary. + * + * The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924). + * No throw wrapper here — unlike the module-scope readers converted alongside it + * in objectui#7025, this suite already discharges the reader's non-vacuity duty + * with its OWN first assertion below, which is the sanctioned alternative. What + * it replaces was a NON-OPTIONAL cast of the enum node to an options-bearing + * shape; the loudness that cast provided is preserved by that assertion, not + * lost to `[]`. + */ +const specSummaryOptions: readonly string[] = enumOptions(ColumnSummarySchema); describe('the column-summary vocabulary comes from the spec', () => { it('reads a non-empty enum from the spec (the probe itself is not vacuous)', () => { - expect(Array.isArray(specSummaryOptions) && specSummaryOptions.length > 0).toBe(true); + // The reader answers `[]` when it cannot read the enum, so THIS is what + // keeps every assertion below from passing over an empty list. + expect( + specSummaryOptions, + 'could not read ColumnSummarySchema.options from the spec', + ).not.toEqual([]); }); it('every aggregation the spec accepts is one this renderer computes', () => { diff --git a/packages/plugin-kanban/package.json b/packages/plugin-kanban/package.json index 1093e732f2..37ef99ed8a 100644 --- a/packages/plugin-kanban/package.json +++ b/packages/plugin-kanban/package.json @@ -51,6 +51,7 @@ }, "devDependencies": { "@object-ui/data-objectstack": "workspace:*", + "@object-ui/test-support": "workspace:*", "@tailwindcss/postcss": "^4.3.3", "@types/react": "19.2.18", "@types/react-dom": "19.2.4", diff --git a/packages/plugin-kanban/src/__tests__/expandableFamily.identity-5874.test.tsx b/packages/plugin-kanban/src/__tests__/expandableFamily.identity-5874.test.tsx index 2a3e76dbf2..4c1dd2831f 100644 --- a/packages/plugin-kanban/src/__tests__/expandableFamily.identity-5874.test.tsx +++ b/packages/plugin-kanban/src/__tests__/expandableFamily.identity-5874.test.tsx @@ -63,6 +63,7 @@ import { render, waitFor } from '@testing-library/react'; import React from 'react'; import { EXPANDABLE_FIELD_TYPES } from '@object-ui/core'; import { FieldType } from '@objectstack/spec/data'; +import { enumOptions } from '@object-ui/test-support'; import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react'; // Registers `object-kanban`. import '../index'; @@ -73,9 +74,30 @@ import '../index'; // so ESM's module cache makes that factory resolve immediately. import '../KanbanImpl'; -const SPEC_FIELD_TYPES: readonly string[] = [ - ...(FieldType as unknown as { options: readonly string[] }).options, -]; +/** + * The spec's own `FieldType` vocabulary. + * + * The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924); + * the THROW stays HERE, because the reader deliberately answers `[]` rather than + * raising and this read is module-scope. What it replaces was a NON-OPTIONAL + * cast of the enum node to an options-bearing shape, spread directly — which + * failed LOUDLY the moment the cast stopped holding (spreading `undefined` + * throws), so a bare `enumOptions` call here would have traded a loud failure + * for a silently empty vocabulary: every assertion below would then pass over + * nothing. That trade is the regression objectui#7025 exists to refuse. (The + * retired spelling is deliberately NOT quoted here: the card's enumeration + * instrument is a grep for it, and a comment carrying the literal text makes + * every future re-derivation of this population read a false positive.) + */ +const readSpecFieldTypes = (): readonly string[] => { + const options = enumOptions(FieldType); + if (options.length === 0) { + throw new Error('could not read FieldType.options from @objectstack/spec'); + } + return options; +}; + +const SPEC_FIELD_TYPES: readonly string[] = readSpecFieldTypes(); /** * A board with NO `cardFields` and NO `highlightFields`, so cards fall to the diff --git a/packages/types/src/__tests__/spec-subschema-parity.test.ts b/packages/types/src/__tests__/spec-subschema-parity.test.ts index 2e408da708..900513ceba 100644 --- a/packages/types/src/__tests__/spec-subschema-parity.test.ts +++ b/packages/types/src/__tests__/spec-subschema-parity.test.ts @@ -39,6 +39,7 @@ import { ChartTypeSchema as SpecChartTypeSchema, PageTypeSchema as SpecPageTypeSchema, } from '@objectstack/spec/ui'; +import { enumOptions } from '@object-ui/test-support'; import { HttpMethodSchema, HttpRequestSchema, @@ -175,7 +176,12 @@ describe('ListColumnSchema is the spec schema (the extension collapsed)', () => // arm through `lazySchema`, whose Proxy resolves `.shape.type` to the inner // enum instead of the exported schema object, so a `toBe` check would test // the wrapper rather than the vocabulary it is meant to protect. - const vocabulary = (SpecColumnSummarySchema as unknown as { options: string[] }).options; + // The wrapper walk is `@object-ui/test-support`'s shared reader + // (objectui#6924). No throw wrapper: the assertion on the next line already + // discharges the reader's non-vacuity duty, which is what keeps the loop + // below from passing over an empty vocabulary now that a failed read + // answers `[]` instead of throwing (objectui#7025). + const vocabulary = enumOptions(SpecColumnSummarySchema); expect(vocabulary.length, 'spec ColumnSummarySchema should be a non-empty enum').toBeGreaterThan(0); for (const agg of vocabulary) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42014eb176..d1305d3191 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2043,6 +2043,9 @@ importers: '@object-ui/data-objectstack': specifier: workspace:* version: link:../data-objectstack + '@object-ui/test-support': + specifier: workspace:* + version: link:../test-support '@vitejs/plugin-react': specifier: ^6.0.5 version: 6.0.5(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) @@ -2244,6 +2247,9 @@ importers: '@object-ui/data-objectstack': specifier: workspace:* version: link:../data-objectstack + '@object-ui/test-support': + specifier: workspace:* + version: link:../test-support '@tailwindcss/postcss': specifier: ^4.3.3 version: 4.3.3