/** * Test Utilities for TimeSafari Component Testing * * Provides standardized test patterns, helpers, and utilities * for consistent component testing across the application. * * @author Matthew Raymer */ import { mount, VueWrapper } from '@vue/test-utils' import { ComponentPublicInstance } from 'vue' import { vi } from 'vitest' /** * Standardized test setup interface */ export interface TestSetup { wrapper: VueWrapper | null mountComponent: (props?: any) => VueWrapper cleanup: () => void } /** * Standardized beforeEach pattern for all component tests * @param component - Vue component to test * @param defaultProps - Default props for the component * @param globalOptions - Global options for mounting * @returns Test setup object */ export const createTestSetup = ( component: any, defaultProps = {}, globalOptions = {} ) => { let wrapper: VueWrapper | null = null const mountComponent = (props = {}) => { return mount(component, { props: { ...defaultProps, ...props }, global: globalOptions }) } const cleanup = () => { if (wrapper) { wrapper.unmount() wrapper = null } } return { wrapper, mountComponent, cleanup } } /** * Standardized beforeEach function * @param setup - Test setup object */ export const standardBeforeEach = (setup: TestSetup) => { setup.wrapper = null } /** * Standardized afterEach function * @param setup - Test setup object */ export const standardAfterEach = (setup: TestSetup) => { if (setup.wrapper) { setup.wrapper.unmount() setup.wrapper = null } } /** * Wait for async operations to complete * @param ms - Milliseconds to wait * @returns Promise that resolves after the specified time */ export const waitForAsync = (ms: number = 0): Promise => { return new Promise(resolve => setTimeout(resolve, ms)) } /** * Wait for Vue to finish updating * @param wrapper - Vue test wrapper * @returns Promise that resolves after Vue updates */ export const waitForVueUpdate = async (wrapper: VueWrapper) => { await wrapper.vm.$nextTick() await waitForAsync(10) // Small delay to ensure all updates are complete } /** * Create mock store for testing * @returns Mock Vuex store */ export const createMockStore = () => ({ state: { user: { isRegistered: false }, contacts: [], projects: [] }, getters: { isUserRegistered: (state: any) => state.user.isRegistered, getContacts: (state: any) => state.contacts, getProjects: (state: any) => state.projects }, mutations: { setUserRegistered: vi.fn(), setContacts: vi.fn(), setProjects: vi.fn() }, actions: { fetchContacts: vi.fn(), fetchProjects: vi.fn(), updateUser: vi.fn() } }) /** * Create mock router for testing * @returns Mock Vue router */ export const createMockRouter = () => ({ push: vi.fn(), replace: vi.fn(), go: vi.fn(), back: vi.fn(), forward: vi.fn(), currentRoute: { value: { name: 'home', path: '/', params: {}, query: {} } } }) /** * Create mock service for testing * @returns Mock service object */ export const createMockService = () => ({ getData: vi.fn().mockResolvedValue([]), saveData: vi.fn().mockResolvedValue(true), deleteData: vi.fn().mockResolvedValue(true), updateData: vi.fn().mockResolvedValue(true) }) /** * Performance testing utilities */ export const performanceUtils = { /** * Measure execution time of a function * @param fn - Function to measure * @returns Object with timing information */ measureTime: async (fn: () => any) => { const start = performance.now() const result = await fn() const end = performance.now() return { result, duration: end - start, start, end } }, /** * Check if performance is within acceptable limits * @param duration - Duration in milliseconds * @param threshold - Maximum acceptable duration * @returns Boolean indicating if performance is acceptable */ isWithinThreshold: (duration: number, threshold: number = 200) => { return duration < threshold } } /** * Accessibility testing utilities */ export const accessibilityUtils = { /** * Check if element has required ARIA attributes * @param element - DOM element to check * @param requiredAttributes - Array of required ARIA attributes * @returns Boolean indicating if all required attributes are present */ hasRequiredAriaAttributes: (element: any, requiredAttributes: string[]) => { return requiredAttributes.every(attr => element.attributes(attr) !== undefined ) }, /** * Check if element is keyboard accessible * @param element - DOM element to check * @returns Boolean indicating if element is keyboard accessible */ isKeyboardAccessible: (element: any) => { const tabindex = element.attributes('tabindex') const role = element.attributes('role') return tabindex !== undefined || role === 'button' || role === 'link' } } /** * Error testing utilities */ export const errorUtils = { /** * Test component with various invalid prop combinations * @param mountComponent - Function to mount component * @param invalidProps - Array of invalid prop combinations * @returns Array of test results */ testInvalidProps: async (mountComponent: Function, invalidProps: any[]) => { const results = [] for (const props of invalidProps) { try { const wrapper = mountComponent(props) results.push({ props, success: true, error: null, wrapper: wrapper.exists() }) } catch (error) { results.push({ props, success: false, error: error instanceof Error ? error.message : String(error), wrapper: false }) } } return results } }