feat: integrate latest HMDesign design system updates
This commit is contained in:
1 parent
99faec189e
commit
1c4e0bb9a1
209 files changed
+8845
-552
No files matched your search
@@ -8,6 +8,193 @@ import { createHash } from 'node:crypto';
|
||||
import { performance } from 'node:perf_hooks';
|
||||
import test from 'node:test';
|
||||
|
||||
test('64. finite conditional templates and concatenation validate every Token branch', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; const name = location.hash ? "fg-primary" : "bg-content"; export default () => <Button style={{ color: `var(--hm-sem-${name})` }}/>' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(!r.issues.some(i => i.code.startsWith('TOKEN')));
|
||||
assert.ok(r.tokenEvidence[0].references.some(t => t.name === '--hm-sem-fg-primary'));
|
||||
});
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; const name = location.hash ? "fg-primary" : "missing"; export default () => <Button style={{ color: "var(--hm-sem-" + name + ")" }}/>' }, root => {
|
||||
assert.ok(run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'TOKEN-001' && i.severity === 'FAIL'));
|
||||
});
|
||||
});
|
||||
test('65. fallback never hides unknown Token and declared local variables are inventoried', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button style={{ color: "var(--hm-missing, #fff)" }}/>', 'src/styles.css': '@import "./tokens.css"; :root { --local: var(--hm-sem-fg-primary); } .content { color: var(--local); }' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(r.issues.some(i => i.code === 'TOKEN-001' && i.severity === 'FAIL'));
|
||||
assert.ok(r.tokenEvidence[0].references.some(t => t.name === '--local' && t.status === 'PASS'));
|
||||
});
|
||||
});
|
||||
test('66. external props, style spread and className inputs retain site uncertainty', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default ({ color, styles, classes }) => <Button className={classes} style={{ color, ...styles }}/>' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
const sites = r.tokenEvidence[0].dynamic;
|
||||
assert.ok(sites.some(s => s.kind === 'CLASS_NAME'));
|
||||
assert.ok(sites.some(s => s.kind === 'STYLE_VALUE'));
|
||||
assert.ok(sites.some(s => s.kind === 'STYLE_SPREAD'));
|
||||
assert.equal(r.staticStatus, 'NOT VERIFIED');
|
||||
});
|
||||
});
|
||||
test('67. shadowed constants and partial dynamic templates never become fake literal FAILs', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; const name="fg-primary"; export default ({ name }) => <Button style={{ color: `var(--hm-sem-${name})` }}/>' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(r.issues.some(i => i.code === 'TOKEN-002'));
|
||||
assert.ok(!r.issues.some(i => i.code === 'TOKEN-001'));
|
||||
});
|
||||
});
|
||||
test('68. explicit Component HEX RGB HSL spacing and radius overrides fail', () => {
|
||||
for (const value of ['#abcdef', 'rgb(1,2,3)', 'hsl(1 2% 3%)']) fixture({ 'src/styles.css': `@import "./tokens.css"; [data-slot="button"] { color: ${value}; }` }, root => {
|
||||
assert.ok(run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001' && i.severity === 'FAIL'));
|
||||
});
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button className="rounded-[13px]" style={{ padding: "17px" }}/>' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.equal(r.styleEvidence.staticStatus, 'FAIL');
|
||||
assert.equal(r.status, 'FAIL');
|
||||
});
|
||||
});
|
||||
test('69. documented Token override keeps static measurements and intent separate', () => {
|
||||
fixture({ 'src/styles.css': '@import "./tokens.css"; [data-slot="badge"] { --hm-badge-outline-text: var(--hm-sem-fg-primary); }' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.equal(r.styleEvidence.staticStatus, 'PASS');
|
||||
assert.equal(r.styleEvidence.designIntentStatus, 'NOT VERIFIED');
|
||||
assert.equal(r.styleEvidence.runtimeStatus, 'NOT VERIFIED');
|
||||
assert.ok(r.issues.some(i => i.code === 'REUSE-003'));
|
||||
});
|
||||
});
|
||||
test('70. general layout and global CSS are inventoried without claiming a Component violation', () => {
|
||||
fixture({ 'src/styles.css': '@import "./tokens.css"; body { margin: 0; } .content { padding: 17px; color: #abcdef; }' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(!r.issues.some(i => i.code === 'STYLE-001'));
|
||||
assert.ok(r.styleEvidence.checks.some(c => c.scope === 'GLOBAL'));
|
||||
assert.equal(r.styleEvidence.designIntentStatus, 'NOT VERIFIED');
|
||||
});
|
||||
});
|
||||
test('71. disconnected and PARTIAL CSS do not expand style findings; Storybook stays reference', () => {
|
||||
fixture({ 'src/disconnected.css': '[data-slot="button"] { color: #abcdef; }' }, root => {
|
||||
assert.ok(!run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
storyFixture({ 'src/Example.stories.tsx': 'import { Button } from "@hm-design/ui"; export const All = () => <Button style={{ color: "#abcdef" }}/>' }, root => {
|
||||
assert.ok(!run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
});
|
||||
test('72. malformed var() and numeric / constant Component overrides remain FAIL', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; const color="#abcdef"; export default () => <Button style={{ color, borderRadius: 13 }}/>', 'src/styles.css': '@import "./tokens.css"; .content { color: var(--hm-sem-fg-primary broken); }' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(r.issues.some(i => i.code === 'TOKEN-003'));
|
||||
assert.ok(r.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
});
|
||||
test('73. var fallback does not hide a hardcoded color elsewhere in Component styling', () => {
|
||||
fixture({ 'src/styles.css': '@import "./tokens.css"; [data-slot="badge"] { background: color-mix(in srgb, #abcdef 50%, var(--hm-sem-bg-content)); border-top-left-radius: -1.5px; }' }, root => {
|
||||
assert.equal(run(root, ['--scope', 'FULL_SCREEN']).report.styleEvidence.staticStatus, 'FAIL');
|
||||
});
|
||||
fixture({ 'src/App.tsx': 'import { Badge } from "@hm-design/ui"; export default () => <Badge style={{ color: "var(--hm-sem-fg-primary, #fff)" }}/>' }, root => {
|
||||
assert.ok(!run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
});
|
||||
test('74. unresolved dynamic branches do not hide a known hardcoded Component style', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default ({ value, classes }) => <Button className={`bg-[#abcdef] ${classes}`} style={{ color: value ? "#abcdef" : value }}/>' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(r.issues.some(i => i.code === 'STYLE-001'));
|
||||
assert.ok(r.issues.some(i => i.code === 'TOKEN-002'));
|
||||
assert.equal(r.status, 'FAIL');
|
||||
});
|
||||
});
|
||||
test('75. complete malformed var expressions stay FAIL and style findings appear in reuse group', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button style={{ color: "var(--hm-missing", padding: 17 }}/>' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(r.issues.some(i => i.code === 'TOKEN-003' && i.severity === 'FAIL'));
|
||||
assert.equal(r.checkedGroups.find(g => g.name === 'Component Reuse').status, 'FAIL');
|
||||
assert.equal(r.status, 'FAIL');
|
||||
});
|
||||
});
|
||||
|
||||
test('52. connected public Theme catches unknown HM references in JSX and CSS', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button className="text-[var(--hm-sem-text-made-up)]"/>;', 'src/styles.css': '@import "./tokens.css"; .custom { border-color: var(--hm-sem-stroke-divider); }' }, root => {
|
||||
const { report, status } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.equal(status, 1);
|
||||
assert.equal(report.issues.filter(i => i.code === 'TOKEN-001').length, 2);
|
||||
assert.equal(report.checkedGroups.find(g => g.name === 'Theme Token').status, 'FAIL');
|
||||
});
|
||||
});
|
||||
test('53. valid Theme, shorthand and declared consumer variables are preserved', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button className="text-(--hm-sem-fg-primary)" style={{ color: "var(--hm-app-color)" }}/>;', 'src/styles.css': '@import "./tokens.css"; :root { --hm-app-color: var(--hm-sem-action-primary); }' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.equal(report.status, 'PASS');
|
||||
assert.ok(report.tokenEvidence[0].references.some(r => r.name === '--hm-sem-fg-primary'));
|
||||
});
|
||||
});
|
||||
test('54. disconnected CSS cannot supply a Theme variable declaration', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button style={{ color: "var(--hm-external-token)" }}/>;', 'src/disconnected.css': ':root { --hm-external-token: green; }' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.ok(report.issues.some(i => i.code === 'TOKEN-001'));
|
||||
assert.ok(!report.checkedFiles.includes('src/disconnected.css'));
|
||||
});
|
||||
});
|
||||
test('55. PARTIAL limits Token findings and keeps ancestor declarations', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Input } from "@hm-design/ui"; import Other from "./Other"; export default () => <><Input style={{ color: "var(--hm-sem-fg-primary)" }}/><Other/></>;', 'src/Other.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button className="text-[var(--hm-undefined)]"/>;' }, root => {
|
||||
assert.equal(run(root, ['--scope', 'FULL_SCREEN']).report.status, 'FAIL');
|
||||
assert.equal(run(root, ['--scope', 'PARTIAL', '--files', 'src/App.tsx']).report.status, 'FAIL'); // imported dependency included
|
||||
assert.equal(run(root, ['--scope', 'PARTIAL', '--files', 'src/styles.css']).report.status, 'PASS');
|
||||
});
|
||||
});
|
||||
test('56. missing Status CSS cannot borrow its tokens from the Theme', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button style={{ color: "var(--hm-dashboard-status-active-color)" }}/>;' }, root => {
|
||||
assert.ok(run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'TOKEN-001'));
|
||||
});
|
||||
});
|
||||
test('57. dynamic HM variables remain uncertain without hiding a literal failure', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; const key = location.hash; export default () => <Button style={{ color: `var(--hm-${key})`, background: "var(--hm-unknown)" }}/>;' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.equal(report.status, 'FAIL');
|
||||
assert.ok(report.issues.some(i => i.code === 'TOKEN-002' && i.severity === 'NOT VERIFIED'));
|
||||
});
|
||||
});
|
||||
test('58. staticStatus distinguishes proven React bootstrap from browser evidence', () => {
|
||||
fixture({ 'src/main.tsx': 'import { createRoot } from "react-dom/client"; import App from "./App"; import "./styles.css"; createRoot(document.getElementById("root")).render(<App/>);', 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button>OK</Button>;' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN', '--result', 'index.html']);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.equal(report.staticStatus, 'PASS');
|
||||
assert.equal(report.runtimeStatus, 'NOT VERIFIED');
|
||||
assert.ok(report.issues.some(i => i.code === 'RENDER-002'));
|
||||
assert.ok(report.checkedGroups.every(g => g.basis && Array.isArray(g.evidence)));
|
||||
});
|
||||
});
|
||||
test('59. staticStatus keeps CSS intent uncertainty and actual failures', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button/>;', 'src/styles.css': '@import "./tokens.css"; [data-slot="button"] { color: var(--hm-sem-fg-primary); }' }, root => {
|
||||
assert.equal(run(root, ['--scope', 'FULL_SCREEN']).report.staticStatus, 'NOT VERIFIED');
|
||||
});
|
||||
});
|
||||
test('60. Storybook token scope is isolated per Story with shared preview', () => {
|
||||
storyFixture({ 'src/Example.stories.tsx': 'import { Button } from "@hm-design/ui"; export const All = () => <Button style={{ color: "var(--hm-story-only)" }}/>;', 'src/Other.stories.tsx': 'import "./other.css"; export const All = () => <div/>;', 'src/other.css': ':root { --hm-story-only: green; }' }, root => {
|
||||
const { report } = run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN']);
|
||||
assert.ok(report.issues.some(i => i.code === 'TOKEN-001' && i.file === 'src/Example.stories.tsx'));
|
||||
});
|
||||
});
|
||||
test('61. native role widgets require provenance review while content HTML stays allowed', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Card } from "@hm-design/ui"; export default () => <Card><div role="button" tabIndex={0}>Custom</div><p>Content</p></Card>;' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.equal(report.issues.filter(i => i.code === 'REUSE-006').length, 1);
|
||||
assert.ok(report.issues.every(i => i.severity !== 'FAIL'));
|
||||
});
|
||||
});
|
||||
test('62. an incomplete CSS graph cannot prove an HM variable is absent', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button style={{ color: "var(--hm-external-color)" }}/>;', 'src/styles.css': '@import "./tokens.css"; @import "./unresolved.css";' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(report.issues.some(i => i.code === 'TOKEN-001' && i.severity === 'NOT VERIFIED'));
|
||||
});
|
||||
});
|
||||
test('63. early PARTIAL scope diagnostics survive fingerprint reporting', () => {
|
||||
fixture({ 'src/App.tsx': 'export default () => <div/>;' }, root => {
|
||||
const { report, status } = run(root, ['--scope', 'PARTIAL']);
|
||||
assert.equal(status, 0);
|
||||
assert.ok(report.issues.some(i => i.code === 'SCOPE-002'));
|
||||
assert.equal(report.staticStatus, 'NOT VERIFIED');
|
||||
});
|
||||
});
|
||||
|
||||
const guard = fileURLToPath(new URL('./ai-generation-guard.mjs', import.meta.url));
|
||||
function fixture(files, callback) {
|
||||
const tempRoot = fs.realpathSync(os.tmpdir());
|
||||
@@ -214,3 +401,463 @@ test('14. wrappers and inline HTML strings are not falsely marked FAIL; unused b
|
||||
assert.ok(run(root, ['--scope', 'PARTIAL', '--files', 'src/App.tsx']).report.issues.some(i => i.code === 'REUSE-002'));
|
||||
});
|
||||
});
|
||||
|
||||
const dashboardSource = `import { DashboardLayout, DashboardHeader, DashboardSidebar } from "@hm-design/ui";
|
||||
export function Screen({ mode = "compact" }) {
|
||||
const compact = mode === "compact";
|
||||
const reference = <DashboardSidebar>Reference</DashboardSidebar>;
|
||||
return <DashboardLayout header={<DashboardHeader/>} sidebar={compact ? undefined : reference}/>;
|
||||
}`;
|
||||
const dashboardFixture = (mode, callback) => fixture({
|
||||
'src/Screen.tsx': dashboardSource,
|
||||
'src/App.tsx': `import { Screen } from "./Screen"; export default () => <Screen ${mode}/>;`,
|
||||
}, callback);
|
||||
const dashArgs = ['--scope', 'FULL_SCREEN', '--mode', 'DASHBOARD_ONLY'];
|
||||
|
||||
test('15. compact prop proves a preserved Reference Sidebar value is not rendered', () => {
|
||||
dashboardFixture('mode="compact"', root => {
|
||||
const { report } = run(root, dashArgs);
|
||||
assert.equal(report.status, 'PASS');
|
||||
assert.ok(!report.issues.some(i => i.code.startsWith('NAV')));
|
||||
});
|
||||
});
|
||||
test('16. the same Reference branch fails when selected; no broad Sidebar exception', () => {
|
||||
dashboardFixture('mode="header-sidebar"', root => {
|
||||
const { status, report } = run(root, dashArgs);
|
||||
assert.equal(status, 1);
|
||||
assert.ok(report.issues.some(i => i.code === 'NAV-001' && i.severity === 'FAIL'));
|
||||
});
|
||||
});
|
||||
test('17. unknown conditional Sidebar stays NOT VERIFIED and cannot PASS', () => {
|
||||
dashboardFixture('mode={window.mode}', root => {
|
||||
const { report } = run(root, dashArgs);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(report.issues.some(i => i.code === 'NAV-001' && i.severity === 'NOT VERIFIED'));
|
||||
assert.ok(report.issues.some(i => i.code === 'NAV-005'));
|
||||
});
|
||||
});
|
||||
test('18. default props and aliased/barrel component calls preserve compact evidence', () => {
|
||||
fixture({
|
||||
'src/Screen.tsx': dashboardSource,
|
||||
'src/barrel.ts': 'export { Screen as Board } from "./Screen";',
|
||||
'src/App.tsx': 'import { Board } from "./barrel"; export default () => <Board/>;',
|
||||
}, root => assert.equal(run(root, dashArgs).report.status, 'PASS'));
|
||||
});
|
||||
test('19. multiple call sites retain the violating Reference branch', () => {
|
||||
fixture({
|
||||
'src/Screen.tsx': dashboardSource,
|
||||
'src/App.tsx': 'import { Screen } from "./Screen"; export default () => <><Screen mode="compact"/><Screen mode="sidebar"/></>;',
|
||||
}, root => assert.equal(run(root, dashArgs).report.status, 'FAIL'));
|
||||
});
|
||||
test('20. JSX ternary and logical literal branches: true fails, false is absent', () => {
|
||||
for (const [expression, expected] of [['true && <DashboardSidebar/>', 'FAIL'], ['false && <DashboardSidebar/>', 'PASS'], ['false ? <DashboardSidebar/> : null', 'PASS'], ['true ? <DashboardSidebar/> : null', 'FAIL']]) {
|
||||
fixture({ 'src/App.tsx': `import { DashboardHeader, DashboardSidebar } from "@hm-design/ui"; export default () => <><DashboardHeader/>{${expression}}</>;` }, root => assert.equal(run(root, dashArgs).report.status, expected, expression));
|
||||
}
|
||||
});
|
||||
test('21. spread, mutable conditions and opaque render callbacks cannot conceal Sidebar', () => {
|
||||
for (const source of [
|
||||
'import { Screen } from "./Screen"; export default () => <Screen {...window.props}/>;',
|
||||
'import { Screen } from "./Screen"; export default () => <Screen mode={window.x ? "compact" : "sidebar"}/>;',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => { let show = false; show = window.show; return show && <DashboardSidebar/>; };',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => <External render={() => <DashboardSidebar/>}/>;',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => { const side = <DashboardSidebar/>; return window.forward(side); };',
|
||||
]) fixture({ 'src/Screen.tsx': dashboardSource, 'src/App.tsx': source }, root => assert.notEqual(run(root, dashArgs).report.status, 'PASS', source));
|
||||
});
|
||||
test('22. new mixed-navigation screen fails; PARTIAL retains selected Sidebar failures', () => {
|
||||
fixture({ 'src/App.tsx': 'import { GNB, GNBMobileMenu, LNB, DashboardSidebar } from "@hm-design/ui"; export default () => <><GNB><GNBMobileMenu/></GNB><LNB/><DashboardSidebar/></>;' }, root => {
|
||||
const args = ['--mode', 'GNB_ONLY'];
|
||||
for (const scope of [['--scope', 'FULL_SCREEN'], ['--scope', 'PARTIAL', '--files', 'src/App.tsx']]) {
|
||||
const { report } = run(root, [...scope, ...args]);
|
||||
assert.equal(report.status, 'FAIL');
|
||||
for (const code of ['NAV-001', 'NAV-002']) assert.ok(report.issues.some(i => i.code === code && i.severity === 'FAIL'));
|
||||
}
|
||||
});
|
||||
});
|
||||
const storyFixture = (extra, callback) => fixture({
|
||||
'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button/>;',
|
||||
'.storybook/main.ts': 'export default { stories: ["../src/**/*.stories.@(ts|tsx)"] };',
|
||||
'.storybook/preview.tsx': 'import "../src/styles.css"; export default {};',
|
||||
'src/Example.stories.tsx': 'import { Button } from "@hm-design/ui"; import { StoryPage } from "./story-page"; export default { title: "Components/Example" }; export const All = () => <StoryPage><Button/></StoryPage>;',
|
||||
'src/story-page.tsx': 'export const StoryPage = ({children}) => <div className="sb-page">{children}</div>;',
|
||||
...extra,
|
||||
}, callback);
|
||||
test('23. Storybook independently discovers CSF and preview without Production pollution', () => {
|
||||
storyFixture({}, root => {
|
||||
const production = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.equal(production.status, 'PASS');
|
||||
assert.ok(!production.checkedFiles.some(f => f.endsWith('.stories.tsx')));
|
||||
const stories = run(root, ['--profile', 'STORYBOOK', '--scope', 'PARTIAL', '--files', 'src/Example.stories.tsx']).report;
|
||||
assert.equal(stories.status, 'PASS');
|
||||
assert.ok(stories.checkedFiles.includes('src/Example.stories.tsx'));
|
||||
assert.ok(!stories.issues.some(i => i.code === 'SCOPE-001'));
|
||||
});
|
||||
});
|
||||
test('24. Storybook invalid public export and private import still fail', () => {
|
||||
storyFixture({ 'src/Example.stories.tsx': 'import { NotPublic } from "@hm-design/ui"; import { Button } from "@hm-design/ui/src/components/ui/button"; export const All = () => <><NotPublic/><Button/></>;' }, root => {
|
||||
const { report } = run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN']);
|
||||
for (const code of ['IMP-001', 'IMP-002']) assert.ok(report.issues.some(i => i.code === code));
|
||||
assert.equal(report.status, 'FAIL');
|
||||
});
|
||||
});
|
||||
test('25. Storybook preview supplies CSS; proven missing theme/utilities FAIL', () => {
|
||||
storyFixture({ '.storybook/preview.tsx': 'export default {};' }, root => {
|
||||
const { report } = run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN']);
|
||||
assert.equal(report.status, 'FAIL');
|
||||
for (const code of ['CSS-001', 'CSS-002']) assert.ok(report.issues.some(i => i.code === code));
|
||||
});
|
||||
});
|
||||
test('26. computed Storybook discovery cannot silently PASS', () => {
|
||||
storyFixture({ '.storybook/main.ts': 'export default { stories: getStories() };' }, root => {
|
||||
const { report } = run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN']);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(report.issues.some(i => i.code === 'SB-004'));
|
||||
});
|
||||
});
|
||||
test('27. Reference navigation is preserved; an explicit Storybook mode checks violations', () => {
|
||||
storyFixture({ 'src/Example.stories.tsx': 'import { DashboardSidebar } from "@hm-design/ui"; export const All = () => <DashboardSidebar/>;' }, root => {
|
||||
assert.equal(run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN']).report.status, 'PASS');
|
||||
assert.equal(run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN', '--mode', 'DASHBOARD_ONLY']).report.status, 'FAIL');
|
||||
});
|
||||
});
|
||||
test('28. normal Primary 80% gradient is parsed without modification; consumer slot intent is reviewed', () => {
|
||||
fixture({
|
||||
'src/App.tsx': 'import { Tabs } from "@hm-design/ui"; export default () => <Tabs/>;',
|
||||
'src/styles.css': '@import "./tokens.css"; [data-slot="tabs-frame"][data-fill="true"][data-panel-surface="content"] { --hm-tab-fill-face: linear-gradient(to bottom, color-mix(in srgb, var(--hm-sem-palette-white) 80%, transparent) 0%, var(--hm-sem-bg-content) 100%); }',
|
||||
}, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.ok(!report.issues.some(i => i.severity === 'FAIL' || i.code === 'CSS-005'));
|
||||
assert.ok(report.issues.some(i => i.code === 'REUSE-003'));
|
||||
});
|
||||
});
|
||||
test('29. malformed CSS, forged component slots and CSS replicas do not PASS', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Tabs } from "@hm-design/ui"; export default () => <><Tabs/><div data-slot="tabs"/></>;', 'src/styles.css': '@import "./tokens.css"; .hm-tabs { color: red; }' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.equal(report.status, 'FAIL');
|
||||
for (const code of ['REUSE-001', 'REUSE-003']) assert.ok(report.issues.some(i => i.code === code));
|
||||
});
|
||||
fixture({ 'src/App.tsx': 'import { Tabs } from "@hm-design/ui"; export default () => <Tabs/>;', 'src/styles.css': '.hm-tabs {' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.ok(report.issues.some(i => i.code === 'CSS-005'));
|
||||
assert.notEqual(report.status, 'PASS');
|
||||
});
|
||||
});
|
||||
test('30. raw CSS is not an entry; package implementation scope is never claimed as consumer PASS', () => {
|
||||
fixture({ 'src/main.tsx': 'import App from "./App"; import css from "./tokens.css?raw"; export default App;', 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button/>;' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
for (const code of ['CSS-001', 'CSS-002']) assert.ok(report.issues.some(i => i.code === code));
|
||||
});
|
||||
const root = fileURLToPath(new URL('../', import.meta.url));
|
||||
const { report } = run(root, ['--scope', 'PARTIAL', '--files', 'packages/ui/src/theme/tabs.css']);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(report.issues.some(i => i.code === 'SCOPE-001'));
|
||||
});
|
||||
test('31. opaque spread, destructuring, mutation and callback aliases cannot hide a violation', () => {
|
||||
for (const source of [
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => <External {...{sidebar: <DashboardSidebar/>}}/>;',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => { const props = { sidebar: <DashboardSidebar/> }; return <External {...props}/>; };',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => { const [side] = [<DashboardSidebar/>]; return side; };',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => { let side = <DashboardSidebar/>; return side; };',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => { let side = <DashboardSidebar/>; side = window.forward(side); return side; };',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; function Screen({mode}) { mode = "sidebar"; return mode === "compact" ? null : <DashboardSidebar/>; } export default () => <Screen mode="compact"/>;',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => { const options = {show:false}; window.mutate(options); return options.show && <DashboardSidebar/>; };',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => { const side = <DashboardSidebar/>; const callback = () => side; return window.render(callback); };',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => <External options={{nav: <DashboardSidebar/>}}/>;',
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; export default () => { const obj = {...window.props, nav: <DashboardSidebar/>}; return obj.nav; };',
|
||||
]) fixture({ 'src/App.tsx': source }, root => assert.notEqual(run(root, dashArgs).report.status, 'PASS', source));
|
||||
});
|
||||
test('32. internal Reference wrapper is reviewable; public-slot replicas and Production wrappers fail', () => {
|
||||
storyFixture({ 'src/Example.stories.tsx': 'import { LNBMobileLink } from "@hm-design/ui"; export const All = () => <div data-slot="lnb-mobile-nav"><LNBMobileLink/></div>;' }, root => {
|
||||
const reference = run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN']).report;
|
||||
assert.equal(reference.status, 'NOT VERIFIED');
|
||||
assert.ok(reference.issues.some(i => i.code === 'REUSE-001' && i.severity === 'NOT VERIFIED'));
|
||||
fs.writeFileSync(path.join(root, 'src/App.tsx'), 'import { LNBMobileLink } from "@hm-design/ui"; export default () => <div data-slot="lnb-mobile-nav"><LNBMobileLink/></div>;');
|
||||
assert.equal(run(root, ['--scope', 'FULL_SCREEN']).report.status, 'FAIL');
|
||||
fs.writeFileSync(path.join(root, 'src/Example.stories.tsx'), 'export const All = () => <select data-slot="select"/>;');
|
||||
assert.equal(run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN']).report.status, 'FAIL');
|
||||
});
|
||||
});
|
||||
test('33. one lazy Story cannot supply missing CSS entry evidence to another Story', () => {
|
||||
storyFixture({
|
||||
'.storybook/preview.tsx': 'export default {};',
|
||||
'src/First.stories.tsx': 'import "./styles.css"; import { Button } from "@hm-design/ui"; export const All = () => <Button/>;',
|
||||
}, root => {
|
||||
const { report } = run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN']);
|
||||
assert.equal(report.status, 'FAIL');
|
||||
assert.ok(report.issues.some(i => i.code === 'CSS-001' && i.file === 'src/Example.stories.tsx'));
|
||||
assert.ok(!report.issues.some(i => i.code.startsWith('CSS-') && i.file === 'src/First.stories.tsx'));
|
||||
});
|
||||
});
|
||||
test('34. a possible violating call does not become certain through another safe call', () => {
|
||||
fixture({
|
||||
'src/Screen.tsx': dashboardSource,
|
||||
'src/App.tsx': 'import { Screen } from "./Screen"; export default () => <><Screen mode="compact"/>{window.flag && <Screen mode="sidebar"/>}</>;',
|
||||
}, root => {
|
||||
const { report } = run(root, dashArgs);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(report.issues.some(i=>i.code === 'NAV-001' || i.code === 'NAV-005'));
|
||||
});
|
||||
});
|
||||
test('35. a public Sidebar forwarded as a component prop still fails', () => {
|
||||
for (const source of [
|
||||
'import { DashboardSidebar } from "@hm-design/ui"; const Forward = ({component:Component}) => <Component/>; export default () => <Forward component={DashboardSidebar}/>;',
|
||||
'import * as HM from "@hm-design/ui"; const Forward = ({component:Component}) => <Component/>; export default () => <Forward component={HM.DashboardSidebar}/>;',
|
||||
]) fixture({'src/App.tsx':source},root=> {
|
||||
const { report } = run(root,dashArgs);
|
||||
assert.equal(report.status,'FAIL');
|
||||
assert.ok(report.issues.some(i=>i.code === 'NAV-001' && i.severity === 'FAIL'));
|
||||
});
|
||||
});
|
||||
test('36. aliased object mutation and non-primitive identity cannot prove a false dead branch', () => {
|
||||
for (const body of [
|
||||
'const options = {show:false}; const alias = options; alias.show = true; return options.show && <DashboardSidebar/>;',
|
||||
'const options = {show:false}; const alias = options; window.mutate(alias); return options.show && <DashboardSidebar/>;',
|
||||
'const value = <div/>; return value === value ? <DashboardSidebar/> : null;',
|
||||
'const value = () => <div/>; return value.name === "value" ? <DashboardSidebar/> : null;',
|
||||
]) fixture({'src/App.tsx':`import { DashboardSidebar } from "@hm-design/ui"; export default () => { ${body} };`},root=>assert.notEqual(run(root,dashArgs).report.status,'PASS',body));
|
||||
});
|
||||
test('37. resolved Production graph without HM theme is a required-style failure', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button/>;', 'src/tokens.css': '@import "tailwindcss";' }, root => {
|
||||
const { status, report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.equal(status, 1);
|
||||
assert.ok(report.issues.some(issue => issue.code === 'CSS-001' && issue.severity === 'FAIL'));
|
||||
});
|
||||
});
|
||||
test('38. missing Tailwind with a resolved HM component graph fails independently', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Input } from "@hm-design/ui"; export default () => <Input/>;', 'src/tokens.css': '@import "@hm-design/ui/theme.css";' }, root => {
|
||||
const { report } = run(root, ['--scope', 'PARTIAL', '--files', 'src/App.tsx']);
|
||||
assert.ok(report.issues.some(issue => issue.code === 'CSS-002' && issue.severity === 'FAIL'));
|
||||
assert.ok(!report.issues.some(issue => issue.code === 'CSS-001'));
|
||||
});
|
||||
});
|
||||
test('39. unresolved CSS graph preserves NOT VERIFIED rather than claiming absence', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button/>;', 'src/styles.css': '@import "./missing-theme.css";' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.ok(report.issues.some(issue => issue.code === 'CSS-001' && issue.severity === 'NOT VERIFIED'));
|
||||
assert.ok(report.issues.some(issue => issue.code === 'ENTRY-002'));
|
||||
});
|
||||
});
|
||||
test('40. required status CSS absence fails without relaxing navigation or import checks', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Badge, dashboardStatusDefinitions } from "@hm-design/ui"; export default () => <Badge>{dashboardStatusDefinitions.active.label}</Badge>;' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.ok(report.issues.some(issue => issue.code === 'CSS-003' && issue.severity === 'FAIL'));
|
||||
});
|
||||
});
|
||||
|
||||
test('41. Storybook PARTIAL detects disconnected preview styles for the selected Story', () => {
|
||||
storyFixture({ '.storybook/preview.tsx': 'export default {};' }, root => {
|
||||
const { status, report } = run(root, ['--profile', 'STORYBOOK', '--scope', 'PARTIAL', '--files', 'src/Example.stories.tsx']);
|
||||
assert.equal(status, 1);
|
||||
for (const code of ['CSS-001', 'CSS-002']) assert.ok(report.issues.some(i => i.code === code && i.severity === 'FAIL'));
|
||||
});
|
||||
});
|
||||
|
||||
test('42. explicit Story entry resolves computed discovery without claiming unselected coverage', () => {
|
||||
storyFixture({
|
||||
'.storybook/main.ts': 'export default { stories: getStories() };',
|
||||
'src/Bad.stories.tsx': 'export const All = () => <select data-slot="select"/>;',
|
||||
}, root => {
|
||||
const { report } = run(root, ['--profile', 'STORYBOOK', '--scope', 'FULL_SCREEN', '--entry', 'src/Example.stories.tsx']);
|
||||
assert.equal(report.status, 'PASS');
|
||||
assert.ok(report.checkedFiles.includes('src/Example.stories.tsx'));
|
||||
assert.ok(report.checkedFiles.includes('.storybook/preview.tsx'));
|
||||
assert.ok(!report.checkedFiles.includes('src/Bad.stories.tsx'));
|
||||
});
|
||||
});
|
||||
|
||||
test('43. inherited alias configuration remains uncertain even with locally resolved imports', () => {
|
||||
fixture({
|
||||
'tsconfig.app.json': '{ "extends": "./base.json", "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }',
|
||||
'base.json': '{ "compilerOptions": { "paths": { "external/*": ["./external/*"] } } }',
|
||||
'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <Button/>;',
|
||||
}, root => {
|
||||
const { status, report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.equal(status, 0);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(report.issues.some(i => i.code === 'ENTRY-003' && i.severity === 'NOT VERIFIED'));
|
||||
});
|
||||
});
|
||||
|
||||
test('44. internal Reference wrapper cannot downgrade a public-slot violation in the same Story', () => {
|
||||
storyFixture({ 'src/Example.stories.tsx': 'import { LNBMobileLink } from "@hm-design/ui"; export const All = () => <div data-slot="lnb-mobile-nav"><LNBMobileLink/><div data-slot="select-trigger"/></div>;' }, root => {
|
||||
const { report } = run(root, ['--profile', 'STORYBOOK', '--scope', 'PARTIAL', '--files', 'src/Example.stories.tsx']);
|
||||
assert.equal(report.status, 'FAIL');
|
||||
assert.ok(report.issues.some(i => i.code === 'REUSE-001' && i.severity === 'NOT VERIFIED'));
|
||||
assert.ok(report.issues.some(i => i.code === 'REUSE-001' && i.severity === 'FAIL'));
|
||||
});
|
||||
});
|
||||
|
||||
test('45. proven compact navigation does not approve consumer CSS intent', () => {
|
||||
fixture({
|
||||
'src/Screen.tsx': dashboardSource,
|
||||
'src/App.tsx': 'import { Screen } from "./Screen"; export default () => <Screen mode="compact"/>;',
|
||||
'src/styles.css': '@import "./tokens.css"; [data-slot="dashboard-header"] { background: red; }',
|
||||
}, root => {
|
||||
const { report } = run(root, dashArgs);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(!report.issues.some(i => i.code.startsWith('NAV')));
|
||||
assert.ok(report.issues.some(i => i.code === 'REUSE-003' && i.severity === 'NOT VERIFIED'));
|
||||
});
|
||||
});
|
||||
|
||||
test('46. selected package implementation stays outside consumer coverage in both profiles', () => {
|
||||
const root = fileURLToPath(new URL('../', import.meta.url));
|
||||
for (const profile of ['PRODUCTION', 'STORYBOOK']) {
|
||||
const { report } = run(root, ['--profile', profile, '--scope', 'PARTIAL', '--files', 'packages/ui/src/components/ui/select.tsx']);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(report.issues.some(i => i.code === 'SCOPE-001' && i.severity === 'NOT VERIFIED'));
|
||||
assert.ok(!report.checkedFiles.includes('packages/ui/src/components/ui/select.tsx'));
|
||||
assert.ok(!report.issues.some(i => i.severity === 'FAIL'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('47. external native field Label needs review, with no blanket native HTML failure', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Input } from "@hm-design/ui"; export default () => <><label htmlFor="name">Name</label><Input id="name"/></>;' }, root => {
|
||||
const { report, status } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.equal(status, 0);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(report.issues.some(i => i.code === 'REUSE-004' && i.message.includes('<label>') && i.severity === 'NOT VERIFIED'));
|
||||
assert.ok(!report.issues.some(i => i.severity === 'FAIL'));
|
||||
});
|
||||
});
|
||||
|
||||
test('48. aliased public Label and linked controls are actual public consumption', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Label as FieldName, Input } from "@hm-design/ui"; export default () => <><FieldName htmlFor="name">Name</FieldName><Input id="name"/></>;' }, root => {
|
||||
const { report } = run(root, ['--scope', 'FULL_SCREEN']);
|
||||
assert.equal(report.status, 'PASS');
|
||||
assert.ok(report.checkedHMElements.includes('Label'));
|
||||
assert.ok(!report.issues.some(i => i.code === 'REUSE-004'));
|
||||
});
|
||||
});
|
||||
|
||||
test('49. external React artifact is covered while browser verification stays separate', () => {
|
||||
fixture({
|
||||
'index.html': '<div id="root"></div><script type="module" src="/src/main.tsx"></script>',
|
||||
'src/main.tsx': 'import {createRoot} from "react-dom/client"; import App from "./App"; import "./styles.css"; createRoot(document.getElementById("root")).render(<App/>);',
|
||||
'src/App.tsx': 'import { GNB, GNBMobileMenu, Label, Input } from "@hm-design/ui"; export default () => <><GNB><GNBMobileMenu title="Menu"/></GNB><Label htmlFor="name">Name</Label><Input id="name"/></>;',
|
||||
}, root => {
|
||||
for (const scope of ['FULL_SCREEN', 'PARTIAL']) {
|
||||
const args = ['--scope', scope, '--mode', 'GNB_ONLY', '--entry', 'index.html', '--result', 'index.html'];
|
||||
if (scope === 'PARTIAL') args.push('--files', 'src/App.tsx');
|
||||
const { report } = run(root, args);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(report.checkedFiles.includes('src/App.tsx'));
|
||||
assert.ok(report.checkedGroups.every(g => g.status === 'PASS'));
|
||||
assert.deepEqual(report.issues.map(i => i.code), ['RENDER-002']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('50. external artifact still fails actual Sidebar and missing Theme', () => {
|
||||
fixture({
|
||||
'src/main.tsx': 'import {createRoot} from "react-dom/client"; import App from "./App"; import "./styles.css"; createRoot(document.getElementById("root")).render(<App/>);',
|
||||
'src/App.tsx': 'import { GNB, DashboardSidebar, Input } from "@hm-design/ui"; export default () => <><GNB/><DashboardSidebar/><Input/></>;',
|
||||
'src/tokens.css': '@import "tailwindcss";',
|
||||
}, root => {
|
||||
const { report, status } = run(root, ['--scope', 'FULL_SCREEN', '--mode', 'GNB_ONLY', '--result', 'index.html']);
|
||||
assert.equal(status, 1);
|
||||
assert.equal(report.status, 'FAIL');
|
||||
assert.ok(report.issues.some(i => i.code === 'NAV-001' && i.severity === 'FAIL'));
|
||||
assert.ok(report.issues.some(i => i.code === 'CSS-001' && i.severity === 'FAIL'));
|
||||
assert.ok(report.issues.some(i => i.code === 'RENDER-002'));
|
||||
});
|
||||
});
|
||||
|
||||
test('51. external orphan selection is scope omission, not browser-only uncertainty', () => {
|
||||
fixture({ 'src/App.tsx': 'import {Button} from "@hm-design/ui"; export default () => <Button/>;', 'src/Orphan.tsx': 'import {Label} from "@hm-design/ui"; export default () => <Label/>;' }, root => {
|
||||
const { report } = run(root, ['--scope', 'PARTIAL', '--files', 'src/Orphan.tsx', '--entry', 'index.html']);
|
||||
assert.equal(report.status, 'NOT VERIFIED');
|
||||
assert.ok(report.issues.some(i => i.code === 'SCOPE-001'));
|
||||
assert.ok(!report.checkedFiles.includes('src/Orphan.tsx'));
|
||||
assert.ok(!report.issues.some(i => i.code === 'RENDER-002'));
|
||||
});
|
||||
});
|
||||
|
||||
test('76. native hm-named content is not a public Component identity', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <><div className="hm-breadcrumb">Content</div><Button/></>;', 'src/styles.css': '@import "./tokens.css"; .hm-breadcrumb { color: #827b70; }' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(!r.issues.some(i => i.code === 'STYLE-001'));
|
||||
const check = r.styleEvidence.checks.find(c => c.selector === '.hm-breadcrumb');
|
||||
assert.equal(check.component, null); assert.equal(check.designIntentStatus, 'NOT VERIFIED');
|
||||
assert.ok(r.issues.some(i => i.code === 'REUSE-003'));
|
||||
});
|
||||
});
|
||||
test('77. absent positive ancestor bounds only that entry and retains intent inventory', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <div className="work"><Button/></div>;', 'src/styles.css': '@import "./tokens.css"; .legacy [data-slot="button"] { gap: 6px; padding: 0 10px; border-radius: 4px; }' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(!r.issues.some(i => i.code === 'STYLE-001'));
|
||||
const checks = r.styleEvidence.checks.filter(c => c.selector.startsWith('.legacy'));
|
||||
assert.equal(checks.length, 3); assert.ok(checks.every(c => c.applicability.status === 'NOT APPLICABLE' && c.designIntentStatus === 'NOT VERIFIED'));
|
||||
assert.equal(r.styleEvidence.finalStatus, 'NOT VERIFIED');
|
||||
});
|
||||
});
|
||||
test('78. present ancestor and class on a public Component preserve actual violations', () => {
|
||||
for (const jsx of ['<div className="legacy"><Button/></div>', '<Button className="hm-button"/>']) fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => '+jsx+';', 'src/styles.css': '@import "./tokens.css"; .legacy [data-slot="button"], .hm-button { color: #abcdef; }' }, root => {
|
||||
assert.ok(run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001' && i.severity === 'FAIL'));
|
||||
});
|
||||
});
|
||||
test('79. finite ancestor class branch cannot conceal a Component override', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; const name = location.hash ? "legacy" : "work"; export default () => <div className={name}><Button/></div>;', 'src/styles.css': '@import "./tokens.css"; .legacy [data-slot="button"] { padding: 17px; }' }, root => {
|
||||
assert.ok(run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
});
|
||||
test('80. dynamic classes spreads and DOM mutation retain strict detection', () => {
|
||||
for (const app of [
|
||||
'export default ({classes}) => <div className={classes}><Button/></div>;',
|
||||
'export default ({props}) => <div {...props}><Button/></div>;',
|
||||
'document.body.classList.add(location.hash); export default () => <Button/>;',
|
||||
'document.body.setAttribute("class", location.hash); export default () => <Button/>;',
|
||||
'document.body["className"] = location.hash; export default () => <Button/>;',
|
||||
'document.body["classList"].add(location.hash); export default () => <Button/>;',
|
||||
'document.body["setAttribute"]("class", location.hash); export default () => <Button/>;',
|
||||
]) fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; '+app, 'src/styles.css': '@import "./tokens.css"; .legacy [data-slot="button"] { padding: 17px; }' }, root => {
|
||||
assert.ok(run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
});
|
||||
test('81. host HTML classes preserve scoped violation detection', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <div className="work"><Button/></div>;', 'index.html': '<body class="legacy"><div id="root"></div><script type="module" src="/src/main.tsx"></script></body>', 'src/styles.css': '@import "./tokens.css"; .legacy [data-slot="button"] { padding: 17px; }' }, root => {
|
||||
assert.ok(run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
});
|
||||
test('82. selector alternatives pseudos and nesting never get broad exclusions', () => {
|
||||
for (const selector of ['.legacy [data-slot="button"], [data-slot="button"]', ':not(.legacy) [data-slot="button"]', ':is(.legacy, .work) [data-slot="button"]', '.legacy :not(.work) [data-slot="button"]', '& .legacy [data-slot="button"]']) fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <div className="work"><Button/></div>;', 'src/styles.css': '@import "./tokens.css"; '+selector+' { color: #abcdef; }' }, root => {
|
||||
assert.ok(run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
});
|
||||
test('83. unresolved entry and HTML scripts prevent scope exclusions', () => {
|
||||
for (const files of [{ 'src/main.tsx': 'import App from "./App"; import "./styles.css"; import "./missing"; export default App;' }, { 'index.html': '<script>document.body.className="legacy"</script><script type="module" src="/src/main.tsx"></script>' }]) fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <div className="work"><Button/></div>;', ...files, 'src/styles.css': '@import "./tokens.css"; .legacy [data-slot="button"] { padding: 17px; }' }, root => {
|
||||
assert.ok(run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
});
|
||||
test('84. aliases and createElement preserve class binding provenance', () => {
|
||||
for (const app of ['import { Button as Action } from "@hm-design/ui"; export default () => <Action className="hm-button"/>;', 'import { Button } from "@hm-design/ui"; import React from "react"; export default () => React.createElement(Button, {className:"hm-button"});']) fixture({ 'src/App.tsx': app, 'src/styles.css': '@import "./tokens.css"; .hm-button { color: #abcdef; }' }, root => {
|
||||
assert.ok(run(root, ['--scope', 'FULL_SCREEN']).report.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
});
|
||||
test('85. independent entries and PARTIAL CSS scope never borrow exclusions', () => {
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <div className="work"><Button/></div>;', 'src/second.tsx': 'import { Button } from "@hm-design/ui"; import "./styles.css"; export default () => <div className="legacy"><Button/></div>;', 'src/styles.css': '@import "./tokens.css"; .legacy [data-slot="button"] { padding: 17px; }' }, root => {
|
||||
const second = run(root, ['--scope', 'FULL_SCREEN', '--entry', 'src/second.tsx']).report;
|
||||
assert.ok(second.issues.some(i => i.code === 'STYLE-001'));
|
||||
const first = run(root, ['--scope', 'PARTIAL', '--files', 'src/styles.css']).report;
|
||||
assert.ok(!first.issues.some(i => i.code === 'STYLE-001'));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('86. Component implementation classes and unrelated graph mutations stay protected', () => {
|
||||
fixture({ 'src/App.tsx': 'import { FilterBar } from "@hm-design/ui"; export default () => <FilterBar className="hm-list-search-basic"/>;', 'src/styles.css': '@import "./tokens.css"; .hm-list-search-basic { gap: 8px; }' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(!r.issues.some(i => i.code === 'STYLE-001'));
|
||||
assert.equal(r.styleEvidence.checks.find(c => c.selector === '.hm-list-search-basic').designIntentStatus, 'NOT VERIFIED');
|
||||
});
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <div className="work"><Button/></div>;', 'src/styles.css': '@import "./tokens.css"; .group [data-slot="button"] { padding: 17px; }' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(r.issues.some(i => i.code === 'STYLE-001'));
|
||||
assert.equal(r.styleEvidence.checks.find(c => c.selector.startsWith('.group')).applicability.implementationClassPossible, true);
|
||||
});
|
||||
fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; export default () => <div className="work"><Button/></div>;', 'src/main.tsx': 'import App from "./App"; import "./styles.css"; import "./mutation"; export default App;', 'src/mutation.ts': 'document.body.className = location.hash;', 'src/styles.css': '@import "./tokens.css"; .legacy [data-slot="button"] { padding: 17px; }' }, root => {
|
||||
const r = run(root, ['--scope', 'FULL_SCREEN']).report;
|
||||
assert.ok(r.issues.some(i => i.code === 'STYLE-001'));
|
||||
assert.equal(r.styleEvidence.checks.find(c => c.selector.startsWith('.legacy')).applicability.classInputsComplete, false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user