import { mount, shallowMount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { vi } from 'vitest'
import { createMockRouter } from './router.js'
/**
* Mount a component with common test setup
* @param {object} component - Vue component to mount
* @param {object} options - Mount options
* @returns {object} Wrapper instance
*/
export function mountComponent(component, options = {}) {
const {
props = {},
shallow = false,
stores = {},
route = {},
slots = {},
stubs = {},
attachTo = undefined
} = options
const pinia = createPinia()
setActivePinia(pinia)
// Set initial store state if provided
Object.entries(stores).forEach(([storeId, state]) => {
pinia.state.value[storeId] = state
})
const router = createMockRouter({ currentRoute: route })
const mountFn = shallow ? shallowMount : mount
return mountFn(component, {
props,
slots,
attachTo,
global: {
plugins: [pinia, router],
stubs: {
teleport: true,
RouterLink: {
template: '',
props: ['to']
},
RouterView: {
template: '
'
},
...stubs
},
mocks: {
$route: {
path: route.path || '/',
name: route.name || 'home',
params: route.params || {},
query: route.query || {},
meta: route.meta || {},
fullPath: route.fullPath || route.path || '/'
}
}
}
})
}
/**
* Mount a component with shallow rendering
* @param {object} component - Vue component to mount
* @param {object} options - Mount options
* @returns {object} Wrapper instance
*/
export function shallowMountComponent(component, options = {}) {
return mountComponent(component, { ...options, shallow: true })
}
/**
* Create mock app config for testing
* @returns {object} Mock app config
*/
export function createMockAppConfig() {
return {
app: {
colors: {
primary: '#6FB0F9',
success: '#26CD5F',
warning: '#EBB834',
danger: '#FF5574'
},
sidebarTransitionTime: 300,
sidebarColors: [
['default', '#002B49'],
['gray', '#333333'],
['white', '#FFFFFF'],
['blue', '#3754a5']
],
navbarColors: [
['white', '#FFFFFF'],
['light', '#F9FAFE'],
['dark', '#323232']
]
}
}
}
/**
* Create a spy on a component method
* @param {object} wrapper - Component wrapper
* @param {string} methodName - Method name to spy on
* @returns {object} Spy function
*/
export function spyOnMethod(wrapper, methodName) {
const spy = vi.fn()
const original = wrapper.vm[methodName]
wrapper.vm[methodName] = (...args) => {
spy(...args)
return original?.apply(wrapper.vm, args)
}
return spy
}
/**
* Wait for component to update
* @param {object} wrapper - Component wrapper
* @param {number} timeout - Timeout in ms
*/
export async function waitForUpdate(wrapper, timeout = 0) {
await wrapper.vm.$nextTick()
if (timeout > 0) {
await new Promise(resolve => setTimeout(resolve, timeout))
}
}
/**
* Trigger window resize event
* @param {number} width - Window width
* @param {number} height - Window height
*/
export function triggerResize(width, height = 768) {
Object.defineProperty(window, 'innerWidth', { value: width, writable: true })
Object.defineProperty(window, 'innerHeight', { value: height, writable: true })
window.dispatchEvent(new Event('resize'))
}
/**
* Create mock touch events
* @param {number} startX - Start X position
* @param {number} endX - End X position
* @returns {object} Touch event objects
*/
export function createTouchEvents(startX, endX) {
const touchStart = {
changedTouches: [{ screenX: startX, screenY: 100 }]
}
const touchEnd = {
changedTouches: [{ screenX: endX, screenY: 100 }]
}
return { touchStart, touchEnd }
}
/**
* Wait for a specific condition to be true
* @param {function} condition - Condition function
* @param {number} timeout - Timeout in ms
* @param {number} interval - Check interval in ms
*/
export async function waitFor(condition, timeout = 1000, interval = 50) {
const startTime = Date.now()
while (Date.now() - startTime < timeout) {
if (condition()) return
await new Promise(resolve => setTimeout(resolve, interval))
}
throw new Error('Condition not met within timeout')
}
/**
* Find component by ref name
* @param {object} wrapper - Component wrapper
* @param {string} refName - Ref name
* @returns {object|null} Component instance or null
*/
export function findByRef(wrapper, refName) {
return wrapper.vm.$refs[refName] || null
}
/**
* Assert that a component emitted an event with specific payload
* @param {object} wrapper - Component wrapper
* @param {string} eventName - Event name
* @param {any} payload - Expected payload
*/
export function expectEmitted(wrapper, eventName, payload = undefined) {
const emitted = wrapper.emitted(eventName)
expect(emitted).toBeTruthy()
if (payload !== undefined) {
expect(emitted[emitted.length - 1]).toEqual([payload])
}
}
/**
* Create mock store state for layout store
* @param {object} overrides - State overrides
* @returns {object} Layout store state
*/
export function createLayoutStoreState(overrides = {}) {
return {
sidebarClose: false,
sidebarStatic: false,
sidebarColorName: 'default',
navbarColorName: 'light',
navbarColorScheme: 'light',
navbarType: 'static',
sidebarType: 'solid',
sidebarActiveElement: null,
helperOpened: false,
tourInstance: null,
...overrides
}
}
/**
* Create mock store state for auth store
* @param {object} overrides - State overrides
* @returns {object} Auth store state
*/
export function createAuthStoreState(overrides = {}) {
return {
isFetching: false,
errorMessage: '',
...overrides
}
}