Files
crewli/apps/app/tests/unit/boundaries-v2.spec.ts
bert.hausmans 4e9eeb99c4 fix(lint): mode:'file' for the components-foundation Icon.vue bridge
Plan-1 Task-4 added { type:'components-foundation', pattern:
'src/components/Icon.vue' } without mode:'file'. eslint-plugin-boundaries
defaults to folder mode, so the single-file pattern never matched and
Icon.vue fell through to the generic `components` catch-all — breaking
the sanctioned components-v2 -> Icon bridge (RFC AD-G5) for every v2
shell component. Plan-1's boundary test only exercised the forms/**
folder-glob edge so the gap was latent. Adds mode:'file' + a regression
test locking the components-v2 -> Icon.vue edge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 19:23:00 +02:00

71 lines
2.9 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 X from \'@/components-v2/shared/X.vue\'</script><template><X /></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 X from \'@/components-v2/shared/X.vue\'</script><template><X /></template>',
)
expect(errs.length).toBeGreaterThan(0)
})
})