Browse Source
- 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)pull/153/head
6 changed files with 401 additions and 7 deletions
@ -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 }) |
@ -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 |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue