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