// @vitest-environment node // ESLint Node API tests must run in the Node environment — the default // happy-dom environment's `document` object causes case-police's dirs.cjs // (which uses `document.currentScript.src` for __dirname resolution) // to fail with "The URL must be of scheme file". import { fileURLToPath } from 'node:url' import path from 'node:path' import { describe, expect, it } from 'vitest' import { ESLint } from 'eslint' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const rootDir = path.resolve(__dirname, '../../') const eslint = new ESLint({ cwd: rootDir }) // Filter on the boundaries/element-types ruleId, which is the deprecated // alias for boundaries/dependencies (renamed in eslint-plugin-boundaries // v5→v6). The installed 6.0.2 still reports violations under the old // ruleId while .eslintrc.cjs's rule key is also 'boundaries/element-types'. // If a future bump drops the alias, update this filter AND the eslintrc // key together (TECH-WS-GUI-REDESIGN tracks the full migration to // 'boundaries/dependencies') — otherwise this filter silently matches // nothing and the tests pass even when the rule fires. async function boundaryErrors(filePath: string, code: string) { const [result] = await eslint.lintText(code, { filePath }) return result.messages.filter(m => m.ruleId === 'boundaries/element-types') } describe('boundaries — v2 zones', () => { it('allows pages-v2 → components-v2', async () => { const errs = await boundaryErrors( 'src/pages-v2/dashboard.vue', '', ) expect(errs).toHaveLength(0) }) it('allows components-v2 → components-foundation (Icon.vue bridge, single-file mode:file)', async () => { // Regression lock: the single-file element needs mode:'file' or // Icon.vue falls through to the generic `components` catch-all and // every v2 shell component's Icon import breaks (RFC AD-G5 bridge). const errs = await boundaryErrors( 'src/components-v2/layout/SidebarNav.vue', '', ) expect(errs).toHaveLength(0) }) it('allows components-v2 → components-foundation (FormField bridge)', async () => { const errs = await boundaryErrors( 'src/components-v2/forms/Demo.vue', '', ) expect(errs).toHaveLength(0) }) it('forbids v1 components → components-v2 (no back-porting)', async () => { const errs = await boundaryErrors( 'src/components/organizer/Legacy.vue', '', ) expect(errs.length).toBeGreaterThan(0) }) // ------------------------------------------------------------------------- // layouts-v2 zone (RFC AD-G5): the v2 shell layout (src/layouts/*V2*.vue) // gets pages-v2-equivalent v2 capability so AD-G2 ("OrganizerLayoutV2 // wraps AppShellV2") holds — WITHOUT widening the v1 `layouts` zone. // These lock both halves: the new edge AND the preserved isolation. // ------------------------------------------------------------------------- it('allows layouts-v2 → components-v2 (AD-G5: v2 shell composition)', async () => { const errs = await boundaryErrors( 'src/layouts/OrganizerLayoutV2.vue', '', ) expect(errs).toHaveLength(0) }) it('allows layouts-v2 → navigation (v2 layout sources nav data)', async () => { const errs = await boundaryErrors( 'src/layouts/OrganizerLayoutV2.vue', '', ) expect(errs).toHaveLength(0) }) it('forbids v1 layouts → components-v2 (AD-G5 isolation preserved)', async () => { const errs = await boundaryErrors( 'src/layouts/OrganizerLayout.vue', '', ) expect(errs.length).toBeGreaterThan(0) }) // ------------------------------------------------------------------------- // shared/* regression-lock cases: lock the allowed/forbidden edges for // components-v2/shared so future drift is caught by CI (constraint #7 // intent served by regression tests instead of a redundant sub-zone — // Bert-approved plan decision, Phase C gate). // ------------------------------------------------------------------------- it('allows components-v2/shared → types (statusSeverity consumes enums)', async () => { const errs = await boundaryErrors( 'src/components-v2/shared/StatusTag.vue', '', ) expect(errs).toHaveLength(0) }) it('forbids components-v2/shared → pages-v2 (no upward import)', async () => { const errs = await boundaryErrors( 'src/components-v2/shared/StatusTag.vue', '', ) expect(errs.length).toBeGreaterThan(0) }) it('forbids components-v2/shared → layouts (no upward import)', async () => { const errs = await boundaryErrors( 'src/components-v2/shared/StatusTag.vue', '', ) expect(errs.length).toBeGreaterThan(0) }) })