test(10-05): bring every SPA module, composable and component under Vitest
- 41 unit and component suites under admin/tests/{app,state,shell,list,form,relation,views,ui}
covering states and a11y roles; every src module is imported by a test
- typed fixture helper assigns each JSON fixture to its generated OpenAPI type
- fix: iconFor ignores inherited object members such as "constructor"
- fix: field controls import ./control instead of the registry (import cycle
left a renderer unregistered depending on module load order)
- fix: dropdown shows the placeholder for an unknown stored value next to an emptyOption
- fix: list announces a failed schema load even when the rows arrive after it
- tailwind no longer scans admin/tests; boardwalk/dist rebuilt
This commit is contained in:
@@ -6,9 +6,15 @@ import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { createMemoryHistory } from 'vue-router'
|
||||
import App from '../src/App.vue'
|
||||
import { createAdminRouter } from '../src/app/router'
|
||||
import { me } from '../src/state/useAuth'
|
||||
import { loadNavigation } from '../src/state/useNavigation'
|
||||
import { setBundle } from '../src/app/i18n'
|
||||
import { clearUser, me } from '../src/state/useAuth'
|
||||
import { clearRecordCrumb } from '../src/state/useBreadcrumbs'
|
||||
import { loadNavigation, setNavigation } from '../src/state/useNavigation'
|
||||
import { setSettings } from '../src/state/useSettings'
|
||||
import { watchViewport } from '../src/state/useSidebar'
|
||||
import { clearToasts } from '../src/state/useToasts'
|
||||
import navigation from './fixtures/navigation.json'
|
||||
import { langFixture } from './fixtures/typed'
|
||||
|
||||
export const API = '/admin-test/api/v1'
|
||||
|
||||
@@ -85,3 +91,81 @@ export async function mountApp(path: string, routes: Record<string, Route>, opti
|
||||
await flushPromises()
|
||||
return { wrapper, calls, router }
|
||||
}
|
||||
|
||||
/** Signs the profile in over the mocked API; extra routes join the mock. */
|
||||
export async function signIn(routes: Record<string, Route> = {}): Promise<Request[]> {
|
||||
const calls = mockApi({ [`GET ${API}/auth/me`]: { body: profile }, ...routes })
|
||||
await me()
|
||||
return calls
|
||||
}
|
||||
|
||||
/** The real admin router on memory history, already at path. */
|
||||
export async function routerAt(path: string) {
|
||||
const router = createAdminRouter(createMemoryHistory())
|
||||
await router.push(path)
|
||||
await router.isReady()
|
||||
return router
|
||||
}
|
||||
|
||||
/** Dispatches a bubbling, cancelable keydown and returns the event. */
|
||||
export function keydown(target: Element, key: string): KeyboardEvent {
|
||||
const event = new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true })
|
||||
target.dispatchEvent(event)
|
||||
return event
|
||||
}
|
||||
|
||||
export const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
type MediaListener = (event: MediaQueryListEvent) => void
|
||||
|
||||
/** A matchMedia whose answers the test controls, with change events. */
|
||||
export function fakeMedia(initial: Record<string, boolean>) {
|
||||
const state = { ...initial }
|
||||
const listeners = new Map<string, Set<MediaListener>>()
|
||||
const of = (query: string): Set<MediaListener> => {
|
||||
let set = listeners.get(query)
|
||||
if (!set) {
|
||||
set = new Set()
|
||||
listeners.set(query, set)
|
||||
}
|
||||
return set
|
||||
}
|
||||
vi.spyOn(window, 'matchMedia').mockImplementation(
|
||||
(query: string) =>
|
||||
({
|
||||
get matches() {
|
||||
return state[query] ?? false
|
||||
},
|
||||
media: query,
|
||||
onchange: null,
|
||||
addEventListener: (_type: string, listener: MediaListener) => of(query).add(listener),
|
||||
removeEventListener: (_type: string, listener: MediaListener) => of(query).delete(listener),
|
||||
addListener: () => undefined,
|
||||
removeListener: () => undefined,
|
||||
dispatchEvent: () => false,
|
||||
}) as unknown as MediaQueryList,
|
||||
)
|
||||
return {
|
||||
change(query: string, matches: boolean): void {
|
||||
state[query] = matches
|
||||
for (const listener of of(query)) {
|
||||
listener({ matches, media: query } as MediaQueryListEvent)
|
||||
}
|
||||
},
|
||||
listeners: (query: string) => of(query).size,
|
||||
}
|
||||
}
|
||||
|
||||
/** Module state back to a signed-out, Polish-strings start. */
|
||||
export function resetState(): void {
|
||||
clearUser()
|
||||
setNavigation([])
|
||||
setSettings([])
|
||||
clearToasts()
|
||||
clearRecordCrumb()
|
||||
setBundle(langFixture.data, 'pl')
|
||||
window.localStorage.clear()
|
||||
// Re-reads the (cleared) preference and the desktop viewport of setup.ts,
|
||||
// so a narrow or collapsed sidebar never leaks into the next test.
|
||||
watchViewport()()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user