Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/olive-moons-shave.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
---
---

Internal test-support change, no user-visible behaviour (objectui#6924).

The 17 hand-written `(Schema as { options?: readonly string[] }).options` casts
across 16 spec-parity test files converge onto `@object-ui/test-support`'s
`enumOptions(node)` — a sibling entry point onto the wrapper walk
`shapeEnumOptions` already carried, which now delegates to it rather than
holding a second copy. Only test files, the private (never-published)
`@object-ui/test-support`, and `devDependencies` edges change; no package's
shipped `dist/` and no public type moves.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,6 +82,7 @@ import {
ToastSchema,
ToasterSchema,
} from '@object-ui/types/zod';
import { enumOptions } from '@object-ui/test-support';
import { allExamples, getExample } from '../src/index.js';

type Json = Record<string, unknown>;
Expand All@@ -94,14 +95,15 @@ const schemaOf = (id: string): Json => getExample(id).schema as unknown as Json;
* SHIPPED enum rather than to a hand-copied string list is the point: if the
* platform ever adds or drops a member, this file follows it instead of
* asserting yesterday's vocabulary.
*
* The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924);
* the THROW stays here, because that is this file's non-vacuity duty and the
* reader deliberately answers `[]` rather than raising.
*/
function enumOptionsOf(field: unknown): readonly string[] {
let node = field as { options?: readonly string[]; unwrap?: () => unknown };
for (let i = 0; i < 4 && node && !node.options; i += 1) {
node = node.unwrap?.() as typeof node;
}
if (!node?.options) throw new Error('not an enum-bearing field');
return node.options;
const options = enumOptions(field);
if (options.length === 0) throw new Error('not an enum-bearing field');
return options;
}

