110 lines
6.5 KiB
JavaScript
110 lines
6.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import React from 'react';
|
|
import { renderToStaticMarkup as render } from 'react-dom/server';
|
|
import { build } from 'vite';
|
|
import path from 'node:path';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
// Load the actual TSX through the project's Vite transforms; assertions cover
|
|
// consumer contracts rather than matching implementation text.
|
|
const cache = new Map();
|
|
const load = async source => {
|
|
if (!cache.has(source)) {
|
|
const name = path.basename(source, '.tsx');
|
|
await build({ configFile: false, logLevel: 'silent', build: { outDir: `_local/component-test/${name}`, emptyOutDir: false, lib: { entry: source, formats: ['es'], fileName: 'module' }, rolldownOptions: { external: id => !id.startsWith('.') && !path.isAbsolute(id) } } });
|
|
cache.set(source, import(pathToFileURL(path.resolve(`_local/component-test/${name}/module.js`)).href));
|
|
}
|
|
return cache.get(source);
|
|
};
|
|
const element = React.createElement;
|
|
|
|
test('FilterBar list composition is explicit and leaves default consumers intact', async () => {
|
|
const { FilterBar } = await load('packages/ui/src/patterns/filter-bar/filter-bar.tsx');
|
|
const original = render(element(FilterBar, { id: 'default', 'aria-label': 'Default' }, 'Content'));
|
|
assert.doesNotMatch(original, /data-layout/);
|
|
for (const layout of ['list-basic', 'list-extended']) {
|
|
const html = render(element(FilterBar, { layout, id: layout, style: { marginTop: 12 } }, 'Content'));
|
|
assert.match(html, new RegExp(`data-layout="${layout}"`));
|
|
assert.match(html, /margin-top:12px/);
|
|
assert.match(html, /Content/);
|
|
assert.doesNotMatch(html, / layout=/);
|
|
}
|
|
});
|
|
|
|
test('C2 Checkbox asChild preserves the consumer root and content, including checked indicator', async () => {
|
|
const { Checkbox } = await load('packages/ui/src/components/ui/checkbox.tsx');
|
|
const html = render(element(Checkbox, { asChild: true, checked: true }, element('button', { id: 'consumer-check' }, element('span', null, 'Consumer child'))));
|
|
assert.match(html, /id="consumer-check"/);
|
|
assert.match(html, /Consumer child/);
|
|
assert.match(html, /data-slot="checkbox-indicator"/);
|
|
assert.match(html, /role="checkbox"/);
|
|
});
|
|
test('C2 Progress asChild preserves the consumer root/content and progress semantics', async () => {
|
|
const { Progress } = await load('packages/ui/src/components/ui/progress.tsx');
|
|
const html = render(element(Progress, { asChild: true, value: 35 }, element('section', { id: 'consumer-progress' }, 'Consumer progress')));
|
|
assert.match(html, /<section[^>]*id="consumer-progress"/);
|
|
assert.match(html, /Consumer progress/);
|
|
assert.match(html, /aria-valuenow="35"/);
|
|
assert.match(html, /data-slot="progress-indicator"/);
|
|
});
|
|
test('C3 Toggle Group combines consumer styles with the requested spacing', async () => {
|
|
const { ToggleGroup } = await load('packages/ui/src/components/ui/toggle-group.tsx');
|
|
const html = render(element(ToggleGroup, { type: 'multiple', spacing: 3, style: { color: 'red' } }));
|
|
assert.match(html, /--hm-toggle-group-gap:12px/);
|
|
assert.match(html, /color:red/);
|
|
});
|
|
test('C4 every railway state forwards DOM identity, ARIA and style to the final root', async () => {
|
|
const { RouteLine } = await load('packages/center-graphic/src/components/CenterSpatialInformation.tsx');
|
|
for (const state of ['existing', 'planned', 'underConstruction']) {
|
|
const html = render(element(RouteLine, { routeClass: 'railway', state, id: 'route', 'aria-label': 'Railway', style: { opacity: 0.7 } }));
|
|
assert.match(html, /id="route"/);
|
|
assert.match(html, /aria-label="Railway"/);
|
|
assert.match(html, /opacity:0.7/);
|
|
assert.equal((html.match(/id="route"/g) ?? []).length, 1);
|
|
}
|
|
});
|
|
test('PT1 two pagination instances have distinct labels and matching references', async () => {
|
|
const { DataTablePagination, useDataTable } = await load('packages/ui/src/patterns/data-table/data-table.tsx');
|
|
function Tables() {
|
|
const table = useDataTable({ columns: [{ accessorKey: 'name' }], data: [{ name: 'one' }] });
|
|
return element(React.Fragment, null, element(DataTablePagination, { table }), element(DataTablePagination, { table }));
|
|
}
|
|
const html = render(element(Tables));
|
|
const ids = [...html.matchAll(/<span id="([^"]+)">페이지당/g)].map(match => match[1]);
|
|
assert.equal(ids.length, 2);
|
|
assert.equal(new Set(ids).size, 2);
|
|
for (const id of ids) assert.ok(html.includes(`aria-labelledby="${id}"`));
|
|
});
|
|
test('PT2 readOnly and disabled searches expose no mutating clear control', async () => {
|
|
const { FilterBarSearch } = await load('packages/ui/src/patterns/filter-bar/filter-bar.tsx');
|
|
for (const protection of [{ readOnly: true }, { disabled: true }]) {
|
|
const html = render(element(FilterBarSearch, { value: 'Keep', onValueChange() { throw Error('must not mutate'); }, ...protection }));
|
|
assert.doesNotMatch(html, /data-slot="filter-bar-search-clear"/);
|
|
assert.match(html, /value="Keep"/);
|
|
}
|
|
assert.match(render(element(FilterBarSearch, { value: 'Editable', onValueChange() {} })), /data-slot="filter-bar-search-clear"/);
|
|
});
|
|
test('C5 repeated popup triggers have unique generated IDs and retain consumer identity', async () => {
|
|
const { Combobox } = await load('packages/ui/src/components/ui/combobox.tsx');
|
|
const { DatePicker } = await load('packages/ui/src/components/ui/date-picker.tsx');
|
|
const html = render(element(React.Fragment, null,
|
|
element(Combobox, { options: [], 'aria-label': 'Department' }),
|
|
element(Combobox, { options: [], 'aria-label': 'Owner' }),
|
|
element(DatePicker, { 'aria-label': 'Start' }), element(DatePicker, { 'aria-label': 'End' }),
|
|
element(DatePicker, { id: 'consumer-date', 'aria-labelledby': 'date-label' })));
|
|
const ids = [...html.matchAll(/<button[^>]*id="([^"]+)"/g)].map(match => match[1]);
|
|
assert.equal(ids.length, 5);
|
|
assert.equal(new Set(ids).size, 5);
|
|
assert.ok(ids.includes('consumer-date'));
|
|
assert.match(html, /aria-labelledby="date-label"/);
|
|
});
|
|
test('C2 default roots and external Checkbox form remain backward compatible', async () => {
|
|
const { Checkbox } = await load('packages/ui/src/components/ui/checkbox.tsx');
|
|
const { Progress } = await load('packages/ui/src/components/ui/progress.tsx');
|
|
assert.match(render(element(Checkbox, { form: 'external', name: 'approved', checked: true })), /<button[^>]*form="external"/);
|
|
const progress = render(element(Progress, { value: 50, max: 200, 'aria-label': 'Upload' }));
|
|
assert.match(progress, /aria-valuemax="200"/);
|
|
assert.match(progress, /translateX\(-75%\)/);
|
|
});
|