139 lines
6.0 KiB
TypeScript
139 lines
6.0 KiB
TypeScript
// @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',
|
|
'<script setup lang="ts">import StatusTag from \'@/components-v2/shared/StatusTag.vue\'</script><template><StatusTag status="approved" /></template>',
|
|
)
|
|
|
|
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',
|
|
'<script setup lang="ts">import Icon from \'@/components/Icon.vue\'</script><template><Icon name="x" /></template>',
|
|
)
|
|
|
|
expect(errs).toHaveLength(0)
|
|
})
|
|
|
|
it('allows components-v2 → components-foundation (FormField bridge)', async () => {
|
|
const errs = await boundaryErrors(
|
|
'src/components-v2/forms/Demo.vue',
|
|
'<script setup lang="ts">import FormField from \'@/components/forms/FormField.vue\'</script><template><FormField /></template>',
|
|
)
|
|
|
|
expect(errs).toHaveLength(0)
|
|
})
|
|
|
|
it('forbids v1 components → components-v2 (no back-porting)', async () => {
|
|
const errs = await boundaryErrors(
|
|
'src/components/organizer/Legacy.vue',
|
|
'<script setup lang="ts">import StatusTag from \'@/components-v2/shared/StatusTag.vue\'</script><template><StatusTag status="approved" /></template>',
|
|
)
|
|
|
|
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',
|
|
'<script setup lang="ts">import AppSidebar from \'@/components-v2/layout/AppSidebar.vue\'</script><template><AppSidebar /></template>',
|
|
)
|
|
|
|
expect(errs).toHaveLength(0)
|
|
})
|
|
|
|
it('allows layouts-v2 → navigation (v2 layout sources nav data)', async () => {
|
|
const errs = await boundaryErrors(
|
|
'src/layouts/OrganizerLayoutV2.vue',
|
|
'<script setup lang="ts">import { orgNavItems } from \'@/navigation/vertical\'</script><template><div>{{ orgNavItems.length }}</div></template>',
|
|
)
|
|
|
|
expect(errs).toHaveLength(0)
|
|
})
|
|
|
|
it('forbids v1 layouts → components-v2 (AD-G5 isolation preserved)', async () => {
|
|
const errs = await boundaryErrors(
|
|
'src/layouts/OrganizerLayout.vue',
|
|
'<script setup lang="ts">import AppSidebar from \'@/components-v2/layout/AppSidebar.vue\'</script><template><AppSidebar /></template>',
|
|
)
|
|
|
|
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',
|
|
'<script setup lang="ts">import { ShiftAssignmentStatus } from \'@/types/shiftAssignment\'</script><template><span>{{ ShiftAssignmentStatus.APPROVED }}</span></template>',
|
|
)
|
|
|
|
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',
|
|
'<script setup lang="ts">import Dash from \'@/pages-v2/dashboard.vue\'</script><template><Dash /></template>',
|
|
)
|
|
|
|
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',
|
|
'<script setup lang="ts">import L from \'@/layouts/OrganizerLayoutV2.vue\'</script><template><L /></template>',
|
|
)
|
|
|
|
expect(errs.length).toBeGreaterThan(0)
|
|
})
|
|
})
|