const TOAST_FIXTURES = [
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,12 +22,10 @@

import { describe, it, expect } from 'vitest';
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { COLOR_VARIANTS, colorVariantCss } from './color-variant-field';

const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The picker's canonical row — `ColorVariantPicker` renders exactly
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,12 @@ import {
PageHeaderProps,
RecordDetailsProps,
} from '@objectstack/spec/ui';
import { isShapeKeyTombstoned, listedShapeKeys, shapeMemberTypeName } from '@object-ui/test-support';
import {
enumOptions,
isShapeKeyTombstoned,
listedShapeKeys,
shapeMemberTypeName,
} from '@object-ui/test-support';
import { BLOCK_CONFIG, blockHasConfig, type PlaceholderSpec } from '../block-config';
import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';
import { t } from '../../i18n';
Expand DownExpand Up@@ -171,10 +176,7 @@ describe('record:details sections ↔ spec section-entry coverage (#3819)', () =
* offered, or excluded with a documented reason.
*/
describe('page palette ↔ spec PageComponentType coverage', () => {
const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import { ComponentRegistry } from '@object-ui/core';
import { PageComponentType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
// Side-effect import: registers `record:chatter` and `record:discussion`. The
// app-shell test setup does not pull plugin-detail in, and relying on another
// file having imported it first would make this suite order-dependent.
Expand All@@ -51,14 +52,20 @@ import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';

afterEach(cleanup);

const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

const meta = BLOCK_TYPE_META as Record<string, { label: string; category: string } | undefined>;

describe('page palette — the discussion/chatter pair points at the canonical name (#5495)', () => {
// The duty `@object-ui/test-support`'s reader leaves with every caller: `[]`
// is its "could not read", and the `not.toContain` probes below are satisfied
// by an empty list. The `toContain` probes would red on one — but they say
// "the spec dropped this member", which sends the next reader to the contract
// instead of to the reader. This one names the real cause (objectui#6924).
it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
});

it('offers `record:discussion`, labelled for authors as "Discussion"', () => {
expect(meta['record:discussion']).toBeDefined();
expect(meta['record:discussion']!.label).toBe('Discussion');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ import { describe, it, expect, vi } from 'vitest';
import React from 'react';
import '@testing-library/jest-dom';
import { NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { renderComponent } from './test-utils';
import { TOASTER_POSITIONS } from '../renderers/feedback/toaster';
// Registers the renderers at module scope, NOT inside a `beforeAll` — there the
Expand All@@ -39,8 +40,7 @@ vi.mock('../ui/sonner', () => ({
}));

describe('toaster covers the spec notification-position vocabulary', () => {
const rawOptions = (NotificationPositionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(NotificationPositionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read NotificationPositionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import React from 'react';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NotificationTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
NOTIFICATION_PRESENTATIONS,
NotificationProvider,
Expand All@@ -36,8 +37,7 @@ import { NotificationInline } from '../NotificationInline';
import { notificationActionVariant, notificationIcon, notificationSeverityStyle } from '../severity';

function specDisplayTypes(): string[] {
const raw = (NotificationTypeSchema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
return enumOptions(NotificationTypeSchema);
}

type Notify = (input: Omit<NotificationItem, 'id' | 'createdAt' | 'read'>) => string;
Expand Down
5 changes: 2 additions & 3 deletions packages/fields/src/FieldEditWidget.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@

import { describe, it, expect } from 'vitest';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import {
FORM_FIELD_TYPES,
INLINE_EXCLUDED_FIELD_TYPES,
Expand DownExpand Up@@ -108,9 +109,7 @@ describe('inline editor ↔ form widget parity', () => {
* a documented exclusion.
*/
describe('inline editor ↔ SPEC FieldType parity (#2942)', () => {
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);

it('reads a non-empty enum from the spec', () => {
expect(specTypes, 'could not read FieldType.options from the spec').not.toEqual([]);
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-charts/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,13 +29,11 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import AdvancedChartImpl from '../AdvancedChartImpl';
import { RENDERABLE, SINGLE_VALUE_CHART_TYPES, TABULAR_CHART_TYPES } from '../normalizeChartSchema';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('plugin-charts covers the spec chart-type vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-dashboard/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"devDependencies": {
"@object-ui/plugin-charts": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react-grid-layout": "^2.1.0",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,16 +36,14 @@ import { render, screen, cleanup } from '@testing-library/react';
// `@object-ui/types` (decision (a) of objectui#2561), so this is the supported
// way to reach it.
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { DatasetWidget } from '../DatasetWidget';
import { VARIANT_ICON_CLASSES, VARIANT_TEXT_CLASSES, metricAccentTextClass } from '../colorVariants';

afterEach(cleanup);

/** The spec's own token list, read at test time (see the parity block below). */
const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The metric card's markup with NO `colorVariant` declared, exactly as
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartAggregateFunctionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { PivotTable, PIVOT_AGGREGATIONS } from '../PivotTable';

const baseSchema = {
Expand All@@ -41,8 +42,7 @@ const baseSchema = {
};

describe('PivotTable covers the spec UI aggregation vocabulary', () => {
const rawOptions = (ChartAggregateFunctionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ChartAggregateFunctionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read ChartAggregateFunctionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
*/
import { describe, it, expect } from 'vitest';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
classifyWidgetType,
CHART_TYPE_ALIASES,
Expand All@@ -29,10 +30,7 @@ import {
TABLE_LIKE_TYPES,
} from '../widgetDispatch';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('widget dispatch covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,7 @@
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import { InlineEditProvider, useInlineEdit } from '@object-ui/react';
import type { DetailViewSection } from '@object-ui/types';
import { DetailSection } from '../DetailSection';
Expand DownExpand Up@@ -82,9 +83,7 @@ import { isComputedFieldType, isInlineExcludedDetailFieldType } from '../fieldEn
* a member of the spec enum, which is the card's own premise — so it leaves
* this universe entirely rather than moving between buckets.
*/
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);
const ALL_TYPES = [...new Set([...FORM_FIELD_TYPES, ...specTypes])].sort();

/** The hosts never even render an editor for these (the gates, not this switch). */
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-grid/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"devDependencies": {
"@object-ui/data-objectstack": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@tailwindcss/postcss": "^4.3.3",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,11 +14,11 @@
*/
import { describe, it, expect } from 'vitest';
import { ColumnSummarySchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { SUPPORTED_SUMMARY_TYPES } from '../useColumnSummary';

describe('useColumnSummary covers the spec summary vocabulary', () => {
const rawOptions = (ColumnSummarySchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ColumnSummarySchema);

it('reads a non-empty enum from the spec', () => {
// Guards the assertions below against silently passing on an empty list if
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-report/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/node": "^26.2.0",
"@types/react": "19.2.18",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,21 +33,16 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ChartTypeSchema, ReportType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
DatasetReportRenderer,
planReportChart,
resolveReportPresentation,
} from '../DatasetReportRenderer';

const specChartTypes: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specChartTypes: string[] = enumOptions(ChartTypeSchema);

const specReportTypes: string[] = (() => {
const raw = (ReportType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specReportTypes: string[] = enumOptions(ReportType);

describe('planReportChart covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
"typescript": "^6.0.3"
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import { NotificationTypeSchema, NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
useAnimation,
SUPPORTED_TRANSITION_PRESETS,
Expand All@@ -55,11 +56,6 @@ import {
SUPPORTED_NOTIFICATION_POSITIONS,
} from '../../context/NotificationContext';

function options(schema: unknown): string[] {
const raw = (schema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
}

function assertParity(specNames: string[], implemented: ReadonlySet<string>, what: string) {
expect(specNames, `could not read the ${what} enum from the spec`).not.toEqual([]);
expect(
Expand DownExpand Up@@ -89,11 +85,11 @@ describe('react hooks cover the spec animation/notification vocabularies', () =>
});

it('notification display types match NotificationTypeSchema both ways', () => {
assertParity(options(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
assertParity(enumOptions(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
});

it('notification positions match NotificationPositionSchema both ways', () => {
assertParity(options(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
assertParity(enumOptions(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
});

// `NotificationActionButton.variant` was the shadcn Button vocabulary
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/olive-moons-shave.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
---
---

Internal test-support change, no user-visible behaviour (objectui#6924).

The 17 hand-written `(Schema as { options?: readonly string[] }).options` casts
across 16 spec-parity test files converge onto `@object-ui/test-support`'s
`enumOptions(node)` — a sibling entry point onto the wrapper walk
`shapeEnumOptions` already carried, which now delegates to it rather than
holding a second copy. Only test files, the private (never-published)
`@object-ui/test-support`, and `devDependencies` edges change; no package's
shipped `dist/` and no public type moves.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,6 +82,7 @@ import {
ToastSchema,
ToasterSchema,
} from '@object-ui/types/zod';
import { enumOptions } from '@object-ui/test-support';
import { allExamples, getExample } from '../src/index.js';

type Json = Record<string, unknown>;
Expand All@@ -94,14 +95,15 @@ const schemaOf = (id: string): Json => getExample(id).schema as unknown as Json;
* SHIPPED enum rather than to a hand-copied string list is the point: if the
* platform ever adds or drops a member, this file follows it instead of
* asserting yesterday's vocabulary.
*
* The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924);
* the THROW stays here, because that is this file's non-vacuity duty and the
* reader deliberately answers `[]` rather than raising.
*/
function enumOptionsOf(field: unknown): readonly string[] {
let node = field as { options?: readonly string[]; unwrap?: () => unknown };
for (let i = 0; i < 4 && node && !node.options; i += 1) {
node = node.unwrap?.() as typeof node;
}
if (!node?.options) throw new Error('not an enum-bearing field');
return node.options;
const options = enumOptions(field);
if (options.length === 0) throw new Error('not an enum-bearing field');
return options;
}

const TOAST_FIXTURES = [
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,12 +22,10 @@

import { describe, it, expect } from 'vitest';
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { COLOR_VARIANTS, colorVariantCss } from './color-variant-field';

const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The picker's canonical row — `ColorVariantPicker` renders exactly
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,12 @@ import {
PageHeaderProps,
RecordDetailsProps,
} from '@objectstack/spec/ui';
import { isShapeKeyTombstoned, listedShapeKeys, shapeMemberTypeName } from '@object-ui/test-support';
import {
enumOptions,
isShapeKeyTombstoned,
listedShapeKeys,
shapeMemberTypeName,
} from '@object-ui/test-support';
import { BLOCK_CONFIG, blockHasConfig, type PlaceholderSpec } from '../block-config';
import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';
import { t } from '../../i18n';
Expand DownExpand Up@@ -171,10 +176,7 @@ describe('record:details sections ↔ spec section-entry coverage (#3819)', () =
* offered, or excluded with a documented reason.
*/
describe('page palette ↔ spec PageComponentType coverage', () => {
const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import { ComponentRegistry } from '@object-ui/core';
import { PageComponentType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
// Side-effect import: registers `record:chatter` and `record:discussion`. The
// app-shell test setup does not pull plugin-detail in, and relying on another
// file having imported it first would make this suite order-dependent.
Expand All@@ -51,14 +52,20 @@ import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';

afterEach(cleanup);

const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

const meta = BLOCK_TYPE_META as Record<string, { label: string; category: string } | undefined>;

describe('page palette — the discussion/chatter pair points at the canonical name (#5495)', () => {
// The duty `@object-ui/test-support`'s reader leaves with every caller: `[]`
// is its "could not read", and the `not.toContain` probes below are satisfied
// by an empty list. The `toContain` probes would red on one — but they say
// "the spec dropped this member", which sends the next reader to the contract
// instead of to the reader. This one names the real cause (objectui#6924).
it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
});

it('offers `record:discussion`, labelled for authors as "Discussion"', () => {
expect(meta['record:discussion']).toBeDefined();
expect(meta['record:discussion']!.label).toBe('Discussion');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ import { describe, it, expect, vi } from 'vitest';
import React from 'react';
import '@testing-library/jest-dom';
import { NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { renderComponent } from './test-utils';
import { TOASTER_POSITIONS } from '../renderers/feedback/toaster';
// Registers the renderers at module scope, NOT inside a `beforeAll` — there the
Expand All@@ -39,8 +40,7 @@ vi.mock('../ui/sonner', () => ({
}));

describe('toaster covers the spec notification-position vocabulary', () => {
const rawOptions = (NotificationPositionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(NotificationPositionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read NotificationPositionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import React from 'react';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NotificationTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
NOTIFICATION_PRESENTATIONS,
NotificationProvider,
Expand All@@ -36,8 +37,7 @@ import { NotificationInline } from '../NotificationInline';
import { notificationActionVariant, notificationIcon, notificationSeverityStyle } from '../severity';

function specDisplayTypes(): string[] {
const raw = (NotificationTypeSchema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
return enumOptions(NotificationTypeSchema);
}

type Notify = (input: Omit<NotificationItem, 'id' | 'createdAt' | 'read'>) => string;
Expand Down
5 changes: 2 additions & 3 deletions packages/fields/src/FieldEditWidget.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@

import { describe, it, expect } from 'vitest';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import {
FORM_FIELD_TYPES,
INLINE_EXCLUDED_FIELD_TYPES,
Expand DownExpand Up@@ -108,9 +109,7 @@ describe('inline editor ↔ form widget parity', () => {
* a documented exclusion.
*/
describe('inline editor ↔ SPEC FieldType parity (#2942)', () => {
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);

it('reads a non-empty enum from the spec', () => {
expect(specTypes, 'could not read FieldType.options from the spec').not.toEqual([]);
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-charts/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,13 +29,11 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import AdvancedChartImpl from '../AdvancedChartImpl';
import { RENDERABLE, SINGLE_VALUE_CHART_TYPES, TABULAR_CHART_TYPES } from '../normalizeChartSchema';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('plugin-charts covers the spec chart-type vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-dashboard/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"devDependencies": {
"@object-ui/plugin-charts": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react-grid-layout": "^2.1.0",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,16 +36,14 @@ import { render, screen, cleanup } from '@testing-library/react';
// `@object-ui/types` (decision (a) of objectui#2561), so this is the supported
// way to reach it.
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { DatasetWidget } from '../DatasetWidget';
import { VARIANT_ICON_CLASSES, VARIANT_TEXT_CLASSES, metricAccentTextClass } from '../colorVariants';

afterEach(cleanup);

/** The spec's own token list, read at test time (see the parity block below). */
const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The metric card's markup with NO `colorVariant` declared, exactly as
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartAggregateFunctionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { PivotTable, PIVOT_AGGREGATIONS } from '../PivotTable';

const baseSchema = {
Expand All@@ -41,8 +42,7 @@ const baseSchema = {
};

describe('PivotTable covers the spec UI aggregation vocabulary', () => {
const rawOptions = (ChartAggregateFunctionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ChartAggregateFunctionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read ChartAggregateFunctionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
*/
import { describe, it, expect } from 'vitest';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
classifyWidgetType,
CHART_TYPE_ALIASES,
Expand All@@ -29,10 +30,7 @@ import {
TABLE_LIKE_TYPES,
} from '../widgetDispatch';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('widget dispatch covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,7 @@
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import { InlineEditProvider, useInlineEdit } from '@object-ui/react';
import type { DetailViewSection } from '@object-ui/types';
import { DetailSection } from '../DetailSection';
Expand DownExpand Up@@ -82,9 +83,7 @@ import { isComputedFieldType, isInlineExcludedDetailFieldType } from '../fieldEn
* a member of the spec enum, which is the card's own premise — so it leaves
* this universe entirely rather than moving between buckets.
*/
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);
const ALL_TYPES = [...new Set([...FORM_FIELD_TYPES, ...specTypes])].sort();

/** The hosts never even render an editor for these (the gates, not this switch). */
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-grid/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"devDependencies": {
"@object-ui/data-objectstack": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@tailwindcss/postcss": "^4.3.3",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,11 +14,11 @@
*/
import { describe, it, expect } from 'vitest';
import { ColumnSummarySchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { SUPPORTED_SUMMARY_TYPES } from '../useColumnSummary';

describe('useColumnSummary covers the spec summary vocabulary', () => {
const rawOptions = (ColumnSummarySchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ColumnSummarySchema);

it('reads a non-empty enum from the spec', () => {
// Guards the assertions below against silently passing on an empty list if
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-report/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/node": "^26.2.0",
"@types/react": "19.2.18",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,21 +33,16 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ChartTypeSchema, ReportType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
DatasetReportRenderer,
planReportChart,
resolveReportPresentation,
} from '../DatasetReportRenderer';

const specChartTypes: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specChartTypes: string[] = enumOptions(ChartTypeSchema);

const specReportTypes: string[] = (() => {
const raw = (ReportType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specReportTypes: string[] = enumOptions(ReportType);

describe('planReportChart covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
"typescript": "^6.0.3"
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import { NotificationTypeSchema, NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
useAnimation,
SUPPORTED_TRANSITION_PRESETS,
Expand All@@ -55,11 +56,6 @@ import {
SUPPORTED_NOTIFICATION_POSITIONS,
} from '../../context/NotificationContext';

function options(schema: unknown): string[] {
const raw = (schema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
}

function assertParity(specNames: string[], implemented: ReadonlySet<string>, what: string) {
expect(specNames, `could not read the ${what} enum from the spec`).not.toEqual([]);
expect(
Expand DownExpand Up@@ -89,11 +85,11 @@ describe('react hooks cover the spec animation/notification vocabularies', () =>
});

it('notification display types match NotificationTypeSchema both ways', () => {
assertParity(options(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
assertParity(enumOptions(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
});

it('notification positions match NotificationPositionSchema both ways', () => {
assertParity(options(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
assertParity(enumOptions(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
});

// `NotificationActionButton.variant` was the shadcn Button vocabulary
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/olive-moons-shave.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
---
---

Internal test-support change, no user-visible behaviour (objectui#6924).

The 17 hand-written `(Schema as { options?: readonly string[] }).options` casts
across 16 spec-parity test files converge onto `@object-ui/test-support`'s
`enumOptions(node)` — a sibling entry point onto the wrapper walk
`shapeEnumOptions` already carried, which now delegates to it rather than
holding a second copy. Only test files, the private (never-published)
`@object-ui/test-support`, and `devDependencies` edges change; no package's
shipped `dist/` and no public type moves.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,6 +82,7 @@ import {
ToastSchema,
ToasterSchema,
} from '@object-ui/types/zod';
import { enumOptions } from '@object-ui/test-support';
import { allExamples, getExample } from '../src/index.js';

type Json = Record<string, unknown>;
Expand All@@ -94,14 +95,15 @@ const schemaOf = (id: string): Json => getExample(id).schema as unknown as Json;
* SHIPPED enum rather than to a hand-copied string list is the point: if the
* platform ever adds or drops a member, this file follows it instead of
* asserting yesterday's vocabulary.
*
* The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924);
* the THROW stays here, because that is this file's non-vacuity duty and the
* reader deliberately answers `[]` rather than raising.
*/
function enumOptionsOf(field: unknown): readonly string[] {
let node = field as { options?: readonly string[]; unwrap?: () => unknown };
for (let i = 0; i < 4 && node && !node.options; i += 1) {
node = node.unwrap?.() as typeof node;
}
if (!node?.options) throw new Error('not an enum-bearing field');
return node.options;
const options = enumOptions(field);
if (options.length === 0) throw new Error('not an enum-bearing field');
return options;
}

const TOAST_FIXTURES = [
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,12 +22,10 @@

import { describe, it, expect } from 'vitest';
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { COLOR_VARIANTS, colorVariantCss } from './color-variant-field';

const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The picker's canonical row — `ColorVariantPicker` renders exactly
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,12 @@ import {
PageHeaderProps,
RecordDetailsProps,
} from '@objectstack/spec/ui';
import { isShapeKeyTombstoned, listedShapeKeys, shapeMemberTypeName } from '@object-ui/test-support';
import {
enumOptions,
isShapeKeyTombstoned,
listedShapeKeys,
shapeMemberTypeName,
} from '@object-ui/test-support';
import { BLOCK_CONFIG, blockHasConfig, type PlaceholderSpec } from '../block-config';
import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';
import { t } from '../../i18n';
Expand DownExpand Up@@ -171,10 +176,7 @@ describe('record:details sections ↔ spec section-entry coverage (#3819)', () =
* offered, or excluded with a documented reason.
*/
describe('page palette ↔ spec PageComponentType coverage', () => {
const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import { ComponentRegistry } from '@object-ui/core';
import { PageComponentType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
// Side-effect import: registers `record:chatter` and `record:discussion`. The
// app-shell test setup does not pull plugin-detail in, and relying on another
// file having imported it first would make this suite order-dependent.
Expand All@@ -51,14 +52,20 @@ import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';

afterEach(cleanup);

const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

const meta = BLOCK_TYPE_META as Record<string, { label: string; category: string } | undefined>;

describe('page palette — the discussion/chatter pair points at the canonical name (#5495)', () => {
// The duty `@object-ui/test-support`'s reader leaves with every caller: `[]`
// is its "could not read", and the `not.toContain` probes below are satisfied
// by an empty list. The `toContain` probes would red on one — but they say
// "the spec dropped this member", which sends the next reader to the contract
// instead of to the reader. This one names the real cause (objectui#6924).
it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
});

it('offers `record:discussion`, labelled for authors as "Discussion"', () => {
expect(meta['record:discussion']).toBeDefined();
expect(meta['record:discussion']!.label).toBe('Discussion');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ import { describe, it, expect, vi } from 'vitest';
import React from 'react';
import '@testing-library/jest-dom';
import { NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { renderComponent } from './test-utils';
import { TOASTER_POSITIONS } from '../renderers/feedback/toaster';
// Registers the renderers at module scope, NOT inside a `beforeAll` — there the
Expand All@@ -39,8 +40,7 @@ vi.mock('../ui/sonner', () => ({
}));

describe('toaster covers the spec notification-position vocabulary', () => {
const rawOptions = (NotificationPositionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(NotificationPositionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read NotificationPositionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import React from 'react';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NotificationTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
NOTIFICATION_PRESENTATIONS,
NotificationProvider,
Expand All@@ -36,8 +37,7 @@ import { NotificationInline } from '../NotificationInline';
import { notificationActionVariant, notificationIcon, notificationSeverityStyle } from '../severity';

function specDisplayTypes(): string[] {
const raw = (NotificationTypeSchema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
return enumOptions(NotificationTypeSchema);
}

type Notify = (input: Omit<NotificationItem, 'id' | 'createdAt' | 'read'>) => string;
Expand Down
5 changes: 2 additions & 3 deletions packages/fields/src/FieldEditWidget.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@

import { describe, it, expect } from 'vitest';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import {
FORM_FIELD_TYPES,
INLINE_EXCLUDED_FIELD_TYPES,
Expand DownExpand Up@@ -108,9 +109,7 @@ describe('inline editor ↔ form widget parity', () => {
* a documented exclusion.
*/
describe('inline editor ↔ SPEC FieldType parity (#2942)', () => {
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);

it('reads a non-empty enum from the spec', () => {
expect(specTypes, 'could not read FieldType.options from the spec').not.toEqual([]);
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-charts/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,13 +29,11 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import AdvancedChartImpl from '../AdvancedChartImpl';
import { RENDERABLE, SINGLE_VALUE_CHART_TYPES, TABULAR_CHART_TYPES } from '../normalizeChartSchema';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('plugin-charts covers the spec chart-type vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-dashboard/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"devDependencies": {
"@object-ui/plugin-charts": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react-grid-layout": "^2.1.0",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,16 +36,14 @@ import { render, screen, cleanup } from '@testing-library/react';
// `@object-ui/types` (decision (a) of objectui#2561), so this is the supported
// way to reach it.
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { DatasetWidget } from '../DatasetWidget';
import { VARIANT_ICON_CLASSES, VARIANT_TEXT_CLASSES, metricAccentTextClass } from '../colorVariants';

afterEach(cleanup);

/** The spec's own token list, read at test time (see the parity block below). */
const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The metric card's markup with NO `colorVariant` declared, exactly as
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartAggregateFunctionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { PivotTable, PIVOT_AGGREGATIONS } from '../PivotTable';

const baseSchema = {
Expand All@@ -41,8 +42,7 @@ const baseSchema = {
};

describe('PivotTable covers the spec UI aggregation vocabulary', () => {
const rawOptions = (ChartAggregateFunctionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ChartAggregateFunctionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read ChartAggregateFunctionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
*/
import { describe, it, expect } from 'vitest';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
classifyWidgetType,
CHART_TYPE_ALIASES,
Expand All@@ -29,10 +30,7 @@ import {
TABLE_LIKE_TYPES,
} from '../widgetDispatch';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('widget dispatch covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,7 @@
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import { InlineEditProvider, useInlineEdit } from '@object-ui/react';
import type { DetailViewSection } from '@object-ui/types';
import { DetailSection } from '../DetailSection';
Expand DownExpand Up@@ -82,9 +83,7 @@ import { isComputedFieldType, isInlineExcludedDetailFieldType } from '../fieldEn
* a member of the spec enum, which is the card's own premise — so it leaves
* this universe entirely rather than moving between buckets.
*/
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);
const ALL_TYPES = [...new Set([...FORM_FIELD_TYPES, ...specTypes])].sort();

/** The hosts never even render an editor for these (the gates, not this switch). */
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-grid/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"devDependencies": {
"@object-ui/data-objectstack": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@tailwindcss/postcss": "^4.3.3",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,11 +14,11 @@
*/
import { describe, it, expect } from 'vitest';
import { ColumnSummarySchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { SUPPORTED_SUMMARY_TYPES } from '../useColumnSummary';

describe('useColumnSummary covers the spec summary vocabulary', () => {
const rawOptions = (ColumnSummarySchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ColumnSummarySchema);

it('reads a non-empty enum from the spec', () => {
// Guards the assertions below against silently passing on an empty list if
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-report/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/node": "^26.2.0",
"@types/react": "19.2.18",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,21 +33,16 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ChartTypeSchema, ReportType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
DatasetReportRenderer,
planReportChart,
resolveReportPresentation,
} from '../DatasetReportRenderer';

const specChartTypes: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specChartTypes: string[] = enumOptions(ChartTypeSchema);

const specReportTypes: string[] = (() => {
const raw = (ReportType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specReportTypes: string[] = enumOptions(ReportType);

describe('planReportChart covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
"typescript": "^6.0.3"
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import { NotificationTypeSchema, NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
useAnimation,
SUPPORTED_TRANSITION_PRESETS,
Expand All@@ -55,11 +56,6 @@ import {
SUPPORTED_NOTIFICATION_POSITIONS,
} from '../../context/NotificationContext';

function options(schema: unknown): string[] {
const raw = (schema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
}

function assertParity(specNames: string[], implemented: ReadonlySet<string>, what: string) {
expect(specNames, `could not read the ${what} enum from the spec`).not.toEqual([]);
expect(
Expand DownExpand Up@@ -89,11 +85,11 @@ describe('react hooks cover the spec animation/notification vocabularies', () =>
});

it('notification display types match NotificationTypeSchema both ways', () => {
assertParity(options(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
assertParity(enumOptions(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
});

it('notification positions match NotificationPositionSchema both ways', () => {
assertParity(options(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
assertParity(enumOptions(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
});

// `NotificationActionButton.variant` was the shadcn Button vocabulary
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/olive-moons-shave.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
---
---

Internal test-support change, no user-visible behaviour (objectui#6924).

The 17 hand-written `(Schema as { options?: readonly string[] }).options` casts
across 16 spec-parity test files converge onto `@object-ui/test-support`'s
`enumOptions(node)` — a sibling entry point onto the wrapper walk
`shapeEnumOptions` already carried, which now delegates to it rather than
holding a second copy. Only test files, the private (never-published)
`@object-ui/test-support`, and `devDependencies` edges change; no package's
shipped `dist/` and no public type moves.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,6 +82,7 @@ import {
ToastSchema,
ToasterSchema,
} from '@object-ui/types/zod';
import { enumOptions } from '@object-ui/test-support';
import { allExamples, getExample } from '../src/index.js';

type Json = Record<string, unknown>;
Expand All@@ -94,14 +95,15 @@ const schemaOf = (id: string): Json => getExample(id).schema as unknown as Json;
* SHIPPED enum rather than to a hand-copied string list is the point: if the
* platform ever adds or drops a member, this file follows it instead of
* asserting yesterday's vocabulary.
*
* The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924);
* the THROW stays here, because that is this file's non-vacuity duty and the
* reader deliberately answers `[]` rather than raising.
*/
function enumOptionsOf(field: unknown): readonly string[] {
let node = field as { options?: readonly string[]; unwrap?: () => unknown };
for (let i = 0; i < 4 && node && !node.options; i += 1) {
node = node.unwrap?.() as typeof node;
}
if (!node?.options) throw new Error('not an enum-bearing field');
return node.options;
const options = enumOptions(field);
if (options.length === 0) throw new Error('not an enum-bearing field');
return options;
}

const TOAST_FIXTURES = [
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,12 +22,10 @@

import { describe, it, expect } from 'vitest';
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { COLOR_VARIANTS, colorVariantCss } from './color-variant-field';

const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The picker's canonical row — `ColorVariantPicker` renders exactly
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,12 @@ import {
PageHeaderProps,
RecordDetailsProps,
} from '@objectstack/spec/ui';
import { isShapeKeyTombstoned, listedShapeKeys, shapeMemberTypeName } from '@object-ui/test-support';
import {
enumOptions,
isShapeKeyTombstoned,
listedShapeKeys,
shapeMemberTypeName,
} from '@object-ui/test-support';
import { BLOCK_CONFIG, blockHasConfig, type PlaceholderSpec } from '../block-config';
import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';
import { t } from '../../i18n';
Expand DownExpand Up@@ -171,10 +176,7 @@ describe('record:details sections ↔ spec section-entry coverage (#3819)', () =
* offered, or excluded with a documented reason.
*/
describe('page palette ↔ spec PageComponentType coverage', () => {
const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import { ComponentRegistry } from '@object-ui/core';
import { PageComponentType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
// Side-effect import: registers `record:chatter` and `record:discussion`. The
// app-shell test setup does not pull plugin-detail in, and relying on another
// file having imported it first would make this suite order-dependent.
Expand All@@ -51,14 +52,20 @@ import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';

afterEach(cleanup);

const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

const meta = BLOCK_TYPE_META as Record<string, { label: string; category: string } | undefined>;

describe('page palette — the discussion/chatter pair points at the canonical name (#5495)', () => {
// The duty `@object-ui/test-support`'s reader leaves with every caller: `[]`
// is its "could not read", and the `not.toContain` probes below are satisfied
// by an empty list. The `toContain` probes would red on one — but they say
// "the spec dropped this member", which sends the next reader to the contract
// instead of to the reader. This one names the real cause (objectui#6924).
it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
});

it('offers `record:discussion`, labelled for authors as "Discussion"', () => {
expect(meta['record:discussion']).toBeDefined();
expect(meta['record:discussion']!.label).toBe('Discussion');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ import { describe, it, expect, vi } from 'vitest';
import React from 'react';
import '@testing-library/jest-dom';
import { NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { renderComponent } from './test-utils';
import { TOASTER_POSITIONS } from '../renderers/feedback/toaster';
// Registers the renderers at module scope, NOT inside a `beforeAll` — there the
Expand All@@ -39,8 +40,7 @@ vi.mock('../ui/sonner', () => ({
}));

describe('toaster covers the spec notification-position vocabulary', () => {
const rawOptions = (NotificationPositionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(NotificationPositionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read NotificationPositionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import React from 'react';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NotificationTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
NOTIFICATION_PRESENTATIONS,
NotificationProvider,
Expand All@@ -36,8 +37,7 @@ import { NotificationInline } from '../NotificationInline';
import { notificationActionVariant, notificationIcon, notificationSeverityStyle } from '../severity';

function specDisplayTypes(): string[] {
const raw = (NotificationTypeSchema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
return enumOptions(NotificationTypeSchema);
}

type Notify = (input: Omit<NotificationItem, 'id' | 'createdAt' | 'read'>) => string;
Expand Down
5 changes: 2 additions & 3 deletions packages/fields/src/FieldEditWidget.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@

import { describe, it, expect } from 'vitest';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import {
FORM_FIELD_TYPES,
INLINE_EXCLUDED_FIELD_TYPES,
Expand DownExpand Up@@ -108,9 +109,7 @@ describe('inline editor ↔ form widget parity', () => {
* a documented exclusion.
*/
describe('inline editor ↔ SPEC FieldType parity (#2942)', () => {
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);

it('reads a non-empty enum from the spec', () => {
expect(specTypes, 'could not read FieldType.options from the spec').not.toEqual([]);
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-charts/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,13 +29,11 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import AdvancedChartImpl from '../AdvancedChartImpl';
import { RENDERABLE, SINGLE_VALUE_CHART_TYPES, TABULAR_CHART_TYPES } from '../normalizeChartSchema';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('plugin-charts covers the spec chart-type vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-dashboard/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"devDependencies": {
"@object-ui/plugin-charts": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react-grid-layout": "^2.1.0",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,16 +36,14 @@ import { render, screen, cleanup } from '@testing-library/react';
// `@object-ui/types` (decision (a) of objectui#2561), so this is the supported
// way to reach it.
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { DatasetWidget } from '../DatasetWidget';
import { VARIANT_ICON_CLASSES, VARIANT_TEXT_CLASSES, metricAccentTextClass } from '../colorVariants';

afterEach(cleanup);

/** The spec's own token list, read at test time (see the parity block below). */
const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The metric card's markup with NO `colorVariant` declared, exactly as
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartAggregateFunctionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { PivotTable, PIVOT_AGGREGATIONS } from '../PivotTable';

const baseSchema = {
Expand All@@ -41,8 +42,7 @@ const baseSchema = {
};

describe('PivotTable covers the spec UI aggregation vocabulary', () => {
const rawOptions = (ChartAggregateFunctionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ChartAggregateFunctionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read ChartAggregateFunctionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
*/
import { describe, it, expect } from 'vitest';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
classifyWidgetType,
CHART_TYPE_ALIASES,
Expand All@@ -29,10 +30,7 @@ import {
TABLE_LIKE_TYPES,
} from '../widgetDispatch';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('widget dispatch covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,7 @@
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import { InlineEditProvider, useInlineEdit } from '@object-ui/react';
import type { DetailViewSection } from '@object-ui/types';
import { DetailSection } from '../DetailSection';
Expand DownExpand Up@@ -82,9 +83,7 @@ import { isComputedFieldType, isInlineExcludedDetailFieldType } from '../fieldEn
* a member of the spec enum, which is the card's own premise — so it leaves
* this universe entirely rather than moving between buckets.
*/
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);
const ALL_TYPES = [...new Set([...FORM_FIELD_TYPES, ...specTypes])].sort();

/** The hosts never even render an editor for these (the gates, not this switch). */
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-grid/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"devDependencies": {
"@object-ui/data-objectstack": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@tailwindcss/postcss": "^4.3.3",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,11 +14,11 @@
*/
import { describe, it, expect } from 'vitest';
import { ColumnSummarySchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { SUPPORTED_SUMMARY_TYPES } from '../useColumnSummary';

describe('useColumnSummary covers the spec summary vocabulary', () => {
const rawOptions = (ColumnSummarySchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ColumnSummarySchema);

it('reads a non-empty enum from the spec', () => {
// Guards the assertions below against silently passing on an empty list if
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-report/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/node": "^26.2.0",
"@types/react": "19.2.18",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,21 +33,16 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ChartTypeSchema, ReportType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
DatasetReportRenderer,
planReportChart,
resolveReportPresentation,
} from '../DatasetReportRenderer';

const specChartTypes: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specChartTypes: string[] = enumOptions(ChartTypeSchema);

const specReportTypes: string[] = (() => {
const raw = (ReportType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specReportTypes: string[] = enumOptions(ReportType);

describe('planReportChart covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
"typescript": "^6.0.3"
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import { NotificationTypeSchema, NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
useAnimation,
SUPPORTED_TRANSITION_PRESETS,
Expand All@@ -55,11 +56,6 @@ import {
SUPPORTED_NOTIFICATION_POSITIONS,
} from '../../context/NotificationContext';

function options(schema: unknown): string[] {
const raw = (schema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
}

function assertParity(specNames: string[], implemented: ReadonlySet<string>, what: string) {
expect(specNames, `could not read the ${what} enum from the spec`).not.toEqual([]);
expect(
Expand DownExpand Up@@ -89,11 +85,11 @@ describe('react hooks cover the spec animation/notification vocabularies', () =>
});

it('notification display types match NotificationTypeSchema both ways', () => {
assertParity(options(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
assertParity(enumOptions(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
});

it('notification positions match NotificationPositionSchema both ways', () => {
assertParity(options(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
assertParity(enumOptions(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
});

// `NotificationActionButton.variant` was the shadcn Button vocabulary
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/olive-moons-shave.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
---
---

Internal test-support change, no user-visible behaviour (objectui#6924).

The 17 hand-written `(Schema as { options?: readonly string[] }).options` casts
across 16 spec-parity test files converge onto `@object-ui/test-support`'s
`enumOptions(node)` — a sibling entry point onto the wrapper walk
`shapeEnumOptions` already carried, which now delegates to it rather than
holding a second copy. Only test files, the private (never-published)
`@object-ui/test-support`, and `devDependencies` edges change; no package's
shipped `dist/` and no public type moves.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,6 +82,7 @@ import {
ToastSchema,
ToasterSchema,
} from '@object-ui/types/zod';
import { enumOptions } from '@object-ui/test-support';
import { allExamples, getExample } from '../src/index.js';

type Json = Record<string, unknown>;
Expand All@@ -94,14 +95,15 @@ const schemaOf = (id: string): Json => getExample(id).schema as unknown as Json;
* SHIPPED enum rather than to a hand-copied string list is the point: if the
* platform ever adds or drops a member, this file follows it instead of
* asserting yesterday's vocabulary.
*
* The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924);
* the THROW stays here, because that is this file's non-vacuity duty and the
* reader deliberately answers `[]` rather than raising.
*/
function enumOptionsOf(field: unknown): readonly string[] {
let node = field as { options?: readonly string[]; unwrap?: () => unknown };
for (let i = 0; i < 4 && node && !node.options; i += 1) {
node = node.unwrap?.() as typeof node;
}
if (!node?.options) throw new Error('not an enum-bearing field');
return node.options;
const options = enumOptions(field);
if (options.length === 0) throw new Error('not an enum-bearing field');
return options;
}

const TOAST_FIXTURES = [
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,12 +22,10 @@

import { describe, it, expect } from 'vitest';
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { COLOR_VARIANTS, colorVariantCss } from './color-variant-field';

const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The picker's canonical row — `ColorVariantPicker` renders exactly
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,12 @@ import {
PageHeaderProps,
RecordDetailsProps,
} from '@objectstack/spec/ui';
import { isShapeKeyTombstoned, listedShapeKeys, shapeMemberTypeName } from '@object-ui/test-support';
import {
enumOptions,
isShapeKeyTombstoned,
listedShapeKeys,
shapeMemberTypeName,
} from '@object-ui/test-support';
import { BLOCK_CONFIG, blockHasConfig, type PlaceholderSpec } from '../block-config';
import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';
import { t } from '../../i18n';
Expand DownExpand Up@@ -171,10 +176,7 @@ describe('record:details sections ↔ spec section-entry coverage (#3819)', () =
* offered, or excluded with a documented reason.
*/
describe('page palette ↔ spec PageComponentType coverage', () => {
const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import { ComponentRegistry } from '@object-ui/core';
import { PageComponentType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
// Side-effect import: registers `record:chatter` and `record:discussion`. The
// app-shell test setup does not pull plugin-detail in, and relying on another
// file having imported it first would make this suite order-dependent.
Expand All@@ -51,14 +52,20 @@ import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';

afterEach(cleanup);

const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

const meta = BLOCK_TYPE_META as Record<string, { label: string; category: string } | undefined>;

describe('page palette — the discussion/chatter pair points at the canonical name (#5495)', () => {
// The duty `@object-ui/test-support`'s reader leaves with every caller: `[]`
// is its "could not read", and the `not.toContain` probes below are satisfied
// by an empty list. The `toContain` probes would red on one — but they say
// "the spec dropped this member", which sends the next reader to the contract
// instead of to the reader. This one names the real cause (objectui#6924).
it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
});

it('offers `record:discussion`, labelled for authors as "Discussion"', () => {
expect(meta['record:discussion']).toBeDefined();
expect(meta['record:discussion']!.label).toBe('Discussion');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ import { describe, it, expect, vi } from 'vitest';
import React from 'react';
import '@testing-library/jest-dom';
import { NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { renderComponent } from './test-utils';
import { TOASTER_POSITIONS } from '../renderers/feedback/toaster';
// Registers the renderers at module scope, NOT inside a `beforeAll` — there the
Expand All@@ -39,8 +40,7 @@ vi.mock('../ui/sonner', () => ({
}));

describe('toaster covers the spec notification-position vocabulary', () => {
const rawOptions = (NotificationPositionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(NotificationPositionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read NotificationPositionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import React from 'react';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NotificationTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
NOTIFICATION_PRESENTATIONS,
NotificationProvider,
Expand All@@ -36,8 +37,7 @@ import { NotificationInline } from '../NotificationInline';
import { notificationActionVariant, notificationIcon, notificationSeverityStyle } from '../severity';

function specDisplayTypes(): string[] {
const raw = (NotificationTypeSchema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
return enumOptions(NotificationTypeSchema);
}

type Notify = (input: Omit<NotificationItem, 'id' | 'createdAt' | 'read'>) => string;
Expand Down
5 changes: 2 additions & 3 deletions packages/fields/src/FieldEditWidget.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@

import { describe, it, expect } from 'vitest';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import {
FORM_FIELD_TYPES,
INLINE_EXCLUDED_FIELD_TYPES,
Expand DownExpand Up@@ -108,9 +109,7 @@ describe('inline editor ↔ form widget parity', () => {
* a documented exclusion.
*/
describe('inline editor ↔ SPEC FieldType parity (#2942)', () => {
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);

it('reads a non-empty enum from the spec', () => {
expect(specTypes, 'could not read FieldType.options from the spec').not.toEqual([]);
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-charts/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,13 +29,11 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import AdvancedChartImpl from '../AdvancedChartImpl';
import { RENDERABLE, SINGLE_VALUE_CHART_TYPES, TABULAR_CHART_TYPES } from '../normalizeChartSchema';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('plugin-charts covers the spec chart-type vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-dashboard/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"devDependencies": {
"@object-ui/plugin-charts": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react-grid-layout": "^2.1.0",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,16 +36,14 @@ import { render, screen, cleanup } from '@testing-library/react';
// `@object-ui/types` (decision (a) of objectui#2561), so this is the supported
// way to reach it.
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { DatasetWidget } from '../DatasetWidget';
import { VARIANT_ICON_CLASSES, VARIANT_TEXT_CLASSES, metricAccentTextClass } from '../colorVariants';

afterEach(cleanup);

/** The spec's own token list, read at test time (see the parity block below). */
const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The metric card's markup with NO `colorVariant` declared, exactly as
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartAggregateFunctionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { PivotTable, PIVOT_AGGREGATIONS } from '../PivotTable';

const baseSchema = {
Expand All@@ -41,8 +42,7 @@ const baseSchema = {
};

describe('PivotTable covers the spec UI aggregation vocabulary', () => {
const rawOptions = (ChartAggregateFunctionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ChartAggregateFunctionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read ChartAggregateFunctionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
*/
import { describe, it, expect } from 'vitest';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
classifyWidgetType,
CHART_TYPE_ALIASES,
Expand All@@ -29,10 +30,7 @@ import {
TABLE_LIKE_TYPES,
} from '../widgetDispatch';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('widget dispatch covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,7 @@
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import { InlineEditProvider, useInlineEdit } from '@object-ui/react';
import type { DetailViewSection } from '@object-ui/types';
import { DetailSection } from '../DetailSection';
Expand DownExpand Up@@ -82,9 +83,7 @@ import { isComputedFieldType, isInlineExcludedDetailFieldType } from '../fieldEn
* a member of the spec enum, which is the card's own premise — so it leaves
* this universe entirely rather than moving between buckets.
*/
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);
const ALL_TYPES = [...new Set([...FORM_FIELD_TYPES, ...specTypes])].sort();

/** The hosts never even render an editor for these (the gates, not this switch). */
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-grid/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"devDependencies": {
"@object-ui/data-objectstack": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@tailwindcss/postcss": "^4.3.3",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,11 +14,11 @@
*/
import { describe, it, expect } from 'vitest';
import { ColumnSummarySchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { SUPPORTED_SUMMARY_TYPES } from '../useColumnSummary';

describe('useColumnSummary covers the spec summary vocabulary', () => {
const rawOptions = (ColumnSummarySchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ColumnSummarySchema);

it('reads a non-empty enum from the spec', () => {
// Guards the assertions below against silently passing on an empty list if
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-report/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/node": "^26.2.0",
"@types/react": "19.2.18",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,21 +33,16 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ChartTypeSchema, ReportType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
DatasetReportRenderer,
planReportChart,
resolveReportPresentation,
} from '../DatasetReportRenderer';

const specChartTypes: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specChartTypes: string[] = enumOptions(ChartTypeSchema);

const specReportTypes: string[] = (() => {
const raw = (ReportType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specReportTypes: string[] = enumOptions(ReportType);

describe('planReportChart covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
"typescript": "^6.0.3"
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import { NotificationTypeSchema, NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
useAnimation,
SUPPORTED_TRANSITION_PRESETS,
Expand All@@ -55,11 +56,6 @@ import {
SUPPORTED_NOTIFICATION_POSITIONS,
} from '../../context/NotificationContext';

function options(schema: unknown): string[] {
const raw = (schema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
}

function assertParity(specNames: string[], implemented: ReadonlySet<string>, what: string) {
expect(specNames, `could not read the ${what} enum from the spec`).not.toEqual([]);
expect(
Expand DownExpand Up@@ -89,11 +85,11 @@ describe('react hooks cover the spec animation/notification vocabularies', () =>
});

it('notification display types match NotificationTypeSchema both ways', () => {
assertParity(options(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
assertParity(enumOptions(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
});

it('notification positions match NotificationPositionSchema both ways', () => {
assertParity(options(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
assertParity(enumOptions(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
});

// `NotificationActionButton.variant` was the shadcn Button vocabulary
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/olive-moons-shave.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
---
---

Internal test-support change, no user-visible behaviour (objectui#6924).

The 17 hand-written `(Schema as { options?: readonly string[] }).options` casts
across 16 spec-parity test files converge onto `@object-ui/test-support`'s
`enumOptions(node)` — a sibling entry point onto the wrapper walk
`shapeEnumOptions` already carried, which now delegates to it rather than
holding a second copy. Only test files, the private (never-published)
`@object-ui/test-support`, and `devDependencies` edges change; no package's
shipped `dist/` and no public type moves.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,6 +82,7 @@ import {
ToastSchema,
ToasterSchema,
} from '@object-ui/types/zod';
import { enumOptions } from '@object-ui/test-support';
import { allExamples, getExample } from '../src/index.js';

type Json = Record<string, unknown>;
Expand All@@ -94,14 +95,15 @@ const schemaOf = (id: string): Json => getExample(id).schema as unknown as Json;
* SHIPPED enum rather than to a hand-copied string list is the point: if the
* platform ever adds or drops a member, this file follows it instead of
* asserting yesterday's vocabulary.
*
* The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924);
* the THROW stays here, because that is this file's non-vacuity duty and the
* reader deliberately answers `[]` rather than raising.
*/
function enumOptionsOf(field: unknown): readonly string[] {
let node = field as { options?: readonly string[]; unwrap?: () => unknown };
for (let i = 0; i < 4 && node && !node.options; i += 1) {
node = node.unwrap?.() as typeof node;
}
if (!node?.options) throw new Error('not an enum-bearing field');
return node.options;
const options = enumOptions(field);
if (options.length === 0) throw new Error('not an enum-bearing field');
return options;
}

const TOAST_FIXTURES = [
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,12 +22,10 @@

import { describe, it, expect } from 'vitest';
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { COLOR_VARIANTS, colorVariantCss } from './color-variant-field';

const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The picker's canonical row — `ColorVariantPicker` renders exactly
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,12 @@ import {
PageHeaderProps,
RecordDetailsProps,
} from '@objectstack/spec/ui';
import { isShapeKeyTombstoned, listedShapeKeys, shapeMemberTypeName } from '@object-ui/test-support';
import {
enumOptions,
isShapeKeyTombstoned,
listedShapeKeys,
shapeMemberTypeName,
} from '@object-ui/test-support';
import { BLOCK_CONFIG, blockHasConfig, type PlaceholderSpec } from '../block-config';
import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';
import { t } from '../../i18n';
Expand DownExpand Up@@ -171,10 +176,7 @@ describe('record:details sections ↔ spec section-entry coverage (#3819)', () =
* offered, or excluded with a documented reason.
*/
describe('page palette ↔ spec PageComponentType coverage', () => {
const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import { ComponentRegistry } from '@object-ui/core';
import { PageComponentType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
// Side-effect import: registers `record:chatter` and `record:discussion`. The
// app-shell test setup does not pull plugin-detail in, and relying on another
// file having imported it first would make this suite order-dependent.
Expand All@@ -51,14 +52,20 @@ import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';

afterEach(cleanup);

const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

const meta = BLOCK_TYPE_META as Record<string, { label: string; category: string } | undefined>;

describe('page palette — the discussion/chatter pair points at the canonical name (#5495)', () => {
// The duty `@object-ui/test-support`'s reader leaves with every caller: `[]`
// is its "could not read", and the `not.toContain` probes below are satisfied
// by an empty list. The `toContain` probes would red on one — but they say
// "the spec dropped this member", which sends the next reader to the contract
// instead of to the reader. This one names the real cause (objectui#6924).
it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
});

it('offers `record:discussion`, labelled for authors as "Discussion"', () => {
expect(meta['record:discussion']).toBeDefined();
expect(meta['record:discussion']!.label).toBe('Discussion');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ import { describe, it, expect, vi } from 'vitest';
import React from 'react';
import '@testing-library/jest-dom';
import { NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { renderComponent } from './test-utils';
import { TOASTER_POSITIONS } from '../renderers/feedback/toaster';
// Registers the renderers at module scope, NOT inside a `beforeAll` — there the
Expand All@@ -39,8 +40,7 @@ vi.mock('../ui/sonner', () => ({
}));

describe('toaster covers the spec notification-position vocabulary', () => {
const rawOptions = (NotificationPositionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(NotificationPositionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read NotificationPositionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import React from 'react';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NotificationTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
NOTIFICATION_PRESENTATIONS,
NotificationProvider,
Expand All@@ -36,8 +37,7 @@ import { NotificationInline } from '../NotificationInline';
import { notificationActionVariant, notificationIcon, notificationSeverityStyle } from '../severity';

function specDisplayTypes(): string[] {
const raw = (NotificationTypeSchema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
return enumOptions(NotificationTypeSchema);
}

type Notify = (input: Omit<NotificationItem, 'id' | 'createdAt' | 'read'>) => string;
Expand Down
5 changes: 2 additions & 3 deletions packages/fields/src/FieldEditWidget.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@

import { describe, it, expect } from 'vitest';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import {
FORM_FIELD_TYPES,
INLINE_EXCLUDED_FIELD_TYPES,
Expand DownExpand Up@@ -108,9 +109,7 @@ describe('inline editor ↔ form widget parity', () => {
* a documented exclusion.
*/
describe('inline editor ↔ SPEC FieldType parity (#2942)', () => {
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);

it('reads a non-empty enum from the spec', () => {
expect(specTypes, 'could not read FieldType.options from the spec').not.toEqual([]);
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-charts/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,13 +29,11 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import AdvancedChartImpl from '../AdvancedChartImpl';
import { RENDERABLE, SINGLE_VALUE_CHART_TYPES, TABULAR_CHART_TYPES } from '../normalizeChartSchema';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('plugin-charts covers the spec chart-type vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-dashboard/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"devDependencies": {
"@object-ui/plugin-charts": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react-grid-layout": "^2.1.0",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,16 +36,14 @@ import { render, screen, cleanup } from '@testing-library/react';
// `@object-ui/types` (decision (a) of objectui#2561), so this is the supported
// way to reach it.
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { DatasetWidget } from '../DatasetWidget';
import { VARIANT_ICON_CLASSES, VARIANT_TEXT_CLASSES, metricAccentTextClass } from '../colorVariants';

afterEach(cleanup);

/** The spec's own token list, read at test time (see the parity block below). */
const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The metric card's markup with NO `colorVariant` declared, exactly as
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartAggregateFunctionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { PivotTable, PIVOT_AGGREGATIONS } from '../PivotTable';

const baseSchema = {
Expand All@@ -41,8 +42,7 @@ const baseSchema = {
};

describe('PivotTable covers the spec UI aggregation vocabulary', () => {
const rawOptions = (ChartAggregateFunctionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ChartAggregateFunctionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read ChartAggregateFunctionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
*/
import { describe, it, expect } from 'vitest';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
classifyWidgetType,
CHART_TYPE_ALIASES,
Expand All@@ -29,10 +30,7 @@ import {
TABLE_LIKE_TYPES,
} from '../widgetDispatch';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('widget dispatch covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,7 @@
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import { InlineEditProvider, useInlineEdit } from '@object-ui/react';
import type { DetailViewSection } from '@object-ui/types';
import { DetailSection } from '../DetailSection';
Expand DownExpand Up@@ -82,9 +83,7 @@ import { isComputedFieldType, isInlineExcludedDetailFieldType } from '../fieldEn
* a member of the spec enum, which is the card's own premise — so it leaves
* this universe entirely rather than moving between buckets.
*/
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);
const ALL_TYPES = [...new Set([...FORM_FIELD_TYPES, ...specTypes])].sort();

/** The hosts never even render an editor for these (the gates, not this switch). */
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-grid/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"devDependencies": {
"@object-ui/data-objectstack": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@tailwindcss/postcss": "^4.3.3",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,11 +14,11 @@
*/
import { describe, it, expect } from 'vitest';
import { ColumnSummarySchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { SUPPORTED_SUMMARY_TYPES } from '../useColumnSummary';

describe('useColumnSummary covers the spec summary vocabulary', () => {
const rawOptions = (ColumnSummarySchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ColumnSummarySchema);

it('reads a non-empty enum from the spec', () => {
// Guards the assertions below against silently passing on an empty list if
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-report/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/node": "^26.2.0",
"@types/react": "19.2.18",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,21 +33,16 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ChartTypeSchema, ReportType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
DatasetReportRenderer,
planReportChart,
resolveReportPresentation,
} from '../DatasetReportRenderer';

const specChartTypes: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specChartTypes: string[] = enumOptions(ChartTypeSchema);

const specReportTypes: string[] = (() => {
const raw = (ReportType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specReportTypes: string[] = enumOptions(ReportType);

describe('planReportChart covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
"typescript": "^6.0.3"
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import { NotificationTypeSchema, NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
useAnimation,
SUPPORTED_TRANSITION_PRESETS,
Expand All@@ -55,11 +56,6 @@ import {
SUPPORTED_NOTIFICATION_POSITIONS,
} from '../../context/NotificationContext';

function options(schema: unknown): string[] {
const raw = (schema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
}

function assertParity(specNames: string[], implemented: ReadonlySet<string>, what: string) {
expect(specNames, `could not read the ${what} enum from the spec`).not.toEqual([]);
expect(
Expand DownExpand Up@@ -89,11 +85,11 @@ describe('react hooks cover the spec animation/notification vocabularies', () =>
});

it('notification display types match NotificationTypeSchema both ways', () => {
assertParity(options(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
assertParity(enumOptions(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
});

it('notification positions match NotificationPositionSchema both ways', () => {
assertParity(options(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
assertParity(enumOptions(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
});

// `NotificationActionButton.variant` was the shadcn Button vocabulary
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/olive-moons-shave.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
---
---

Internal test-support change, no user-visible behaviour (objectui#6924).

The 17 hand-written `(Schema as { options?: readonly string[] }).options` casts
across 16 spec-parity test files converge onto `@object-ui/test-support`'s
`enumOptions(node)` — a sibling entry point onto the wrapper walk
`shapeEnumOptions` already carried, which now delegates to it rather than
holding a second copy. Only test files, the private (never-published)
`@object-ui/test-support`, and `devDependencies` edges change; no package's
shipped `dist/` and no public type moves.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,6 +82,7 @@ import {
ToastSchema,
ToasterSchema,
} from '@object-ui/types/zod';
import { enumOptions } from '@object-ui/test-support';
import { allExamples, getExample } from '../src/index.js';

type Json = Record<string, unknown>;
Expand All@@ -94,14 +95,15 @@ const schemaOf = (id: string): Json => getExample(id).schema as unknown as Json;
* SHIPPED enum rather than to a hand-copied string list is the point: if the
* platform ever adds or drops a member, this file follows it instead of
* asserting yesterday's vocabulary.
*
* The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924);
* the THROW stays here, because that is this file's non-vacuity duty and the
* reader deliberately answers `[]` rather than raising.
*/
function enumOptionsOf(field: unknown): readonly string[] {
let node = field as { options?: readonly string[]; unwrap?: () => unknown };
for (let i = 0; i < 4 && node && !node.options; i += 1) {
node = node.unwrap?.() as typeof node;
}
if (!node?.options) throw new Error('not an enum-bearing field');
return node.options;
const options = enumOptions(field);
if (options.length === 0) throw new Error('not an enum-bearing field');
return options;
}

const TOAST_FIXTURES = [
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,12 +22,10 @@

import { describe, it, expect } from 'vitest';
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { COLOR_VARIANTS, colorVariantCss } from './color-variant-field';

const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The picker's canonical row — `ColorVariantPicker` renders exactly
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,12 @@ import {
PageHeaderProps,
RecordDetailsProps,
} from '@objectstack/spec/ui';
import { isShapeKeyTombstoned, listedShapeKeys, shapeMemberTypeName } from '@object-ui/test-support';
import {
enumOptions,
isShapeKeyTombstoned,
listedShapeKeys,
shapeMemberTypeName,
} from '@object-ui/test-support';
import { BLOCK_CONFIG, blockHasConfig, type PlaceholderSpec } from '../block-config';
import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';
import { t } from '../../i18n';
Expand DownExpand Up@@ -171,10 +176,7 @@ describe('record:details sections ↔ spec section-entry coverage (#3819)', () =
* offered, or excluded with a documented reason.
*/
describe('page palette ↔ spec PageComponentType coverage', () => {
const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import { ComponentRegistry } from '@object-ui/core';
import { PageComponentType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
// Side-effect import: registers `record:chatter` and `record:discussion`. The
// app-shell test setup does not pull plugin-detail in, and relying on another
// file having imported it first would make this suite order-dependent.
Expand All@@ -51,14 +52,20 @@ import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';

afterEach(cleanup);

const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

const meta = BLOCK_TYPE_META as Record<string, { label: string; category: string } | undefined>;

describe('page palette — the discussion/chatter pair points at the canonical name (#5495)', () => {
// The duty `@object-ui/test-support`'s reader leaves with every caller: `[]`
// is its "could not read", and the `not.toContain` probes below are satisfied
// by an empty list. The `toContain` probes would red on one — but they say
// "the spec dropped this member", which sends the next reader to the contract
// instead of to the reader. This one names the real cause (objectui#6924).
it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
});

it('offers `record:discussion`, labelled for authors as "Discussion"', () => {
expect(meta['record:discussion']).toBeDefined();
expect(meta['record:discussion']!.label).toBe('Discussion');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ import { describe, it, expect, vi } from 'vitest';
import React from 'react';
import '@testing-library/jest-dom';
import { NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { renderComponent } from './test-utils';
import { TOASTER_POSITIONS } from '../renderers/feedback/toaster';
// Registers the renderers at module scope, NOT inside a `beforeAll` — there the
Expand All@@ -39,8 +40,7 @@ vi.mock('../ui/sonner', () => ({
}));

describe('toaster covers the spec notification-position vocabulary', () => {
const rawOptions = (NotificationPositionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(NotificationPositionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read NotificationPositionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import React from 'react';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NotificationTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
NOTIFICATION_PRESENTATIONS,
NotificationProvider,
Expand All@@ -36,8 +37,7 @@ import { NotificationInline } from '../NotificationInline';
import { notificationActionVariant, notificationIcon, notificationSeverityStyle } from '../severity';

function specDisplayTypes(): string[] {
const raw = (NotificationTypeSchema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
return enumOptions(NotificationTypeSchema);
}

type Notify = (input: Omit<NotificationItem, 'id' | 'createdAt' | 'read'>) => string;
Expand Down
5 changes: 2 additions & 3 deletions packages/fields/src/FieldEditWidget.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@

import { describe, it, expect } from 'vitest';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import {
FORM_FIELD_TYPES,
INLINE_EXCLUDED_FIELD_TYPES,
Expand DownExpand Up@@ -108,9 +109,7 @@ describe('inline editor ↔ form widget parity', () => {
* a documented exclusion.
*/
describe('inline editor ↔ SPEC FieldType parity (#2942)', () => {
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);

it('reads a non-empty enum from the spec', () => {
expect(specTypes, 'could not read FieldType.options from the spec').not.toEqual([]);
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-charts/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,13 +29,11 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import AdvancedChartImpl from '../AdvancedChartImpl';
import { RENDERABLE, SINGLE_VALUE_CHART_TYPES, TABULAR_CHART_TYPES } from '../normalizeChartSchema';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('plugin-charts covers the spec chart-type vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-dashboard/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"devDependencies": {
"@object-ui/plugin-charts": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react-grid-layout": "^2.1.0",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,16 +36,14 @@ import { render, screen, cleanup } from '@testing-library/react';
// `@object-ui/types` (decision (a) of objectui#2561), so this is the supported
// way to reach it.
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { DatasetWidget } from '../DatasetWidget';
import { VARIANT_ICON_CLASSES, VARIANT_TEXT_CLASSES, metricAccentTextClass } from '../colorVariants';

afterEach(cleanup);

/** The spec's own token list, read at test time (see the parity block below). */
const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The metric card's markup with NO `colorVariant` declared, exactly as
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartAggregateFunctionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { PivotTable, PIVOT_AGGREGATIONS } from '../PivotTable';

const baseSchema = {
Expand All@@ -41,8 +42,7 @@ const baseSchema = {
};

describe('PivotTable covers the spec UI aggregation vocabulary', () => {
const rawOptions = (ChartAggregateFunctionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ChartAggregateFunctionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read ChartAggregateFunctionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
*/
import { describe, it, expect } from 'vitest';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
classifyWidgetType,
CHART_TYPE_ALIASES,
Expand All@@ -29,10 +30,7 @@ import {
TABLE_LIKE_TYPES,
} from '../widgetDispatch';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('widget dispatch covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,7 @@
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import { InlineEditProvider, useInlineEdit } from '@object-ui/react';
import type { DetailViewSection } from '@object-ui/types';
import { DetailSection } from '../DetailSection';
Expand DownExpand Up@@ -82,9 +83,7 @@ import { isComputedFieldType, isInlineExcludedDetailFieldType } from '../fieldEn
* a member of the spec enum, which is the card's own premise — so it leaves
* this universe entirely rather than moving between buckets.
*/
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);
const ALL_TYPES = [...new Set([...FORM_FIELD_TYPES, ...specTypes])].sort();

/** The hosts never even render an editor for these (the gates, not this switch). */
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-grid/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"devDependencies": {
"@object-ui/data-objectstack": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@tailwindcss/postcss": "^4.3.3",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,11 +14,11 @@
*/
import { describe, it, expect } from 'vitest';
import { ColumnSummarySchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { SUPPORTED_SUMMARY_TYPES } from '../useColumnSummary';

describe('useColumnSummary covers the spec summary vocabulary', () => {
const rawOptions = (ColumnSummarySchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ColumnSummarySchema);

it('reads a non-empty enum from the spec', () => {
// Guards the assertions below against silently passing on an empty list if
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-report/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/node": "^26.2.0",
"@types/react": "19.2.18",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,21 +33,16 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ChartTypeSchema, ReportType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
DatasetReportRenderer,
planReportChart,
resolveReportPresentation,
} from '../DatasetReportRenderer';

const specChartTypes: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specChartTypes: string[] = enumOptions(ChartTypeSchema);

const specReportTypes: string[] = (() => {
const raw = (ReportType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specReportTypes: string[] = enumOptions(ReportType);

describe('planReportChart covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
"typescript": "^6.0.3"
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import { NotificationTypeSchema, NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
useAnimation,
SUPPORTED_TRANSITION_PRESETS,
Expand All@@ -55,11 +56,6 @@ import {
SUPPORTED_NOTIFICATION_POSITIONS,
} from '../../context/NotificationContext';

function options(schema: unknown): string[] {
const raw = (schema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
}

function assertParity(specNames: string[], implemented: ReadonlySet<string>, what: string) {
expect(specNames, `could not read the ${what} enum from the spec`).not.toEqual([]);
expect(
Expand DownExpand Up@@ -89,11 +85,11 @@ describe('react hooks cover the spec animation/notification vocabularies', () =>
});

it('notification display types match NotificationTypeSchema both ways', () => {
assertParity(options(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
assertParity(enumOptions(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
});

it('notification positions match NotificationPositionSchema both ways', () => {
assertParity(options(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
assertParity(enumOptions(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
});

// `NotificationActionButton.variant` was the shadcn Button vocabulary
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/olive-moons-shave.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
---
---

Internal test-support change, no user-visible behaviour (objectui#6924).

The 17 hand-written `(Schema as { options?: readonly string[] }).options` casts
across 16 spec-parity test files converge onto `@object-ui/test-support`'s
`enumOptions(node)` — a sibling entry point onto the wrapper walk
`shapeEnumOptions` already carried, which now delegates to it rather than
holding a second copy. Only test files, the private (never-published)
`@object-ui/test-support`, and `devDependencies` edges change; no package's
shipped `dist/` and no public type moves.
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,6 +82,7 @@ import {
ToastSchema,
ToasterSchema,
} from '@object-ui/types/zod';
import { enumOptions } from '@object-ui/test-support';
import { allExamples, getExample } from '../src/index.js';

type Json = Record<string, unknown>;
Expand All@@ -94,14 +95,15 @@ const schemaOf = (id: string): Json => getExample(id).schema as unknown as Json;
* SHIPPED enum rather than to a hand-copied string list is the point: if the
* platform ever adds or drops a member, this file follows it instead of
* asserting yesterday's vocabulary.
*
* The wrapper walk is `@object-ui/test-support`'s shared reader (objectui#6924);
* the THROW stays here, because that is this file's non-vacuity duty and the
* reader deliberately answers `[]` rather than raising.
*/
function enumOptionsOf(field: unknown): readonly string[] {
let node = field as { options?: readonly string[]; unwrap?: () => unknown };
for (let i = 0; i < 4 && node && !node.options; i += 1) {
node = node.unwrap?.() as typeof node;
}
if (!node?.options) throw new Error('not an enum-bearing field');
return node.options;
const options = enumOptions(field);
if (options.length === 0) throw new Error('not an enum-bearing field');
return options;
}

const TOAST_FIXTURES = [
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,12 +22,10 @@

import { describe, it, expect } from 'vitest';
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { COLOR_VARIANTS, colorVariantCss } from './color-variant-field';

const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The picker's canonical row — `ColorVariantPicker` renders exactly
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,12 @@ import {
PageHeaderProps,
RecordDetailsProps,
} from '@objectstack/spec/ui';
import { isShapeKeyTombstoned, listedShapeKeys, shapeMemberTypeName } from '@object-ui/test-support';
import {
enumOptions,
isShapeKeyTombstoned,
listedShapeKeys,
shapeMemberTypeName,
} from '@object-ui/test-support';
import { BLOCK_CONFIG, blockHasConfig, type PlaceholderSpec } from '../block-config';
import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';
import { t } from '../../i18n';
Expand DownExpand Up@@ -171,10 +176,7 @@ describe('record:details sections ↔ spec section-entry coverage (#3819)', () =
* offered, or excluded with a documented reason.
*/
describe('page palette ↔ spec PageComponentType coverage', () => {
const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import { ComponentRegistry } from '@object-ui/core';
import { PageComponentType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
// Side-effect import: registers `record:chatter` and `record:discussion`. The
// app-shell test setup does not pull plugin-detail in, and relying on another
// file having imported it first would make this suite order-dependent.
Expand All@@ -51,14 +52,20 @@ import { BLOCK_TYPE_META, PALETTE_EXCLUSIONS } from '../block-types';

afterEach(cleanup);

const specNames: string[] = (() => {
const raw = (PageComponentType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(PageComponentType);

const meta = BLOCK_TYPE_META as Record<string, { label: string; category: string } | undefined>;

describe('page palette — the discussion/chatter pair points at the canonical name (#5495)', () => {
// The duty `@object-ui/test-support`'s reader leaves with every caller: `[]`
// is its "could not read", and the `not.toContain` probes below are satisfied
// by an empty list. The `toContain` probes would red on one — but they say
// "the spec dropped this member", which sends the next reader to the contract
// instead of to the reader. This one names the real cause (objectui#6924).
it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read PageComponentType.options from the spec').not.toEqual([]);
});

it('offers `record:discussion`, labelled for authors as "Discussion"', () => {
expect(meta['record:discussion']).toBeDefined();
expect(meta['record:discussion']!.label).toBe('Discussion');
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ import { describe, it, expect, vi } from 'vitest';
import React from 'react';
import '@testing-library/jest-dom';
import { NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { renderComponent } from './test-utils';
import { TOASTER_POSITIONS } from '../renderers/feedback/toaster';
// Registers the renderers at module scope, NOT inside a `beforeAll` — there the
Expand All@@ -39,8 +40,7 @@ vi.mock('../ui/sonner', () => ({
}));

describe('toaster covers the spec notification-position vocabulary', () => {
const rawOptions = (NotificationPositionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(NotificationPositionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read NotificationPositionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import React from 'react';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NotificationTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
NOTIFICATION_PRESENTATIONS,
NotificationProvider,
Expand All@@ -36,8 +37,7 @@ import { NotificationInline } from '../NotificationInline';
import { notificationActionVariant, notificationIcon, notificationSeverityStyle } from '../severity';

function specDisplayTypes(): string[] {
const raw = (NotificationTypeSchema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
return enumOptions(NotificationTypeSchema);
}

type Notify = (input: Omit<NotificationItem, 'id' | 'createdAt' | 'read'>) => string;
Expand Down
5 changes: 2 additions & 3 deletions packages/fields/src/FieldEditWidget.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@

import { describe, it, expect } from 'vitest';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import {
FORM_FIELD_TYPES,
INLINE_EXCLUDED_FIELD_TYPES,
Expand DownExpand Up@@ -108,9 +109,7 @@ describe('inline editor ↔ form widget parity', () => {
* a documented exclusion.
*/
describe('inline editor ↔ SPEC FieldType parity (#2942)', () => {
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);

it('reads a non-empty enum from the spec', () => {
expect(specTypes, 'could not read FieldType.options from the spec').not.toEqual([]);
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-charts/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,13 +29,11 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import AdvancedChartImpl from '../AdvancedChartImpl';
import { RENDERABLE, SINGLE_VALUE_CHART_TYPES, TABULAR_CHART_TYPES } from '../normalizeChartSchema';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('plugin-charts covers the spec chart-type vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-dashboard/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
"devDependencies": {
"@object-ui/plugin-charts": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/react-grid-layout": "^2.1.0",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,16 +36,14 @@ import { render, screen, cleanup } from '@testing-library/react';
// `@object-ui/types` (decision (a) of objectui#2561), so this is the supported
// way to reach it.
import { WidgetColorVariantSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { DatasetWidget } from '../DatasetWidget';
import { VARIANT_ICON_CLASSES, VARIANT_TEXT_CLASSES, metricAccentTextClass } from '../colorVariants';

afterEach(cleanup);

/** The spec's own token list, read at test time (see the parity block below). */
const specVariants: string[] = (() => {
const raw = (WidgetColorVariantSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specVariants: string[] = enumOptions(WidgetColorVariantSchema);

/**
* The metric card's markup with NO `colorVariant` declared, exactly as
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';
import { ChartAggregateFunctionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { PivotTable, PIVOT_AGGREGATIONS } from '../PivotTable';

const baseSchema = {
Expand All@@ -41,8 +42,7 @@ const baseSchema = {
};

describe('PivotTable covers the spec UI aggregation vocabulary', () => {
const rawOptions = (ChartAggregateFunctionSchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ChartAggregateFunctionSchema);

it('reads a non-empty enum from the spec', () => {
expect(specNames, 'could not read ChartAggregateFunctionSchema.options from the spec').not.toEqual([]);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@
*/
import { describe, it, expect } from 'vitest';
import { ChartTypeSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
classifyWidgetType,
CHART_TYPE_ALIASES,
Expand All@@ -29,10 +30,7 @@ import {
TABLE_LIKE_TYPES,
} from '../widgetDispatch';

const specNames: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specNames: string[] = enumOptions(ChartTypeSchema);

describe('widget dispatch covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,7 @@
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { FieldType } from '@objectstack/spec/data';
import { enumOptions } from '@object-ui/test-support';
import { InlineEditProvider, useInlineEdit } from '@object-ui/react';
import type { DetailViewSection } from '@object-ui/types';
import { DetailSection } from '../DetailSection';
Expand DownExpand Up@@ -82,9 +83,7 @@ import { isComputedFieldType, isInlineExcludedDetailFieldType } from '../fieldEn
* a member of the spec enum, which is the card's own premise — so it leaves
* this universe entirely rather than moving between buckets.
*/
const specTypes: string[] = Array.isArray((FieldType as { options?: readonly string[] }).options)
? [...(FieldType as { options: readonly string[] }).options]
: [];
const specTypes: string[] = enumOptions(FieldType);
const ALL_TYPES = [...new Set([...FORM_FIELD_TYPES, ...specTypes])].sort();

/** The hosts never even render an editor for these (the gates, not this switch). */
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-grid/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"devDependencies": {
"@object-ui/data-objectstack": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@tailwindcss/postcss": "^4.3.3",
"@vitejs/plugin-react": "^6.0.5",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,11 +14,11 @@
*/
import { describe, it, expect } from 'vitest';
import { ColumnSummarySchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import { SUPPORTED_SUMMARY_TYPES } from '../useColumnSummary';

describe('useColumnSummary covers the spec summary vocabulary', () => {
const rawOptions = (ColumnSummarySchema as unknown as { options?: readonly string[] }).options;
const specNames: string[] = Array.isArray(rawOptions) ? [...rawOptions] : [];
const specNames: string[] = enumOptions(ColumnSummarySchema);

it('reads a non-empty enum from the spec', () => {
// Guards the assertions below against silently passing on an empty list if
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-report/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@types/node": "^26.2.0",
"@types/react": "19.2.18",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,21 +33,16 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ChartTypeSchema, ReportType } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
DatasetReportRenderer,
planReportChart,
resolveReportPresentation,
} from '../DatasetReportRenderer';

const specChartTypes: string[] = (() => {
const raw = (ChartTypeSchema as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specChartTypes: string[] = enumOptions(ChartTypeSchema);

const specReportTypes: string[] = (() => {
const raw = (ReportType as unknown as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
})();
const specReportTypes: string[] = enumOptions(ReportType);

describe('planReportChart covers the spec chart vocabulary', () => {
it('reads a non-empty enum from the spec', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@object-ui/test-support": "workspace:*",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
"typescript": "^6.0.3"
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import { NotificationTypeSchema, NotificationPositionSchema } from '@objectstack/spec/ui';
import { enumOptions } from '@object-ui/test-support';
import {
useAnimation,
SUPPORTED_TRANSITION_PRESETS,
Expand All@@ -55,11 +56,6 @@ import {
SUPPORTED_NOTIFICATION_POSITIONS,
} from '../../context/NotificationContext';

function options(schema: unknown): string[] {
const raw = (schema as { options?: readonly string[] }).options;
return Array.isArray(raw) ? [...raw] : [];
}

function assertParity(specNames: string[], implemented: ReadonlySet<string>, what: string) {
expect(specNames, `could not read the ${what} enum from the spec`).not.toEqual([]);
expect(
Expand DownExpand Up@@ -89,11 +85,11 @@ describe('react hooks cover the spec animation/notification vocabularies', () =>
});

it('notification display types match NotificationTypeSchema both ways', () => {
assertParity(options(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
assertParity(enumOptions(NotificationTypeSchema), SUPPORTED_NOTIFICATION_DISPLAY_TYPES, 'notification display type');
});

it('notification positions match NotificationPositionSchema both ways', () => {
assertParity(options(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
assertParity(enumOptions(NotificationPositionSchema), SUPPORTED_NOTIFICATION_POSITIONS, 'notification position');
});

// `NotificationActionButton.variant` was the shadcn Button vocabulary
Expand Down
Loading
Loading