diff --git a/packages/@luke-ui/react/src/blockquote/blockquote.browser.test.tsx b/packages/@luke-ui/react/src/blockquote/blockquote.browser.test.tsx new file mode 100644 index 00000000..4661fbce --- /dev/null +++ b/packages/@luke-ui/react/src/blockquote/blockquote.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { Blockquote } from './index.js'; + +testConformance({ + path: 'blockquote', + getTarget: (result) => { + const target = result.container.firstElementChild; + if (!(target instanceof HTMLElement)) throw new Error('Expected a Blockquote element.'); + return target; + }, + render: (props = {}) => render(
Quoted text
), +}); diff --git a/packages/@luke-ui/react/src/code/code.browser.test.tsx b/packages/@luke-ui/react/src/code/code.browser.test.tsx new file mode 100644 index 00000000..29004c2d --- /dev/null +++ b/packages/@luke-ui/react/src/code/code.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { Code } from './index.js'; + +testConformance({ + path: 'code', + getTarget: (result) => { + const target = result.container.firstElementChild; + if (!(target instanceof HTMLElement)) throw new Error('Expected a Code element.'); + return target; + }, + render: (props = {}) => render(npm install), +}); diff --git a/packages/@luke-ui/react/src/conformance/manifest.ts b/packages/@luke-ui/react/src/conformance/manifest.ts index 370df091..f127db1c 100644 --- a/packages/@luke-ui/react/src/conformance/manifest.ts +++ b/packages/@luke-ui/react/src/conformance/manifest.ts @@ -12,36 +12,42 @@ export type ComponentTestManifestEntry = { // Keep this list explicit. An empty `conformance` list is a deliberate exception, not an omission. export const componentTestManifest = [ - ['Blockquote', 'blockquote', [], 'none', 'none'], + ['Blockquote', 'blockquote', ['dom'], 'none', 'none'], ['Box', 'box', ['dom'], 'none', 'applicable'], ['Button', 'button', ['dom'], 'required', 'applicable'], - ['Button primitive', 'primitives/button', [], 'none', 'none'], + ['Button primitive', 'primitives/button', ['dom'], 'none', 'none'], // Fields take `inputRef` and RAC moves `id` onto the control, so they cannot satisfy `dom`. ['Checkbox', 'checkbox', ['field'], 'required', 'applicable'], + // Multi-part field root. RAC moves `id` onto the control. ['Checkbox primitive', 'primitives/checkbox', [], 'none', 'none'], - ['Code', 'code', [], 'none', 'none'], + ['Code', 'code', ['dom'], 'none', 'none'], // Fields take `inputRef` and RAC moves `id` onto the control, so they cannot satisfy `dom`. ['ComboboxField', 'combobox-field', ['field'], 'required', 'applicable'], + // Multi-part collection. No single root receives the public DOM props. ['Combobox primitive', 'primitives/combobox', [], 'none', 'none'], - ['Em', 'em', [], 'none', 'none'], - ['Emoji', 'emoji', [], 'none', 'applicable'], + ['Em', 'em', ['dom'], 'none', 'none'], + ['Emoji', 'emoji', ['dom'], 'none', 'applicable'], + // Multi-part composition. No single root is the documented element. ['Field primitive', 'primitives/field', [], 'none', 'none'], - ['Heading', 'heading', [], 'none', 'applicable'], + ['Heading', 'heading', ['dom'], 'none', 'applicable'], + // Public props are a closed set. `ref` and `data-*` are not forwarded. ['Icon', 'icon', [], 'none', 'applicable'], ['IconButton', 'icon-button', ['dom'], 'required', 'applicable'], + // Multi-part composition. No single root is the documented element. ['Input group primitive', 'primitives/input-group', [], 'none', 'none'], - ['Kbd', 'kbd', [], 'none', 'none'], + ['Kbd', 'kbd', ['dom'], 'none', 'none'], ['Link', 'link', ['dom'], 'required', 'applicable'], - ['LoadingSkeleton', 'loading-skeleton', [], 'none', 'applicable'], - ['LoadingSpinner', 'loading-spinner', [], 'none', 'applicable'], - ['Numeral', 'numeral', [], 'none', 'applicable'], - ['Quote', 'quote', [], 'none', 'none'], - ['Strong', 'strong', [], 'none', 'none'], - ['Text', 'text', [], 'none', 'applicable'], + ['LoadingSkeleton', 'loading-skeleton', ['dom'], 'none', 'applicable'], + ['LoadingSpinner', 'loading-spinner', ['dom'], 'none', 'applicable'], + ['Numeral', 'numeral', ['dom'], 'none', 'applicable'], + ['Quote', 'quote', ['dom'], 'none', 'none'], + ['Strong', 'strong', ['dom'], 'none', 'none'], + ['Text', 'text', ['dom'], 'none', 'applicable'], // Fields take `inputRef` and RAC moves `id` onto the control, so they cannot satisfy `dom`. ['TextField', 'text-field', ['field'], 'required', 'applicable'], + // Theme utilities, not a component with a rendered root. ['Theme', 'theme', [], 'none', 'none'], - ['VisuallyHidden', 'visually-hidden', [], 'none', 'none'], + ['VisuallyHidden', 'visually-hidden', ['dom'], 'none', 'none'], ].map(([name, path, conformance, integrationTripwire, visualApplicability]) => ({ conformance, integrationTripwire, diff --git a/packages/@luke-ui/react/src/em/em.browser.test.tsx b/packages/@luke-ui/react/src/em/em.browser.test.tsx new file mode 100644 index 00000000..f15e9e06 --- /dev/null +++ b/packages/@luke-ui/react/src/em/em.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { Em } from './index.js'; + +testConformance({ + path: 'em', + getTarget: (result) => { + const target = result.container.firstElementChild; + if (!(target instanceof HTMLElement)) throw new Error('Expected an Em element.'); + return target; + }, + render: (props = {}) => render(stressed), +}); diff --git a/packages/@luke-ui/react/src/emoji/emoji.browser.test.tsx b/packages/@luke-ui/react/src/emoji/emoji.browser.test.tsx new file mode 100644 index 00000000..aa3a08f0 --- /dev/null +++ b/packages/@luke-ui/react/src/emoji/emoji.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { Emoji } from './index.js'; + +testConformance({ + path: 'emoji', + getTarget: (result) => { + const target = result.locator.getByRole('img', { name: 'Celebration' }).element(); + if (!(target instanceof HTMLElement)) throw new Error('Expected an Emoji element.'); + return target; + }, + render: (props = {}) => render(), +}); diff --git a/packages/@luke-ui/react/src/heading/heading.browser.test.tsx b/packages/@luke-ui/react/src/heading/heading.browser.test.tsx index 25e3dce6..a1b57b24 100644 --- a/packages/@luke-ui/react/src/heading/heading.browser.test.tsx +++ b/packages/@luke-ui/react/src/heading/heading.browser.test.tsx @@ -1,7 +1,18 @@ import { expect, test } from 'vite-plus/test'; +import { testConformance } from '../conformance/helpers.js'; import { render } from '../test-utils/render.js'; import { Heading, HeadingLevels, useHeadingLevel } from './index.js'; +testConformance({ + path: 'heading', + getTarget: (result) => { + const target = result.locator.getByRole('heading').element(); + if (!(target instanceof HTMLElement)) throw new Error('Expected a Heading element.'); + return target; + }, + render: (props = {}) => render(Section title), +}); + test('keeps semantic heading level independent of visual type style', () => { const { locator } = render( diff --git a/packages/@luke-ui/react/src/kbd/kbd.browser.test.tsx b/packages/@luke-ui/react/src/kbd/kbd.browser.test.tsx new file mode 100644 index 00000000..53701230 --- /dev/null +++ b/packages/@luke-ui/react/src/kbd/kbd.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { Kbd } from './index.js'; + +testConformance({ + path: 'kbd', + getTarget: (result) => { + const target = result.container.firstElementChild; + if (!(target instanceof HTMLElement)) throw new Error('Expected a Kbd element.'); + return target; + }, + render: (props = {}) => render(⌘), +}); diff --git a/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.browser.test.tsx b/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.browser.test.tsx new file mode 100644 index 00000000..22bf53d6 --- /dev/null +++ b/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { LoadingSkeleton } from './index.js'; + +testConformance({ + path: 'loading-skeleton', + getTarget: (result) => { + const target = result.container.firstElementChild; + if (!(target instanceof HTMLElement)) throw new Error('Expected a LoadingSkeleton element.'); + return target; + }, + render: (props = {}) => render(Loading copy), +}); diff --git a/packages/@luke-ui/react/src/loading-spinner/loading-spinner.browser.test.tsx b/packages/@luke-ui/react/src/loading-spinner/loading-spinner.browser.test.tsx new file mode 100644 index 00000000..dceceb30 --- /dev/null +++ b/packages/@luke-ui/react/src/loading-spinner/loading-spinner.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { LoadingSpinner } from './index.js'; + +testConformance({ + path: 'loading-spinner', + getTarget: (result) => { + const target = result.locator.getByRole('status').element(); + if (!(target instanceof HTMLElement)) throw new Error('Expected a LoadingSpinner element.'); + return target; + }, + render: (props = {}) => render(), +}); diff --git a/packages/@luke-ui/react/src/numeral/numeral.browser.test.tsx b/packages/@luke-ui/react/src/numeral/numeral.browser.test.tsx new file mode 100644 index 00000000..285e75c7 --- /dev/null +++ b/packages/@luke-ui/react/src/numeral/numeral.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { Numeral } from './index.js'; + +testConformance({ + path: 'numeral', + getTarget: (result) => { + const target = result.container.firstElementChild; + if (!(target instanceof HTMLElement)) throw new Error('Expected a Numeral element.'); + return target; + }, + render: (props = {}) => render(), +}); diff --git a/packages/@luke-ui/react/src/primitives/button/button.browser.test.tsx b/packages/@luke-ui/react/src/primitives/button/button.browser.test.tsx new file mode 100644 index 00000000..13a96cf6 --- /dev/null +++ b/packages/@luke-ui/react/src/primitives/button/button.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../../conformance/helpers.js'; +import { render } from '../../test-utils/render.js'; +import { Button } from './index.js'; + +testConformance({ + path: 'primitives/button', + getTarget: (result) => { + const target = result.locator.getByRole('button').element(); + if (!(target instanceof HTMLElement)) throw new Error('Expected a button primitive.'); + return target; + }, + render: (props = {}) => render(), +}); diff --git a/packages/@luke-ui/react/src/quote/quote.browser.test.tsx b/packages/@luke-ui/react/src/quote/quote.browser.test.tsx new file mode 100644 index 00000000..98c6c865 --- /dev/null +++ b/packages/@luke-ui/react/src/quote/quote.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { Quote } from './index.js'; + +testConformance({ + path: 'quote', + getTarget: (result) => { + const target = result.container.firstElementChild; + if (!(target instanceof HTMLElement)) throw new Error('Expected a Quote element.'); + return target; + }, + render: (props = {}) => render(short quote), +}); diff --git a/packages/@luke-ui/react/src/strong/strong.browser.test.tsx b/packages/@luke-ui/react/src/strong/strong.browser.test.tsx new file mode 100644 index 00000000..a852439e --- /dev/null +++ b/packages/@luke-ui/react/src/strong/strong.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { Strong } from './index.js'; + +testConformance({ + path: 'strong', + getTarget: (result) => { + const target = result.container.firstElementChild; + if (!(target instanceof HTMLElement)) throw new Error('Expected a Strong element.'); + return target; + }, + render: (props = {}) => render(important), +}); diff --git a/packages/@luke-ui/react/src/text/text.browser.test.tsx b/packages/@luke-ui/react/src/text/text.browser.test.tsx new file mode 100644 index 00000000..674c002a --- /dev/null +++ b/packages/@luke-ui/react/src/text/text.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { Text } from './index.js'; + +testConformance({ + path: 'text', + getTarget: (result) => { + const target = result.container.firstElementChild; + if (!(target instanceof HTMLElement)) throw new Error('Expected a Text element.'); + return target; + }, + render: (props = {}) => render(Body copy), +}); diff --git a/packages/@luke-ui/react/src/visually-hidden/visually-hidden.browser.test.tsx b/packages/@luke-ui/react/src/visually-hidden/visually-hidden.browser.test.tsx new file mode 100644 index 00000000..11b44990 --- /dev/null +++ b/packages/@luke-ui/react/src/visually-hidden/visually-hidden.browser.test.tsx @@ -0,0 +1,13 @@ +import { testConformance } from '../conformance/helpers.js'; +import { render } from '../test-utils/render.js'; +import { VisuallyHidden } from './index.js'; + +testConformance({ + path: 'visually-hidden', + getTarget: (result) => { + const target = result.container.firstElementChild; + if (!(target instanceof HTMLElement)) throw new Error('Expected a VisuallyHidden element.'); + return target; + }, + render: (props = {}) => render(Hidden label), +});