diff --git a/packages/@luke-ui/react/src/box/box.browser.test.tsx b/packages/@luke-ui/react/src/box/box.browser.test.tsx index 31a7c7d3..2d552e0e 100644 --- a/packages/@luke-ui/react/src/box/box.browser.test.tsx +++ b/packages/@luke-ui/react/src/box/box.browser.test.tsx @@ -2,17 +2,15 @@ import { afterEach, expect, test } from 'vite-plus/test'; import { page } from 'vite-plus/test/context'; import { testUniversalConformance } from '../conformance/helpers.js'; import { render } from '../test-utils/render.js'; -import { componentTestRegistration } from './component-test-registration.js'; import { Box } from './index.js'; testUniversalConformance({ + path: 'box', getTarget: (result) => { const target = result.container.firstElementChild; if (!(target instanceof HTMLElement)) throw new Error('Expected a Box element.'); return target; }, - name: 'Box', - registration: componentTestRegistration, render: (props = {}) => render(Content), }); diff --git a/packages/@luke-ui/react/src/box/component-test-registration.ts b/packages/@luke-ui/react/src/box/component-test-registration.ts deleted file mode 100644 index aa260f34..00000000 --- a/packages/@luke-ui/react/src/box/component-test-registration.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineComponentTestRegistration } from '../conformance/registrations.js'; - -export const componentTestRegistration = defineComponentTestRegistration({ - conformanceTier: 'universal', - integrationTripwire: 'none', - path: 'box', -}); diff --git a/packages/@luke-ui/react/src/button/button.browser.test.tsx b/packages/@luke-ui/react/src/button/button.browser.test.tsx index 568fbad8..8b17c8ee 100644 --- a/packages/@luke-ui/react/src/button/button.browser.test.tsx +++ b/packages/@luke-ui/react/src/button/button.browser.test.tsx @@ -1,23 +1,21 @@ import { expect } from 'vite-plus/test'; import { testIntegration, testUniversalConformance } from '../conformance/helpers.js'; import { render } from '../test-utils/render.js'; -import { componentTestRegistration } from './component-test-registration.js'; import { Button } from './index.js'; testUniversalConformance({ + path: 'button', getTarget: (result) => { const target = result.locator.getByRole('button').element(); if (!(target instanceof HTMLElement)) throw new Error('Expected a button.'); return target; }, - name: 'Button', - registration: componentTestRegistration, render: (props = {}) => { return render(); }, }); -testIntegration(componentTestRegistration, 'Button', async () => { +testIntegration('button', async () => { let pressed = false; const { locator, user } = render(); diff --git a/packages/@luke-ui/react/src/button/component-test-registration.ts b/packages/@luke-ui/react/src/button/component-test-registration.ts deleted file mode 100644 index 0407f93b..00000000 --- a/packages/@luke-ui/react/src/button/component-test-registration.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineComponentTestRegistration } from '../conformance/registrations.js'; - -export const componentTestRegistration = defineComponentTestRegistration({ - conformanceTier: 'universal', - integrationTripwire: 'required', - path: 'button', -}); diff --git a/packages/@luke-ui/react/src/checkbox/checkbox.browser.test.tsx b/packages/@luke-ui/react/src/checkbox/checkbox.browser.test.tsx index 6a6d1767..99cec955 100644 --- a/packages/@luke-ui/react/src/checkbox/checkbox.browser.test.tsx +++ b/packages/@luke-ui/react/src/checkbox/checkbox.browser.test.tsx @@ -2,10 +2,10 @@ import { expect, test } from 'vite-plus/test'; import { cdp, page } from 'vite-plus/test/context'; import { testFieldShapedConformance, testIntegration } from '../conformance/helpers.js'; import { render } from '../test-utils/render.js'; -import { componentTestRegistration } from './component-test-registration.js'; import { Checkbox } from './index.js'; testFieldShapedConformance({ + path: 'checkbox', assertAssociation: (result) => { // oxlint-disable-next-line vitest/no-standalone-expect expect(result.locator.getByRole('checkbox', { name: 'Terms' }).element()).toHaveAccessibleName( @@ -17,19 +17,12 @@ testFieldShapedConformance({ if (!(control instanceof HTMLElement)) throw new Error('Expected a checkbox control.'); return control; }, - getTarget: (result) => { - const target = result.container.querySelector('label'); - if (!(target instanceof HTMLElement)) throw new Error('Expected a checkbox label.'); - return target; - }, - name: 'Checkbox', - registration: componentTestRegistration, render: (props = {}) => { return render(Terms); }, }); -testIntegration(componentTestRegistration, 'Checkbox', async () => { +testIntegration('checkbox', async () => { const { locator, user } = render(Terms); const checkbox = locator.getByRole('checkbox', { name: 'Terms' }); diff --git a/packages/@luke-ui/react/src/checkbox/component-test-registration.ts b/packages/@luke-ui/react/src/checkbox/component-test-registration.ts deleted file mode 100644 index 5474cd16..00000000 --- a/packages/@luke-ui/react/src/checkbox/component-test-registration.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineComponentTestRegistration } from '../conformance/registrations.js'; - -export const componentTestRegistration = defineComponentTestRegistration({ - conformanceTier: 'field-shaped', - integrationTripwire: 'required', - path: 'checkbox', -}); diff --git a/packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx b/packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx index ddb13406..00cb11c2 100644 --- a/packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx +++ b/packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx @@ -9,7 +9,6 @@ import { ComboboxRoot } from '../primitives/combobox/root.js'; import { mockScreenWidth } from '../test-utils/mock-screen-width.js'; import { render } from '../test-utils/render.js'; import { waitForOverlayEnter } from '../test-utils/wait-for-overlay-enter.js'; -import { componentTestRegistration } from './component-test-registration.js'; import { ComboboxField } from './index.js'; type CountryItem = { @@ -25,6 +24,7 @@ const countryItems: Array = [ const renderCountryItem = (item: CountryItem) => {item.label}; testFieldShapedConformance({ + path: 'combobox-field', assertAssociation: (result) => { // oxlint-disable-next-line vitest/no-standalone-expect expect(result.locator.getByRole('combobox', { name: 'Country' }).element()).toHaveAttribute( @@ -44,13 +44,6 @@ testFieldShapedConformance({ if (!(control instanceof HTMLElement)) throw new Error('Expected a combobox input.'); return control; }, - getTarget: (result) => { - const target = result.container.firstElementChild; - if (!(target instanceof HTMLElement)) throw new Error('Expected a combobox root.'); - return target; - }, - name: 'ComboboxField', - registration: componentTestRegistration, render: (props = {}) => { return render( @@ -65,7 +58,7 @@ testFieldShapedConformance({ }, }); -testIntegration(componentTestRegistration, 'ComboboxField', async () => { +testIntegration('combobox-field', async () => { const { locator, user } = render( {renderCountryItem} diff --git a/packages/@luke-ui/react/src/combobox-field/component-test-registration.ts b/packages/@luke-ui/react/src/combobox-field/component-test-registration.ts deleted file mode 100644 index 60cc8edd..00000000 --- a/packages/@luke-ui/react/src/combobox-field/component-test-registration.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineComponentTestRegistration } from '../conformance/registrations.js'; - -export const componentTestRegistration = defineComponentTestRegistration({ - conformanceTier: 'field-shaped', - integrationTripwire: 'required', - path: 'combobox-field', -}); diff --git a/packages/@luke-ui/react/src/conformance/helpers.ts b/packages/@luke-ui/react/src/conformance/helpers.ts index 1add77e8..49eeadef 100644 --- a/packages/@luke-ui/react/src/conformance/helpers.ts +++ b/packages/@luke-ui/react/src/conformance/helpers.ts @@ -1,33 +1,41 @@ import { expect, test } from 'vite-plus/test'; import type { RenderResult } from '../test-utils/render.js'; -import type { ComponentTestRegistration } from './registrations.js'; +import { componentTestManifest } from './manifest.js'; type RenderComponent = (props?: Record) => RenderResult; type UniversalConformanceOptions = { - name: string; - registration: ComponentTestRegistration; + path: string; render: RenderComponent; getTarget: (result: RenderResult) => HTMLElement; }; -type FieldConformanceOptions = UniversalConformanceOptions & { +type FieldConformanceOptions = { assertName?: (result: RenderResult, control: HTMLElement) => void; getControl: (result: RenderResult) => HTMLElement; assertAssociation?: (result: RenderResult) => void; + path: string; + render: RenderComponent; }; +function getManifestEntry(path: string) { + const entry = componentTestManifest.find((candidate) => candidate.path === path); + if (entry == null) throw new Error(`Unknown component test path: ${path}`); + return entry; +} + function assertNativeName(_: RenderResult, control: HTMLElement) { expect(control).toHaveAttribute('name', 'conformance-field'); } export function testUniversalConformance(options: UniversalConformanceOptions) { - const { getTarget, name, registration, render } = options; - if (registration.conformanceTier !== 'universal') { - throw new Error(`Expected a universal registration for ${registration.path}.`); + const { getTarget, path, render } = options; + const entry = getManifestEntry(path); + if (entry.conformanceTier !== 'universal') { + throw new Error(`Expected universal conformance for ${path}.`); } - test(`${name} forwards its universal DOM contract`, () => { + test(`${entry.name} forwards its universal DOM contract`, () => { const ref = { current: null as HTMLElement | null }; const result = render({ className: 'conformance-class', @@ -46,11 +54,12 @@ export function testUniversalConformance(options: UniversalConformanceOptions) { } export function testFieldShapedConformance(options: FieldConformanceOptions) { - const { assertAssociation, assertName, getControl, name, registration, render } = options; - if (registration.conformanceTier !== 'field-shaped') { - throw new Error(`Expected a field-shaped registration for ${registration.path}.`); + const { assertAssociation, assertName, getControl, path, render } = options; + const entry = getManifestEntry(path); + if (entry.conformanceTier !== 'field-shaped') { + throw new Error(`Expected field-shaped conformance for ${path}.`); } - test(`${name} forwards its field contract`, async () => { + test(`${entry.name} forwards its field contract`, async () => { const inputRef = { current: null as HTMLElement | null }; let blurred = false; const result = render({ @@ -97,7 +106,7 @@ export function testFieldShapedConformance(options: FieldConformanceOptions) { // React Aria types `inputRef` as a ref object. Luke UI widens it to accept a // callback so React Hook Form's `field.ref` works without an adapter. - test(`${name} resolves a callback inputRef to the control`, () => { + test(`${entry.name} resolves a callback inputRef to the control`, () => { const resolved: Array = []; const result = render({ inputRef: (node: HTMLElement | null) => { @@ -112,15 +121,12 @@ export function testFieldShapedConformance(options: FieldConformanceOptions) { }); } -export function testIntegration( - registration: ComponentTestRegistration, - name: string, - run: () => void | Promise, -) { - if (registration.integrationTripwire !== 'required') { - throw new Error(`Expected an integration registration for ${registration.path}.`); +export function testIntegration(path: string, run: () => void | Promise) { + const entry = getManifestEntry(path); + if (entry.integrationTripwire !== 'required') { + throw new Error(`Expected an integration tripwire for ${path}.`); } - test(`${name} integration`, async () => { + test(`${entry.name} integration`, async () => { expect.hasAssertions(); await run(); }); diff --git a/packages/@luke-ui/react/src/conformance/manifest.test.ts b/packages/@luke-ui/react/src/conformance/manifest.test.ts index cd36948d..ec889940 100644 --- a/packages/@luke-ui/react/src/conformance/manifest.test.ts +++ b/packages/@luke-ui/react/src/conformance/manifest.test.ts @@ -1,6 +1,9 @@ import { existsSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; +import type { Node } from 'oxc-parser'; +import { parseSync } from 'oxc-parser'; import { expect, test } from 'vite-plus/test'; +import type { ComponentTestManifestEntry } from './manifest.js'; import { componentTestManifest } from './manifest.js'; const sourceRoot = resolve(import.meta.dirname, '..'); @@ -39,6 +42,80 @@ function getExportPaths() { return paths.sort(); } +function componentFile(path: string, suffix: 'browser.test.tsx' | 'visual.test.tsx') { + const basename = path.split('/').at(-1); + if (basename == null) throw new Error(`Invalid component test path: ${path}`); + return resolve(sourceRoot, path, `${basename}.${suffix}`); +} + +function hasHelperCall(source: string, helperName: string, path: string): boolean { + const parsed = parseSync(`${path}.browser.test.tsx`, source, { lang: 'tsx' }); + if (parsed.errors.length > 0) { + throw new Error(`Could not parse ${path}.browser.test.tsx: ${parsed.errors[0]?.message}`); + } + + for (const statement of parsed.program.body) { + if (statement.type !== 'ExpressionStatement') continue; + const expression = statement.expression; + if (expression.type !== 'CallExpression') continue; + if (expression.callee.type !== 'Identifier' || expression.callee.name !== helperName) { + continue; + } + if (helperPath(expression.arguments[0], helperName) === path) return true; + } + return false; +} + +function helperPath(argument: Node | undefined, helperName: string): string | undefined { + if (helperName === 'testIntegration') return stringLiteral(argument); + if (argument?.type !== 'ObjectExpression') return undefined; + + for (const property of argument.properties) { + if (property.type !== 'Property' || property.computed) continue; + if (propertyName(property.key) !== 'path') continue; + return stringLiteral(property.value); + } +} + +function propertyName(node: Node): string | undefined { + if (node.type === 'Identifier') return node.name; + return stringLiteral(node); +} + +function stringLiteral(node: Node | undefined): string | undefined { + if (node?.type === 'Literal' && typeof node.value === 'string') return node.value; +} + +function getBrowserCoverageErrors( + manifest: ReadonlyArray, + readBrowserSource: (path: string) => string, +) { + const errors: Array = []; + for (const entry of manifest) { + if (entry.conformanceTier === 'none' && entry.integrationTripwire === 'none') continue; + const source = readBrowserSource(entry.path); + if ( + entry.conformanceTier !== 'none' && + !hasHelperCall( + source, + entry.conformanceTier === 'universal' + ? 'testUniversalConformance' + : 'testFieldShapedConformance', + entry.path, + ) + ) { + errors.push(`${entry.path} must invoke its ${entry.conformanceTier} conformance helper.`); + } + if ( + entry.integrationTripwire === 'required' && + !hasHelperCall(source, 'testIntegration', entry.path) + ) { + errors.push(`${entry.path} must invoke its integration tripwire.`); + } + } + return errors; +} + test('covers every public component entrypoint exactly once', () => { const paths = componentTestManifest.map((entry) => entry.path); @@ -56,3 +133,75 @@ test('declares every conformance dimension explicitly', () => { expect(entry.visualApplicability).toBeTypeOf('string'); } }); + +test('browser tests invoke the helpers required by their manifest entry', () => { + expect( + getBrowserCoverageErrors(componentTestManifest, (path) => { + return readFileSync(componentFile(path, 'browser.test.tsx'), 'utf8'); + }), + ).toEqual([]); +}); + +test('visual fixtures match their manifest entries', () => { + for (const entry of componentTestManifest) { + expect(existsSync(componentFile(entry.path, 'visual.test.tsx'))).toBe( + entry.visualApplicability === 'applicable', + ); + } +}); + +test('rejects helper calls for another manifest path and source lookalikes', () => { + const [button] = componentTestManifest.filter((entry) => entry.path === 'button'); + if (button == null) throw new Error('Expected the Button manifest entry.'); + + const wrongPathSource = ` + // testUniversalConformance({ path: 'button' }); + const description = "testIntegration('button', async () => {})"; + const fake = \` + testUniversalConformance({ path: 'button' }); + testIntegration('button', async () => {}); + \`; + testUniversalConformance({ path: 'link' }); + testIntegration('link', async () => {}); + `; + expect(getBrowserCoverageErrors([button], () => wrongPathSource)).toEqual([ + 'button must invoke its universal conformance helper.', + 'button must invoke its integration tripwire.', + ]); +}); + +test('accepts a conformance helper when path is not the first property', () => { + const [button] = componentTestManifest.filter((entry) => entry.path === 'button'); + if (button == null) throw new Error('Expected the Button manifest entry.'); + + const source = ` + testUniversalConformance({ + render: () => render(); + }, + }); + testIntegration('button', async () => {}); + `; + expect(getBrowserCoverageErrors([button], () => source)).toEqual([]); +}); diff --git a/packages/@luke-ui/react/src/conformance/registrations.browser.test.tsx b/packages/@luke-ui/react/src/conformance/registrations.browser.test.tsx deleted file mode 100644 index f339261c..00000000 --- a/packages/@luke-ui/react/src/conformance/registrations.browser.test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { expect, test } from 'vite-plus/test'; -import { componentTestManifest } from './manifest.js'; -import type { ComponentTestRegistration } from './registrations.js'; - -const registrationModules = import.meta.glob<{ - componentTestRegistration?: ComponentTestRegistration; -}>('../*/component-test-registration.ts', { eager: true }); -const browserTestModules = import.meta.glob('../*/**/*.browser.test.tsx'); - -test('registered component contracts match the manifest', () => { - const expected = componentTestManifest - .filter((entry) => entry.conformanceTier !== 'none' || entry.integrationTripwire !== 'none') - .map(({ path, conformanceTier, integrationTripwire }) => ({ - conformanceTier, - integrationTripwire, - path, - })) - .sort((left, right) => left.path.localeCompare(right.path)); - - const registrations = Object.values(registrationModules) - .map((module) => module.componentTestRegistration) - .filter((registration): registration is ComponentTestRegistration => registration != null) - .sort((left, right) => left.path.localeCompare(right.path)); - - expect(registrations).toEqual(expected); - for (const registration of registrations) { - const componentName = registration.path.split('/').at(-1); - if (componentName == null) throw new Error(`Invalid component test path: ${registration.path}`); - const testFile = `../${registration.path}/${componentName}.browser.test.tsx`; - expect(browserTestModules).toHaveProperty(testFile); - } -}); diff --git a/packages/@luke-ui/react/src/conformance/registrations.ts b/packages/@luke-ui/react/src/conformance/registrations.ts deleted file mode 100644 index 38c9a4e6..00000000 --- a/packages/@luke-ui/react/src/conformance/registrations.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { ComponentTestManifestEntry } from './manifest.js'; -import { componentTestManifest } from './manifest.js'; - -export type ComponentTestRegistration = Pick< - ComponentTestManifestEntry, - 'path' | 'conformanceTier' | 'integrationTripwire' ->; - -export function defineComponentTestRegistration(registration: ComponentTestRegistration) { - const manifestEntry = componentTestManifest.find((entry) => entry.path === registration.path); - if (manifestEntry == null) throw new Error(`Unknown component test path: ${registration.path}`); - if ( - manifestEntry.conformanceTier !== registration.conformanceTier || - manifestEntry.integrationTripwire !== registration.integrationTripwire - ) { - throw new Error( - `Component test registration does not match the manifest: ${registration.path}`, - ); - } - - return registration; -} diff --git a/packages/@luke-ui/react/src/icon-button/component-test-registration.ts b/packages/@luke-ui/react/src/icon-button/component-test-registration.ts deleted file mode 100644 index f0b58b20..00000000 --- a/packages/@luke-ui/react/src/icon-button/component-test-registration.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineComponentTestRegistration } from '../conformance/registrations.js'; - -export const componentTestRegistration = defineComponentTestRegistration({ - conformanceTier: 'universal', - integrationTripwire: 'required', - path: 'icon-button', -}); diff --git a/packages/@luke-ui/react/src/icon-button/icon-button.browser.test.tsx b/packages/@luke-ui/react/src/icon-button/icon-button.browser.test.tsx index c6921dce..64dd9e6a 100644 --- a/packages/@luke-ui/react/src/icon-button/icon-button.browser.test.tsx +++ b/packages/@luke-ui/react/src/icon-button/icon-button.browser.test.tsx @@ -1,23 +1,21 @@ import { expect } from 'vite-plus/test'; import { testIntegration, testUniversalConformance } from '../conformance/helpers.js'; import { render } from '../test-utils/render.js'; -import { componentTestRegistration } from './component-test-registration.js'; import { IconButton } from './index.js'; testUniversalConformance({ + path: 'icon-button', getTarget: (result) => { const target = result.locator.getByRole('button', { name: 'Add' }).element(); if (!(target instanceof HTMLElement)) throw new Error('Expected an icon button.'); return target; }, - name: 'IconButton', - registration: componentTestRegistration, render: (props = {}) => { return render(); }, }); -testIntegration(componentTestRegistration, 'IconButton', async () => { +testIntegration('icon-button', async () => { let pressed = false; const { locator, user } = render( (pressed = true)} />, diff --git a/packages/@luke-ui/react/src/link/component-test-registration.ts b/packages/@luke-ui/react/src/link/component-test-registration.ts deleted file mode 100644 index 850a5485..00000000 --- a/packages/@luke-ui/react/src/link/component-test-registration.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineComponentTestRegistration } from '../conformance/registrations.js'; - -export const componentTestRegistration = defineComponentTestRegistration({ - conformanceTier: 'universal', - integrationTripwire: 'required', - path: 'link', -}); diff --git a/packages/@luke-ui/react/src/link/link.browser.test.tsx b/packages/@luke-ui/react/src/link/link.browser.test.tsx index 1b71be36..7f45a1c1 100644 --- a/packages/@luke-ui/react/src/link/link.browser.test.tsx +++ b/packages/@luke-ui/react/src/link/link.browser.test.tsx @@ -1,17 +1,15 @@ import { expect } from 'vite-plus/test'; import { testIntegration, testUniversalConformance } from '../conformance/helpers.js'; import { render } from '../test-utils/render.js'; -import { componentTestRegistration } from './component-test-registration.js'; import { Link } from './index.js'; testUniversalConformance({ + path: 'link', getTarget: (result) => { const target = result.locator.getByRole('link', { name: 'Settings' }).element(); if (!(target instanceof HTMLElement)) throw new Error('Expected a link.'); return target; }, - name: 'Link', - registration: componentTestRegistration, render: (props = {}) => { return render( @@ -21,7 +19,7 @@ testUniversalConformance({ }, }); -testIntegration(componentTestRegistration, 'Link', async () => { +testIntegration('link', async () => { let pressed = false; const { locator, user } = render( (pressed = true)}>Settings); diff --git a/packages/@luke-ui/react/src/text-field/component-test-registration.ts b/packages/@luke-ui/react/src/text-field/component-test-registration.ts deleted file mode 100644 index 199be1ac..00000000 --- a/packages/@luke-ui/react/src/text-field/component-test-registration.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineComponentTestRegistration } from '../conformance/registrations.js'; - -export const componentTestRegistration = defineComponentTestRegistration({ - conformanceTier: 'field-shaped', - integrationTripwire: 'required', - path: 'text-field', -}); diff --git a/packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx b/packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx index 56a8279c..773b473a 100644 --- a/packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx +++ b/packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx @@ -10,10 +10,10 @@ import { } from '../primitives/input-group/index.js'; import { inputGroupRecipe } from '../primitives/input-group/recipe.css.js'; import { render } from '../test-utils/render.js'; -import { componentTestRegistration } from './component-test-registration.js'; import { TextField } from './index.js'; testFieldShapedConformance({ + path: 'text-field', assertAssociation: (result) => { const input = result.locator.getByRole('textbox', { name: 'Name' }).element(); // oxlint-disable-next-line vitest/no-standalone-expect @@ -24,19 +24,12 @@ testFieldShapedConformance({ if (!(control instanceof HTMLElement)) throw new Error('Expected a text input.'); return control; }, - getTarget: (result) => { - const target = result.container.firstElementChild; - if (!(target instanceof HTMLElement)) throw new Error('Expected a text-field root.'); - return target; - }, - name: 'TextField', - registration: componentTestRegistration, render: (props = {}) => { return render(); }, }); -testIntegration(componentTestRegistration, 'TextField', async () => { +testIntegration('text-field', async () => { let value = ''; const { locator, user } = render( (value = next)} />); const input = locator.getByRole('textbox', { name: 'Name' }); diff --git a/packages/turbo-generators/src/component-creation-plan.test.ts b/packages/turbo-generators/src/component-creation-plan.test.ts index 0da0846c..e5e3b5e0 100644 --- a/packages/turbo-generators/src/component-creation-plan.test.ts +++ b/packages/turbo-generators/src/component-creation-plan.test.ts @@ -35,7 +35,6 @@ describe('createComponentPlan', () => { expect(plan.files.map((file) => file.path).sort()).toEqual([ 'apps/docs/content/docs/components/feedback/status-badge.mdx', 'apps/docs/src/examples/status-badge/basic.tsx', - 'packages/@luke-ui/react/src/status-badge/component-test-registration.ts', 'packages/@luke-ui/react/src/status-badge/index.ts', 'packages/@luke-ui/react/src/status-badge/recipe.css.ts', 'packages/@luke-ui/react/src/status-badge/status-badge.browser.test.tsx', @@ -77,7 +76,7 @@ describe('createComponentPlan', () => { ); }); - it('scaffolds field-shaped conformance registrations', () => { + it('scaffolds field-shaped conformance coverage without a universal target', () => { const plan = createComponentPlan({ conformanceTier: 'field-shaped', docsGroup: 'forms', @@ -87,9 +86,13 @@ describe('createComponentPlan', () => { expect(plan.textFileInserts?.[0]?.lines).toEqual([ "\t['DateField', 'date-field', 'field-shaped', 'none', 'applicable'],", ]); - expect( - plan.files.find((file) => file.path.endsWith('/date-field.browser.test.tsx'))?.contents, - ).toContain('testFieldShapedConformance'); + const browserTest = plan.files.find((file) => + file.path.endsWith('/date-field.browser.test.tsx'), + )?.contents; + expect(browserTest).toContain('testFieldShapedConformance'); + expect(browserTest).toContain("path: 'date-field'"); + expect(browserTest).not.toContain('getTarget'); + expect(browserTest).not.toContain("name: 'DateField'"); }); it('scaffolds integration tripwire coverage when requested', () => { @@ -104,7 +107,7 @@ describe('createComponentPlan', () => { ]); expect( plan.files.find((file) => file.path.endsWith('/action-chip.browser.test.tsx'))?.contents, - ).toContain('testIntegration'); + ).toContain("testIntegration('action-chip', async"); }); it('omits visual coverage when it does not apply', () => { diff --git a/packages/turbo-generators/src/component-creation-plan.ts b/packages/turbo-generators/src/component-creation-plan.ts index ad346011..3c68728b 100644 --- a/packages/turbo-generators/src/component-creation-plan.ts +++ b/packages/turbo-generators/src/component-creation-plan.ts @@ -107,17 +107,6 @@ export function createComponentPlan(input: CreateComponentInput): ComponentCreat }, ]; - if (conformanceTier !== 'none' || integrationTripwire === 'required') { - files.push({ - contents: renderComponentTestRegistration({ - conformanceTier, - integrationTripwire, - name, - }), - path: `packages/@luke-ui/react/src/${name}/component-test-registration.ts`, - }); - } - if (input.visualCoverage !== false) { files.push({ contents: renderVisualTest({ name, pascalName }), @@ -302,9 +291,6 @@ function renderComponentTest(input: { ? [`import { ${helperImports.join(', ')} } from '../conformance/helpers.js';`] : []), "import { render } from '../test-utils/render.js';", - ...(input.conformanceTier !== 'none' || input.integrationTripwire === 'required' - ? ["import { componentTestRegistration } from './component-test-registration.js';"] - : []), `import { ${input.pascalName} } from './index.js';`, ]; @@ -312,24 +298,22 @@ function renderComponentTest(input: { const contract = input.conformanceTier === 'universal' ? `testUniversalConformance({ + path: '${input.name}', getTarget: (result) => { const target = result.container.firstElementChild; if (!(target instanceof HTMLElement)) throw new Error('Expected ${input.pascalName} element.'); return target; }, - name: '${input.pascalName}', - registration: componentTestRegistration, render: (props = {}) => ${renderComponent}, });` : input.conformanceTier === 'field-shaped' ? `testFieldShapedConformance({ + path: '${input.name}', getControl: (result) => { const control = result.container.querySelector('[name="conformance-field"]'); if (!(control instanceof HTMLElement)) throw new Error('Expected a native field control.'); return control; }, - name: '${input.pascalName}', - registration: componentTestRegistration, render: (props = {}) => ${renderComponent}, });` : `test('${input.pascalName} renders its root element', () => { @@ -340,7 +324,7 @@ function renderComponentTest(input: { const integration = input.integrationTripwire === 'required' ? ` -testIntegration(componentTestRegistration, '${input.pascalName}', async () => { +testIntegration('${input.name}', async () => { let clicked = false; const { locator, user } = render( <${input.pascalName} onClick={() => (clicked = true)}>Content, @@ -357,21 +341,6 @@ ${contract}${integration} `; } -function renderComponentTestRegistration(input: { - conformanceTier: ConformanceTier; - integrationTripwire: 'none' | 'required'; - name: string; -}): string { - return `import { defineComponentTestRegistration } from '../conformance/registrations.js'; - -export const componentTestRegistration = defineComponentTestRegistration({ - conformanceTier: '${input.conformanceTier}', - integrationTripwire: '${input.integrationTripwire}', - path: '${input.name}', -}); -`; -} - function renderVisualTest(input: { name: string; pascalName: string }): string { return `import { test } from 'vite-plus/test'; import { render } from '../test-utils/render.js';