B2 of TEST-INFRA-001 (RFC-WS-FRONTEND-PRIMEVUE Amendment A-1). - Add tests/playwright-ct/utils/mountWithProviders.ts: ergonomic wrapper around Playwright CT's mount() exposing buildMountArgs() and readNotificationState(). Documents the Vue Test Utils ↔ Playwright CT API divergence (provider plugins must be wired in beforeMount, not at call time) and the Vuetify-temp lifecycle (replaced by PrimeVue in F3). - Add tests/playwright-ct/components/SanityButtonHarness.vue: a v-btn harness with a click counter; lives in a .vue file so Vite bundles its CSS-side-effect imports for the browser context (Playwright CT runs the test orchestrator in Node and components in a Vite-bundled browser, unlike Vitest's single jsdom graph). - Add tests/playwright-ct/components/sanity-vuetify.spec.ts: two tests proving (a) v-btn renders and propagates clicks, (b) the --v-theme-primary CSS variable resolves to a parseable RGB triplet. - Update playwright/index.ts: import 'vuetify/styles' so the v-btn renders with its actual visual appearance (not unstyled). Required for B3's visual baselines. 3 component tests pass. 402 Vitest tests still pass unchanged. Lint + typecheck clean on new files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
126 lines
4.6 KiB
TypeScript
126 lines
4.6 KiB
TypeScript
import { beforeMount } from '@playwright/experimental-ct-vue/hooks'
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'
|
|
import { type RouteRecordRaw, createMemoryHistory, createRouter } from 'vue-router'
|
|
import { type ThemeDefinition, createVuetify } from 'vuetify'
|
|
|
|
// vuetify/components namespace import: required to register the full
|
|
// component set on a freshly-created Vuetify instance per test, mirroring
|
|
// tests/utils/mountWithVuexy.ts. Test infra only.
|
|
import * as components from 'vuetify/components' // eslint-disable-line no-restricted-imports
|
|
import * as directives from 'vuetify/directives'
|
|
|
|
// Vuetify base styles — required for v-btn / v-chip / v-card etc. to
|
|
// render with their actual visual appearance (not a default browser
|
|
// look). Without this, Playwright visual baselines would capture
|
|
// unstyled components. Removed in F3 alongside the Vuetify plugin.
|
|
import 'vuetify/styles'
|
|
|
|
// Plain-CSS token sheet — getComputedStyle(el).getPropertyValue('--tt-status-…')
|
|
// resolves during component tests. Path resolved by the alias map in
|
|
// playwright-ct.config.ts.
|
|
import '@/styles/tokens/_timetable.css'
|
|
|
|
// =============================================================================
|
|
// HOOKS CONFIG (per-test, opt-in)
|
|
// =============================================================================
|
|
//
|
|
// Tests pass `hooksConfig` to mount() to override defaults. Shape:
|
|
// {
|
|
// initialRoute?: string,
|
|
// initialQuery?: Record<string, string>,
|
|
// routes?: RouteRecordRaw[],
|
|
// piniaInitialState?: Record<string, Record<string, unknown>>,
|
|
// // injected by mountWithProviders.ts wrapper:
|
|
// notificationMockKey?: string,
|
|
// }
|
|
//
|
|
// Defaults below render every component with the full Vuexy/Vuetify
|
|
// stack. F3 (PrimeVue foundation) replaces the Vuetify plugin line
|
|
// here with PrimeVue and updates the sanity test — that is a ~2-hour
|
|
// swap, not a rewrite. Vuetify is INTENTIONAL TEMPORARY STATE in this
|
|
// file; do not abstract behind a "UI framework provider" indirection
|
|
// because the abstraction would itself need to be removed in F3.
|
|
// See dev-docs/ARCH-TESTING.md §6 for the migration timeline.
|
|
// =============================================================================
|
|
|
|
export interface HooksConfig {
|
|
initialRoute?: string
|
|
initialQuery?: Record<string, string>
|
|
routes?: RouteRecordRaw[]
|
|
piniaInitialState?: Record<string, Record<string, unknown>>
|
|
}
|
|
|
|
const defaultTheme: ThemeDefinition = {
|
|
dark: false,
|
|
colors: {
|
|
primary: '#1f7ad1',
|
|
error: '#d63d4b',
|
|
success: '#2fa66a',
|
|
warning: '#e0992c',
|
|
info: '#1f7ad1',
|
|
},
|
|
}
|
|
|
|
beforeMount<HooksConfig>(async ({ app, hooksConfig }) => {
|
|
// ---- Vuetify (TEMPORARY: replaced by PrimeVue in F3) -----------------
|
|
const vuetify = createVuetify({
|
|
components,
|
|
directives,
|
|
theme: { defaultTheme: 'crewliLight', themes: { crewliLight: defaultTheme } },
|
|
})
|
|
|
|
app.use(vuetify)
|
|
|
|
// ---- Pinia ----------------------------------------------------------
|
|
// Fresh instance per test. We do NOT use @pinia/testing's
|
|
// createTestingPinia here because it depends on Vitest's `vi.fn`,
|
|
// which doesn't exist in Playwright's Node runtime. Tests that need
|
|
// to assert on store actions should snapshot store state via
|
|
// page.evaluate() against window.__pinia (exposed below).
|
|
const pinia = createPinia()
|
|
|
|
app.use(pinia)
|
|
setActivePinia(pinia)
|
|
|
|
if (hooksConfig?.piniaInitialState) {
|
|
// Hydrate store state directly. Stores are created lazily on first
|
|
// use(); pre-hydration via pinia.state.value is safe.
|
|
pinia.state.value = {
|
|
...pinia.state.value,
|
|
...hooksConfig.piniaInitialState,
|
|
}
|
|
}
|
|
|
|
// Expose pinia on window for cross-frame state assertions.
|
|
;(globalThis as { __pinia?: typeof pinia }).__pinia = pinia
|
|
|
|
// ---- TanStack Vue Query ---------------------------------------------
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false, refetchOnWindowFocus: false },
|
|
mutations: { retry: false },
|
|
},
|
|
})
|
|
|
|
app.use(VueQueryPlugin, { queryClient })
|
|
|
|
// ---- Router (memory history; no auth guards) ------------------------
|
|
const routes: RouteRecordRaw[] = hooksConfig?.routes ?? [
|
|
{ path: '/', component: { template: '<div data-test="ct-route-root" />' } },
|
|
{ path: '/:pathMatch(.*)*', component: { template: '<div data-test="ct-route-catchall" />' } },
|
|
]
|
|
|
|
const router = createRouter({ history: createMemoryHistory(), routes })
|
|
|
|
app.use(router)
|
|
|
|
if (hooksConfig?.initialRoute) {
|
|
await router.push({
|
|
path: hooksConfig.initialRoute,
|
|
query: hooksConfig.initialQuery,
|
|
})
|
|
}
|
|
await router.isReady()
|
|
})
|