Enhance test infrastructure with standardized patterns and factories
- Add comprehensive contact factory with 3 complexity levels (simple, standard, complex) - Create centralized test utilities with performance, accessibility, and error helpers - Standardize test data patterns across all component tests - Add test data factories for RegistrationNotice, ProjectIcon, and ContactBulkActions - Improve test structure consistency with better beforeEach patterns - All 149 tests passing with enhanced error handling and performance testing - Establish foundation for scalable test development with reusable utilities Files changed: - src/test/factories/contactFactory.ts (new) - src/test/utils/testHelpers.ts (new) - src/test/LargeIdenticonModal.test.ts (updated) - src/test/RegistrationNotice.test.ts (updated) - src/test/ProjectIcon.test.ts (updated) - src/test/ContactBulkActions.test.ts (updated)
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContactBulkActions from '@/components/ContactBulkActions.vue'
|
import ContactBulkActions from '@/components/ContactBulkActions.vue'
|
||||||
|
import { createMockContacts } from '@/test/factories/contactFactory'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ContactBulkActions Component Tests
|
* ContactBulkActions Component Tests
|
||||||
@@ -37,6 +38,17 @@ describe('ContactBulkActions', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test data factory for consistent test data
|
||||||
|
*/
|
||||||
|
const createTestProps = (overrides = {}) => ({
|
||||||
|
showGiveNumbers: false,
|
||||||
|
allContactsSelected: false,
|
||||||
|
copyButtonClass: 'btn-primary',
|
||||||
|
copyButtonDisabled: false,
|
||||||
|
...overrides
|
||||||
|
})
|
||||||
|
|
||||||
describe('Component Rendering', () => {
|
describe('Component Rendering', () => {
|
||||||
it('should render when all props are provided', () => {
|
it('should render when all props are provided', () => {
|
||||||
wrapper = mountComponent()
|
wrapper = mountComponent()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import LargeIdenticonModal from '@/components/LargeIdenticonModal.vue'
|
import LargeIdenticonModal from '@/components/LargeIdenticonModal.vue'
|
||||||
import { Contact } from '@/db/tables/contacts'
|
import { Contact } from '@/db/tables/contacts'
|
||||||
|
import { createSimpleMockContact } from '@/test/factories/contactFactory'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LargeIdenticonModal Component Tests
|
* LargeIdenticonModal Component Tests
|
||||||
@@ -20,13 +21,7 @@ describe('LargeIdenticonModal', () => {
|
|||||||
*/
|
*/
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = null
|
wrapper = null
|
||||||
mockContact = {
|
mockContact = createSimpleMockContact()
|
||||||
id: 1,
|
|
||||||
name: 'Test Contact',
|
|
||||||
did: 'did:ethr:test',
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date()
|
|
||||||
} as Contact
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ProjectIcon from '@/components/ProjectIcon.vue'
|
import ProjectIcon from '@/components/ProjectIcon.vue'
|
||||||
|
import { createSimpleMockContact } from '@/test/factories/contactFactory'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProjectIcon Component Tests
|
* ProjectIcon Component Tests
|
||||||
@@ -37,6 +38,17 @@ describe('ProjectIcon', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test data factory for consistent test data
|
||||||
|
*/
|
||||||
|
const createTestProps = (overrides = {}) => ({
|
||||||
|
entityId: 'test-entity',
|
||||||
|
iconSize: 64,
|
||||||
|
imageUrl: '',
|
||||||
|
linkToFullImage: false,
|
||||||
|
...overrides
|
||||||
|
})
|
||||||
|
|
||||||
describe('Component Rendering', () => {
|
describe('Component Rendering', () => {
|
||||||
it('should render when all props are provided', () => {
|
it('should render when all props are provided', () => {
|
||||||
wrapper = mountComponent()
|
wrapper = mountComponent()
|
||||||
|
|||||||
@@ -35,6 +35,15 @@ describe('RegistrationNotice', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test data factory for consistent test data
|
||||||
|
*/
|
||||||
|
const createTestProps = (overrides = {}) => ({
|
||||||
|
isRegistered: false,
|
||||||
|
show: true,
|
||||||
|
...overrides
|
||||||
|
})
|
||||||
|
|
||||||
describe('Component Rendering', () => {
|
describe('Component Rendering', () => {
|
||||||
it('should render when not registered and show is true', () => {
|
it('should render when not registered and show is true', () => {
|
||||||
wrapper = mountComponent()
|
wrapper = mountComponent()
|
||||||
|
|||||||
118
src/test/factories/contactFactory.ts
Normal file
118
src/test/factories/contactFactory.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* Contact Factory for TimeSafari Testing
|
||||||
|
*
|
||||||
|
* Provides different levels of mock contact data for testing
|
||||||
|
* various components and scenarios.
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Contact, ContactMethod } from '@/db/tables/contacts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a simple mock contact for basic component testing
|
||||||
|
* Used for: LargeIdenticonModal, EntityIcon, basic display components
|
||||||
|
*/
|
||||||
|
export const createSimpleMockContact = (overrides = {}): Contact => ({
|
||||||
|
did: `did:ethr:test:${Date.now()}`,
|
||||||
|
name: `Test Contact ${Date.now()}`,
|
||||||
|
...overrides
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a standard mock contact for most component testing
|
||||||
|
* Used for: ContactList, ContactEdit, ContactView components
|
||||||
|
*/
|
||||||
|
export const createStandardMockContact = (overrides = {}): Contact => ({
|
||||||
|
did: `did:ethr:test:${Date.now()}`,
|
||||||
|
name: `Test Contact ${Date.now()}`,
|
||||||
|
contactMethods: [
|
||||||
|
{ label: 'Email', type: 'EMAIL', value: 'test@example.com' },
|
||||||
|
{ label: 'Phone', type: 'SMS', value: '+1234567890' }
|
||||||
|
],
|
||||||
|
notes: 'Test contact notes',
|
||||||
|
seesMe: true,
|
||||||
|
registered: false,
|
||||||
|
...overrides
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a complex mock contact for integration and service testing
|
||||||
|
* Used for: Full contact management, service integration tests
|
||||||
|
*/
|
||||||
|
export const createComplexMockContact = (overrides = {}): Contact => ({
|
||||||
|
did: `did:ethr:test:${Date.now()}`,
|
||||||
|
name: `Test Contact ${Date.now()}`,
|
||||||
|
contactMethods: [
|
||||||
|
{ label: 'Email', type: 'EMAIL', value: 'test@example.com' },
|
||||||
|
{ label: 'Phone', type: 'SMS', value: '+1234567890' },
|
||||||
|
{ label: 'WhatsApp', type: 'WHATSAPP', value: '+1234567890' }
|
||||||
|
],
|
||||||
|
notes: 'Test contact notes with special characters: éñü',
|
||||||
|
profileImageUrl: 'https://example.com/avatar.jpg',
|
||||||
|
publicKeyBase64: 'base64encodedpublickey',
|
||||||
|
nextPubKeyHashB64: 'base64encodedhash',
|
||||||
|
seesMe: true,
|
||||||
|
registered: true,
|
||||||
|
iViewContent: true,
|
||||||
|
...overrides
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create multiple contacts for list testing
|
||||||
|
* @param count - Number of contacts to create
|
||||||
|
* @param factory - Factory function to use (default: standard)
|
||||||
|
* @returns Array of mock contacts
|
||||||
|
*/
|
||||||
|
export const createMockContacts = (
|
||||||
|
count: number,
|
||||||
|
factory = createStandardMockContact
|
||||||
|
): Contact[] => {
|
||||||
|
return Array.from({ length: count }, (_, index) =>
|
||||||
|
factory({
|
||||||
|
did: `did:ethr:test:${index + 1}`,
|
||||||
|
name: `Test Contact ${index + 1}`
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create invalid contact data for error testing
|
||||||
|
* @returns Array of invalid contact objects
|
||||||
|
*/
|
||||||
|
export const createInvalidContacts = (): Partial<Contact>[] => [
|
||||||
|
{},
|
||||||
|
{ did: '' },
|
||||||
|
{ did: 'invalid-did' },
|
||||||
|
{ did: 'did:ethr:test', name: null },
|
||||||
|
{ did: 'did:ethr:test', contactMethods: 'invalid' },
|
||||||
|
{ did: 'did:ethr:test', contactMethods: [null] },
|
||||||
|
{ did: 'did:ethr:test', contactMethods: [{ invalid: 'data' }] }
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create contact with specific characteristics for testing
|
||||||
|
*/
|
||||||
|
export const createContactWithMethods = (methods: ContactMethod[]): Contact =>
|
||||||
|
createStandardMockContact({ contactMethods: methods })
|
||||||
|
|
||||||
|
export const createContactWithNotes = (notes: string): Contact =>
|
||||||
|
createStandardMockContact({ notes })
|
||||||
|
|
||||||
|
export const createContactWithName = (name: string): Contact =>
|
||||||
|
createStandardMockContact({ name })
|
||||||
|
|
||||||
|
export const createContactWithDid = (did: string): Contact =>
|
||||||
|
createStandardMockContact({ did })
|
||||||
|
|
||||||
|
export const createRegisteredContact = (): Contact =>
|
||||||
|
createStandardMockContact({ registered: true })
|
||||||
|
|
||||||
|
export const createUnregisteredContact = (): Contact =>
|
||||||
|
createStandardMockContact({ registered: false })
|
||||||
|
|
||||||
|
export const createContactThatSeesMe = (): Contact =>
|
||||||
|
createStandardMockContact({ seesMe: true })
|
||||||
|
|
||||||
|
export const createContactThatDoesntSeeMe = (): Contact =>
|
||||||
|
createStandardMockContact({ seesMe: false })
|
||||||
248
src/test/utils/testHelpers.ts
Normal file
248
src/test/utils/testHelpers.ts
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
/**
|
||||||
|
* 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<ComponentPublicInstance> | null
|
||||||
|
mountComponent: (props?: any) => VueWrapper<ComponentPublicInstance>
|
||||||
|
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<ComponentPublicInstance> | 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<void> => {
|
||||||
|
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<ComponentPublicInstance>) => {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user