Compare commits
8 Commits
ee35719cd5
...
886baa8bea
Author | SHA1 | Date |
---|---|---|
|
886baa8bea | 4 days ago |
|
aee53242a0 | 4 days ago |
|
4829582584 | 4 days ago |
|
6cf5183371 | 4 days ago |
|
75ddea4071 | 4 days ago |
|
5aceab434f | 4 days ago |
|
fca4bf5d16 | 4 days ago |
|
e2c812a5a6 | 4 days ago |
14 changed files with 9754 additions and 5801 deletions
@ -0,0 +1,714 @@ |
|||||
|
```json |
||||
|
{ |
||||
|
"coaching_level": "standard", |
||||
|
"socratic_max_questions": 2, |
||||
|
"verbosity": "normal", |
||||
|
"timebox_minutes": null, |
||||
|
"format_enforcement": "strict" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
# Unit Testing & Mocks — Universal Development Guide |
||||
|
|
||||
|
**Author**: Matthew Raymer |
||||
|
**Date**: 2025-08-21T09:40Z |
||||
|
**Status**: 🎯 **ACTIVE** - Comprehensive testing standards |
||||
|
|
||||
|
## Overview |
||||
|
|
||||
|
This guide establishes **unified unit testing and mocking standards** for Vue |
||||
|
and React projects, ensuring consistent, maintainable test patterns using |
||||
|
Vitest, JSDOM, and component testing utilities. All tests follow F.I.R.S.T. |
||||
|
principles with comprehensive mock implementations. |
||||
|
|
||||
|
## Scope and Goals |
||||
|
|
||||
|
**Scope**: Applies to all unit tests, mock implementations, and testing |
||||
|
infrastructure in any project workspace. |
||||
|
|
||||
|
**Goal**: One consistent testing approach with comprehensive mock coverage, |
||||
|
100% test coverage for simple components, and maintainable test patterns. |
||||
|
|
||||
|
## Non‑Negotiables (DO THIS) |
||||
|
|
||||
|
- **MUST** use Vitest + JSDOM for unit testing; **DO NOT** use Jest or other |
||||
|
frameworks |
||||
|
- **MUST** implement comprehensive mock levels (Simple, Standard, Complex) for |
||||
|
all components |
||||
|
- **MUST** achieve 100% line coverage for simple components (<100 lines) |
||||
|
- **MUST** follow F.I.R.S.T. principles: Fast, Independent, Repeatable, |
||||
|
Self-validating, Timely |
||||
|
- **MUST** use centralized test utilities from `src/test/utils/` |
||||
|
|
||||
|
## Testing Infrastructure |
||||
|
|
||||
|
### **Core Technologies** |
||||
|
|
||||
|
- **Vitest**: Fast unit testing framework with Vue/React support |
||||
|
- **JSDOM**: Browser-like environment for Node.js testing |
||||
|
- **@vue/test-utils**: Vue component testing utilities |
||||
|
- **TypeScript**: Full type safety for tests and mocks |
||||
|
|
||||
|
### **Configuration Files** |
||||
|
|
||||
|
- `vitest.config.ts` - Vitest configuration with JSDOM environment |
||||
|
- `src/test/setup.ts` - Global test configuration and mocks |
||||
|
- `src/test/utils/` - Centralized testing utilities |
||||
|
|
||||
|
### **Global Mocks** |
||||
|
|
||||
|
```typescript |
||||
|
// Required browser API mocks |
||||
|
ResizeObserver, IntersectionObserver, localStorage, sessionStorage, |
||||
|
matchMedia, console methods (reduced noise) |
||||
|
``` |
||||
|
|
||||
|
## Mock Implementation Standards |
||||
|
|
||||
|
### **Mock Architecture Levels** |
||||
|
|
||||
|
#### **1. Simple Mock (Basic Testing)** |
||||
|
|
||||
|
```typescript |
||||
|
// Minimal interface compliance |
||||
|
class ComponentSimpleMock { |
||||
|
// Essential props and methods only |
||||
|
// Basic computed properties |
||||
|
// No complex behavior |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### **2. Standard Mock (Integration Testing)** |
||||
|
|
||||
|
```typescript |
||||
|
// Full interface compliance |
||||
|
class ComponentStandardMock { |
||||
|
// All props, methods, computed properties |
||||
|
// Realistic behavior simulation |
||||
|
// Helper methods for test scenarios |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### **3. Complex Mock (Advanced Testing)** |
||||
|
|
||||
|
```typescript |
||||
|
// Enhanced testing capabilities |
||||
|
class ComponentComplexMock extends ComponentStandardMock { |
||||
|
// Mock event listeners |
||||
|
// Performance testing hooks |
||||
|
// Error scenario simulation |
||||
|
// Accessibility testing support |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### **Mock Component Structure** |
||||
|
|
||||
|
Each mock component provides: |
||||
|
|
||||
|
- Same interface as original component |
||||
|
- Simplified behavior for testing |
||||
|
- Helper methods for test scenarios |
||||
|
- Computed properties for state validation |
||||
|
|
||||
|
### **Enhanced Mock Architecture Validation** ✅ **NEW** |
||||
|
|
||||
|
The three-tier mock architecture (Simple/Standard/Complex) has been successfully |
||||
|
validated through real-world implementation: |
||||
|
|
||||
|
#### **Tier 1: Simple Mock** |
||||
|
|
||||
|
```typescript |
||||
|
class ComponentSimpleMock { |
||||
|
// Basic interface compliance |
||||
|
// Minimal implementation for simple tests |
||||
|
// Fast execution for high-volume testing |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### **Tier 2: Standard Mock** |
||||
|
|
||||
|
```typescript |
||||
|
class ComponentStandardMock { |
||||
|
// Full interface implementation |
||||
|
// Realistic behavior simulation |
||||
|
// Helper methods for common scenarios |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### **Tier 3: Complex Mock** |
||||
|
|
||||
|
```typescript |
||||
|
class ComponentComplexMock { |
||||
|
// Enhanced testing capabilities |
||||
|
// Validation and error simulation |
||||
|
// Advanced state management |
||||
|
// Performance testing support |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### **Factory Function Pattern** |
||||
|
|
||||
|
```typescript |
||||
|
// Specialized factory functions for common use cases |
||||
|
export const createComponentMock = () => |
||||
|
new ComponentStandardMock({ type: 'default' }) |
||||
|
|
||||
|
export const createSpecializedMock = () => |
||||
|
new ComponentComplexMock({ |
||||
|
options: { filter: 'active', sort: 'name' } |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
### **Mock Usage Examples** |
||||
|
|
||||
|
```typescript |
||||
|
export default class ComponentMock { |
||||
|
// Props simulation |
||||
|
props: ComponentProps |
||||
|
|
||||
|
// Computed properties |
||||
|
get computedProp(): boolean { |
||||
|
return this.props.condition |
||||
|
} |
||||
|
|
||||
|
// Mock methods |
||||
|
mockMethod(): void { |
||||
|
// Simulate behavior |
||||
|
} |
||||
|
|
||||
|
// Helper methods |
||||
|
getCssClasses(): string[] { |
||||
|
return ['base-class', 'conditional-class'] |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Test Patterns |
||||
|
|
||||
|
### **Component Testing Template** |
||||
|
|
||||
|
```typescript |
||||
|
import { mount } from '@vue/test-utils' |
||||
|
import { createComponentWrapper } from '@/test/utils/componentTestUtils' |
||||
|
|
||||
|
describe('ComponentName', () => { |
||||
|
let wrapper: VueWrapper<any> |
||||
|
|
||||
|
const mountComponent = (props = {}) => { |
||||
|
return mount(ComponentName, { |
||||
|
props: { ...defaultProps, ...props } |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
wrapper = mountComponent() |
||||
|
}) |
||||
|
|
||||
|
afterEach(() => { |
||||
|
wrapper?.unmount() |
||||
|
}) |
||||
|
|
||||
|
describe('Component Rendering', () => { |
||||
|
it('should render correctly', () => { |
||||
|
expect(wrapper.exists()).toBe(true) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
### **Mock Integration Testing** |
||||
|
|
||||
|
```typescript |
||||
|
import ComponentMock from '@/test/__mocks__/Component.mock' |
||||
|
|
||||
|
it('should work with mock component', () => { |
||||
|
const mock = new ComponentMock() |
||||
|
expect(mock.shouldShow).toBe(true) |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
### **Event Testing** |
||||
|
|
||||
|
```typescript |
||||
|
it('should emit event when triggered', async () => { |
||||
|
await wrapper.find('button').trigger('click') |
||||
|
expect(wrapper.emitted('event-name')).toBeTruthy() |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
### **Prop Validation** |
||||
|
|
||||
|
```typescript |
||||
|
it('should accept all required props', () => { |
||||
|
wrapper = mountComponent() |
||||
|
expect(wrapper.vm.propName).toBeDefined() |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
## Test Categories |
||||
|
|
||||
|
### **Required Coverage Areas** |
||||
|
|
||||
|
1. **Component Rendering** - Existence, structure, conditional rendering |
||||
|
2. **Component Styling** - CSS classes, responsive design, framework |
||||
|
integration |
||||
|
3. **Component Props** - Required/optional prop handling, type validation |
||||
|
4. **User Interactions** - Click events, form inputs, keyboard navigation |
||||
|
5. **Component Methods** - Method existence, functionality, return values |
||||
|
6. **Edge Cases** - Empty/null props, rapid interactions, state changes |
||||
|
7. **Error Handling** - Invalid props, malformed data, graceful degradation |
||||
|
8. **Accessibility** - Semantic HTML, ARIA attributes, keyboard navigation |
||||
|
9. **Performance** - Render time, memory leaks, rapid re-renders |
||||
|
10. **Integration** - Parent-child interaction, dependency injection |
||||
|
|
||||
|
### **Error Handling Testing** |
||||
|
|
||||
|
```typescript |
||||
|
const invalidPropCombinations = [ |
||||
|
null, undefined, 'invalid', 0, -1, {}, [], |
||||
|
() => {}, NaN, Infinity |
||||
|
] |
||||
|
|
||||
|
invalidPropCombinations.forEach(invalidProp => { |
||||
|
it(`should handle invalid prop: ${invalidProp}`, () => { |
||||
|
wrapper = mountComponent({ prop: invalidProp }) |
||||
|
expect(wrapper.exists()).toBe(true) |
||||
|
// Verify graceful handling |
||||
|
}) |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
## Centralized Test Utilities |
||||
|
|
||||
|
### **Component Testing Utilities** |
||||
|
|
||||
|
```typescript |
||||
|
import { |
||||
|
createComponentWrapper, |
||||
|
createTestDataFactory, |
||||
|
testLifecycleEvents, |
||||
|
testComputedProperties, |
||||
|
testWatchers, |
||||
|
testPerformance, |
||||
|
testAccessibility, |
||||
|
testErrorHandling |
||||
|
} from '@/test/utils/componentTestUtils' |
||||
|
|
||||
|
// Component wrapper factory |
||||
|
const wrapperFactory = createComponentWrapper( |
||||
|
Component, |
||||
|
defaultProps, |
||||
|
globalOptions |
||||
|
) |
||||
|
|
||||
|
// Test data factory |
||||
|
const createTestProps = createTestDataFactory({ |
||||
|
prop1: 'default', |
||||
|
prop2: true |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
### **Test Data Factories** |
||||
|
|
||||
|
```typescript |
||||
|
import { |
||||
|
createMockContact, |
||||
|
createMockProject, |
||||
|
createMockUser |
||||
|
} from '@/test/factories/contactFactory' |
||||
|
|
||||
|
const testContact = createMockContact({ |
||||
|
id: 'test-1', |
||||
|
name: 'Test User' |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
## Coverage Standards |
||||
|
|
||||
|
### **Coverage Standards by Component Complexity** |
||||
|
|
||||
|
| Component Complexity | Line Coverage | Branch Coverage | Function Coverage | |
||||
|
|---------------------|---------------|-----------------|-------------------| |
||||
|
| **Simple (<100 lines)** | 100% | 100% | 100% | |
||||
|
| **Medium (100-300 lines)** | 95% | 90% | 100% | |
||||
|
| **Complex (300+ lines)** | 90% | 85% | 100% | |
||||
|
|
||||
|
### **Current Coverage Status** |
||||
|
|
||||
|
- **Simple Components**: Ready for implementation |
||||
|
- **Medium Components**: Ready for expansion |
||||
|
- **Complex Components**: Ready for expansion |
||||
|
- **Overall Coverage**: Varies by project implementation |
||||
|
|
||||
|
### **Test Infrastructure Requirements** |
||||
|
|
||||
|
- **Test Framework**: Vitest + JSDOM recommended |
||||
|
- **Component Testing**: Vue Test Utils integration |
||||
|
- **Mock Architecture**: Three-tier system (Simple/Standard/Complex) |
||||
|
- **Test Categories**: 10 comprehensive categories |
||||
|
- **Coverage Goals**: 100% for simple components, 90%+ for complex |
||||
|
|
||||
|
## Testing Philosophy |
||||
|
|
||||
|
### **Defensive Programming Validation** |
||||
|
|
||||
|
- **Real-world edge case protection** against invalid API responses |
||||
|
- **System stability assurance** preventing cascading failures |
||||
|
- **Production readiness** ensuring graceful error handling |
||||
|
|
||||
|
### **Comprehensive Error Scenarios** |
||||
|
|
||||
|
- **Invalid input testing** with 10+ different invalid prop combinations |
||||
|
- **Malformed data testing** with various corrupted data structures |
||||
|
- **Extreme value testing** with boundary conditions and edge cases |
||||
|
- **Concurrent error testing** with rapid state changes |
||||
|
|
||||
|
### **Benefits Beyond Coverage** |
||||
|
|
||||
|
1. **Defensive Programming Validation** - Components handle unexpected data |
||||
|
gracefully |
||||
|
2. **Real-World Resilience** - Tested against actual failure scenarios |
||||
|
3. **Developer Confidence** - Safe to refactor and extend components |
||||
|
4. **Production Stability** - Reduced support tickets and user complaints |
||||
|
|
||||
|
## Advanced Testing Patterns |
||||
|
|
||||
|
### **Performance Testing** ✅ **NEW** |
||||
|
|
||||
|
- Render time benchmarks |
||||
|
- Memory leak detection |
||||
|
- Rapid re-render efficiency |
||||
|
- Component cleanup validation |
||||
|
|
||||
|
#### **Advanced Performance Testing Patterns** |
||||
|
|
||||
|
```typescript |
||||
|
// Memory leak detection |
||||
|
it('should not cause memory leaks during prop changes', async () => { |
||||
|
const initialMemory = (performance as any).memory?.usedJSHeapSize || 0 |
||||
|
|
||||
|
for (let i = 0; i < 100; i++) { |
||||
|
await wrapper.setProps({ |
||||
|
queryParams: { iteration: i.toString() } |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const finalMemory = (performance as any).memory?.usedJSHeapSize || 0 |
||||
|
const memoryIncrease = finalMemory - initialMemory |
||||
|
|
||||
|
// Memory increase should be reasonable (less than 10MB) |
||||
|
expect(memoryIncrease).toBeLessThan(10 * 1024 * 1024) |
||||
|
}) |
||||
|
|
||||
|
// Rapid re-render efficiency |
||||
|
it('should handle rapid re-renders efficiently', async () => { |
||||
|
const start = performance.now() |
||||
|
|
||||
|
for (let i = 0; i < 50; i++) { |
||||
|
await wrapper.setProps({ |
||||
|
entityType: i % 2 === 0 ? 'type1' : 'type2', |
||||
|
queryParams: { index: i.toString() } |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const end = performance.now() |
||||
|
expect(end - start).toBeLessThan(500) // 500ms threshold for 50 updates |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
### **Snapshot Testing** ✅ **NEW** |
||||
|
|
||||
|
- DOM structure validation |
||||
|
- CSS class regression detection |
||||
|
- Accessibility attribute consistency |
||||
|
- Visual structure verification |
||||
|
|
||||
|
#### **Snapshot Testing Implementation** |
||||
|
|
||||
|
```typescript |
||||
|
describe('Snapshot Testing', () => { |
||||
|
it('should maintain consistent DOM structure', () => { |
||||
|
expect(wrapper.html()).toMatchSnapshot() |
||||
|
}) |
||||
|
|
||||
|
it('should maintain consistent structure with different props', () => { |
||||
|
wrapper = mountComponent({ type: 'alternative' }) |
||||
|
expect(wrapper.html()).toMatchSnapshot() |
||||
|
}) |
||||
|
|
||||
|
it('should maintain consistent structure with query params', () => { |
||||
|
wrapper = mountComponent({ |
||||
|
queryParams: { filter: 'active', sort: 'name' } |
||||
|
}) |
||||
|
expect(wrapper.html()).toMatchSnapshot() |
||||
|
}) |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
### **Mock Integration Testing** ✅ **NEW** |
||||
|
|
||||
|
- Mock component validation |
||||
|
- Factory function testing |
||||
|
- Mock behavior verification |
||||
|
- Integration with testing utilities |
||||
|
|
||||
|
#### **Mock Integration Testing Patterns** |
||||
|
|
||||
|
```typescript |
||||
|
describe('Mock Integration Testing', () => { |
||||
|
it('should work with simple mock', () => { |
||||
|
const mock = new ComponentSimpleMock() |
||||
|
expect(mock.navigationRoute).toEqual({ |
||||
|
name: 'default', |
||||
|
query: {} |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it('should work with standard mock', () => { |
||||
|
const mock = new ComponentStandardMock({ |
||||
|
type: 'special', |
||||
|
name: 'test' |
||||
|
}) |
||||
|
expect(mock.getType()).toBe('special') |
||||
|
expect(mock.getName()).toBe('test') |
||||
|
}) |
||||
|
|
||||
|
it('should work with complex mock', () => { |
||||
|
const mock = new ComponentComplexMock({ |
||||
|
type: 'advanced', |
||||
|
options: { filter: 'active' } |
||||
|
}) |
||||
|
|
||||
|
expect(mock.isValidState()).toBe(true) |
||||
|
expect(mock.getValidationErrors()).toEqual([]) |
||||
|
}) |
||||
|
|
||||
|
it('should work with factory functions', () => { |
||||
|
const defaultMock = createComponentMock() |
||||
|
const specializedMock = createSpecializedMock() |
||||
|
|
||||
|
expect(defaultMock.getType()).toBe('default') |
||||
|
expect(specializedMock.getOptions()).toHaveProperty('filter') |
||||
|
}) |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
## Project Implementation Tracking |
||||
|
|
||||
|
### **Setting Up Project-Specific Tracking** |
||||
|
|
||||
|
Each project should maintain its own tracking file to monitor testing progress |
||||
|
and coverage metrics. This keeps the universal MDC clean while providing a |
||||
|
template for project implementation. |
||||
|
|
||||
|
#### **Recommended Project Tracking Structure** |
||||
|
|
||||
|
```tree |
||||
|
src/test/ |
||||
|
├── README.md # Testing documentation |
||||
|
├── PROJECT_COVERAGE_TRACKING.md # Project-specific progress tracking |
||||
|
├── __mocks__/ # Mock implementations |
||||
|
├── utils/ # Test utilities |
||||
|
└── [test files] |
||||
|
``` |
||||
|
|
||||
|
#### **Project Tracking File Template** |
||||
|
|
||||
|
Create a `PROJECT_COVERAGE_TRACKING.md` file with: |
||||
|
|
||||
|
- **Current Coverage Status**: Component-by-component breakdown |
||||
|
- **Implementation Progress**: Phase completion status |
||||
|
- **Test Infrastructure Status**: Framework setup and metrics |
||||
|
- **Next Steps**: Immediate priorities and long-term goals |
||||
|
- **Lessons Learned**: Project-specific insights and best practices |
||||
|
|
||||
|
#### **Example Project Tracking Sections** |
||||
|
|
||||
|
```markdown |
||||
|
# [Project Name] Testing Coverage Tracking |
||||
|
|
||||
|
## Current Coverage Status |
||||
|
- Simple Components: X/Y at 100% coverage |
||||
|
- Medium Components: X/Y ready for expansion |
||||
|
- Complex Components: X/Y planned |
||||
|
|
||||
|
## Implementation Progress |
||||
|
- Phase 1: Simple Components ✅ COMPLETE |
||||
|
- Phase 2: Medium Components 🔄 IN PROGRESS |
||||
|
- Phase 3: Complex Components 🔄 PLANNED |
||||
|
|
||||
|
## Test Infrastructure Status |
||||
|
- Total Tests: X tests passing |
||||
|
- Test Files: X files |
||||
|
- Mock Files: X implementations |
||||
|
- Overall Coverage: X% (focused on simple components) |
||||
|
``` |
||||
|
|
||||
|
### **Integration with Universal MDC** |
||||
|
|
||||
|
- **MDC provides**: Testing patterns, mock architecture, best practices |
||||
|
- **Project tracking provides**: Implementation status, coverage metrics, |
||||
|
progress |
||||
|
- **Separation ensures**: MDC remains reusable, project data stays local |
||||
|
- **Template approach**: Other projects can copy and adapt the structure |
||||
|
|
||||
|
### **Benefits of This Approach** |
||||
|
|
||||
|
1. **Universal Reusability**: MDC works for any project |
||||
|
2. **Project Visibility**: Clear tracking of implementation progress |
||||
|
3. **Template Reuse**: Easy to set up tracking in new projects |
||||
|
4. **Clean Separation**: No project data polluting universal guidance |
||||
|
5. **Scalability**: Multiple projects can use the same MDC |
||||
|
|
||||
|
## Best Practices |
||||
|
|
||||
|
### **Test Organization** |
||||
|
|
||||
|
1. **Group related tests** using `describe` blocks |
||||
|
2. **Use descriptive test names** that explain the scenario |
||||
|
3. **Keep tests focused** on one specific behavior |
||||
|
4. **Use helper functions** for common setup |
||||
|
|
||||
|
### **Mock Design** |
||||
|
|
||||
|
1. **Maintain interface compatibility** with original components |
||||
|
2. **Provide helper methods** for common test scenarios |
||||
|
3. **Include computed properties** for state validation |
||||
|
4. **Document mock behavior** clearly |
||||
|
|
||||
|
### **Coverage Goals** |
||||
|
|
||||
|
1. **100% line coverage** for simple components |
||||
|
2. **100% branch coverage** for conditional logic |
||||
|
3. **100% function coverage** for all methods |
||||
|
4. **Edge case coverage** for error scenarios |
||||
|
|
||||
|
### **Lessons Learned from Implementation** ✅ **NEW** |
||||
|
|
||||
|
#### **1. Performance Testing Best Practices** |
||||
|
|
||||
|
- **Memory leak detection**: Use `performance.memory.usedJSHeapSize` for |
||||
|
memory profiling |
||||
|
- **Render time benchmarking**: Set realistic thresholds (100ms for single |
||||
|
render, 500ms for 50 updates) |
||||
|
- **Rapid re-render testing**: Test with 50+ prop changes to ensure |
||||
|
stability |
||||
|
|
||||
|
#### **2. Snapshot Testing Implementation** |
||||
|
|
||||
|
- **DOM structure validation**: Use `toMatchSnapshot()` for consistent |
||||
|
structure verification |
||||
|
- **Prop variation testing**: Test snapshots with different prop combinations |
||||
|
- **Regression prevention**: Snapshots catch unexpected DOM changes |
||||
|
|
||||
|
#### **3. Mock Integration Validation** |
||||
|
|
||||
|
- **Mock self-testing**: Test that mocks work correctly with testing |
||||
|
utilities |
||||
|
- **Factory function testing**: Validate specialized factory functions |
||||
|
- **Mock behavior verification**: Ensure mocks simulate real component |
||||
|
behavior |
||||
|
|
||||
|
#### **4. Edge Case Coverage** |
||||
|
|
||||
|
- **Null/undefined handling**: Test with `null as any` and `undefined` |
||||
|
props |
||||
|
- **Extreme values**: Test with very long strings and large numbers |
||||
|
- **Rapid changes**: Test with rapid prop changes to ensure stability |
||||
|
|
||||
|
#### **5. Accessibility Testing** |
||||
|
|
||||
|
- **Semantic structure**: Verify proper HTML elements and hierarchy |
||||
|
- **Component attributes**: Check component-specific attributes |
||||
|
- **Text content**: Validate text content and trimming |
||||
|
|
||||
|
## Future Improvements |
||||
|
|
||||
|
### **Implemented Enhancements** |
||||
|
|
||||
|
1. ✅ **Error handling** - Component error states and exception handling |
||||
|
2. ✅ **Performance testing** - Render time benchmarks and memory leak |
||||
|
detection |
||||
|
3. ✅ **Integration testing** - Parent-child component interaction and |
||||
|
dependency injection |
||||
|
4. ✅ **Snapshot testing** - DOM structure validation and CSS class |
||||
|
regression detection |
||||
|
5. ✅ **Accessibility compliance** - ARIA attributes and semantic structure |
||||
|
validation |
||||
|
|
||||
|
### **Future Enhancements** |
||||
|
|
||||
|
1. **Visual regression testing** - Automated UI consistency checks |
||||
|
2. **Cross-browser compatibility** testing |
||||
|
3. **Service layer integration** testing |
||||
|
4. **End-to-end component** testing |
||||
|
5. **Advanced performance** profiling |
||||
|
|
||||
|
### **Coverage Expansion** |
||||
|
|
||||
|
1. **Medium complexity components** (100-300 lines) |
||||
|
2. **Complex components** (300+ lines) |
||||
|
3. **Service layer testing** |
||||
|
4. **Utility function testing** |
||||
|
5. **API integration testing** |
||||
|
|
||||
|
## Troubleshooting |
||||
|
|
||||
|
### **Common Issues** |
||||
|
|
||||
|
1. **Import errors**: Check path aliases in `vitest.config.ts` |
||||
|
2. **Mock not found**: Verify mock file exists and exports correctly |
||||
|
3. **Test failures**: Check for timing issues with async operations |
||||
|
4. **Coverage gaps**: Add tests for uncovered code paths |
||||
|
|
||||
|
### **Debug Tips** |
||||
|
|
||||
|
1. **Use `console.log`** in tests for debugging |
||||
|
2. **Check test output** for detailed error messages |
||||
|
3. **Verify component props** are being passed correctly |
||||
|
4. **Test one assertion at a time** to isolate issues |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Status**: Active testing standards |
||||
|
**Priority**: High |
||||
|
**Estimated Effort**: Ongoing reference |
||||
|
**Dependencies**: Vitest, JSDOM, Vue Test Utils |
||||
|
**Stakeholders**: Development team, QA team |
||||
|
|
||||
|
## Competence Hooks |
||||
|
|
||||
|
- *Why this works*: Three-tier mock architecture provides flexibility, |
||||
|
comprehensive test categories ensure thorough coverage, performance testing |
||||
|
catches real-world issues early |
||||
|
- *Common pitfalls*: Not testing mocks themselves, missing edge case |
||||
|
coverage, ignoring performance implications |
||||
|
- *Next skill unlock*: Implement medium complexity component testing with |
||||
|
established patterns |
||||
|
- *Teach-back*: Explain how the three-tier mock architecture supports |
||||
|
different testing needs |
||||
|
|
||||
|
## Collaboration Hooks |
||||
|
|
||||
|
- **Reviewers**: Testing team, component developers, architecture team |
||||
|
- **Sign-off checklist**: All simple components at 100% coverage, mock |
||||
|
utilities documented, test patterns established, coverage expansion plan |
||||
|
approved |
||||
|
|
||||
|
## Assumptions & Limits |
||||
|
|
||||
|
- Assumes Vue/React component architecture |
||||
|
- Requires Vitest + JSDOM testing environment |
||||
|
- Mock complexity scales with component complexity |
||||
|
- Performance testing requires browser-like environment |
||||
|
|
||||
|
## References |
||||
|
|
||||
|
- [Vitest Documentation](https://vitest.dev/) |
||||
|
- [Vue Test Utils](https://test-utils.vuejs.org/) |
||||
|
- [JSDOM](https://github.com/jsdom/jsdom) |
||||
|
- [Testing Best Practices](https://testing-library.com/docs/guiding-principles) |
||||
|
|
||||
|
- **Sign-off checklist**: All simple components at 100% coverage, mock |
||||
|
utilities documented, test patterns established, coverage expansion plan |
||||
|
approved |
@ -0,0 +1,37 @@ |
|||||
|
# Husky Git Hooks - Optional Activation |
||||
|
|
||||
|
## How to Enable Husky Locally |
||||
|
|
||||
|
### Option 1: Environment Variable (Session Only) |
||||
|
```bash |
||||
|
export HUSKY_ENABLED=1 |
||||
|
``` |
||||
|
|
||||
|
### Option 2: Local File (Persistent) |
||||
|
```bash |
||||
|
touch .husky-enabled |
||||
|
``` |
||||
|
|
||||
|
### Option 3: Global Configuration |
||||
|
```bash |
||||
|
git config --global husky.enabled true |
||||
|
``` |
||||
|
|
||||
|
## Available Hooks |
||||
|
|
||||
|
- **pre-commit**: Runs `npm run lint-fix` before commits |
||||
|
- **commit-msg**: Validates commit message format |
||||
|
|
||||
|
## Disable Hooks |
||||
|
|
||||
|
```bash |
||||
|
unset HUSKY_ENABLED |
||||
|
rm .husky-enabled |
||||
|
``` |
||||
|
|
||||
|
## Why This Approach? |
||||
|
|
||||
|
- Hooks are committed to git for consistency |
||||
|
- Hooks don't run unless explicitly enabled |
||||
|
- Each developer can choose to use them |
||||
|
- No automatic activation on other systems |
@ -1,10 +1,11 @@ |
|||||
#!/usr/bin/env bash |
#!/usr/bin/env sh |
||||
# |
|
||||
# Husky Commit Message Hook |
|
||||
# Validates commit message format using commitlint |
|
||||
# |
|
||||
. "$(dirname -- "$0")/_/husky.sh" |
. "$(dirname -- "$0")/_/husky.sh" |
||||
|
|
||||
# Run commitlint but don't fail the commit (|| true) |
# Only run if Husky is enabled |
||||
# This provides helpful feedback without blocking commits |
if [ "$HUSKY_ENABLED" = "1" ] || [ -f .husky-enabled ]; then |
||||
npx commitlint --edit "$1" || true |
echo "Running commit-msg hooks..." |
||||
|
npx commitlint --edit "$1" |
||||
|
else |
||||
|
echo "Husky commit-msg hook skipped (not enabled)" |
||||
|
exit 0 |
||||
|
fi |
||||
|
@ -1,15 +1,11 @@ |
|||||
#!/usr/bin/env bash |
#!/usr/bin/env sh |
||||
# |
|
||||
# Husky Pre-commit Hook |
|
||||
# Runs Build Architecture Guard to check staged files |
|
||||
# |
|
||||
. "$(dirname -- "$0")/_/husky.sh" |
. "$(dirname -- "$0")/_/husky.sh" |
||||
|
|
||||
echo "🔍 Running Build Architecture Guard (pre-commit)..." |
# Only run if Husky is enabled |
||||
bash ./scripts/build-arch-guard.sh --staged || { |
if [ "$HUSKY_ENABLED" = "1" ] || [ -f .husky-enabled ]; then |
||||
echo |
echo "Running pre-commit hooks..." |
||||
echo "💡 To bypass this check for emergency commits, use:" |
npm run lint-fix |
||||
echo " git commit --no-verify" |
else |
||||
echo |
echo "Husky pre-commit hook skipped (not enabled)" |
||||
exit 1 |
exit 0 |
||||
} |
fi |
||||
|
@ -0,0 +1,381 @@ |
|||||
|
# Husky Conditional Activation System |
||||
|
|
||||
|
**Author**: Matthew Raymer |
||||
|
**Date**: 2025-08-21T09:40Z |
||||
|
**Status**: 🎯 **ACTIVE** - Git hooks with optional activation |
||||
|
|
||||
|
## Overview |
||||
|
|
||||
|
This document describes the **conditional Husky activation system** implemented |
||||
|
in the TimeSafari project. The system provides standardized git hooks that are |
||||
|
committed to version control but only activate when explicitly enabled by |
||||
|
individual developers. |
||||
|
|
||||
|
## Problem Statement |
||||
|
|
||||
|
Traditional Husky implementations face several challenges: |
||||
|
|
||||
|
1. **Automatic activation** on all systems can be disruptive |
||||
|
2. **Different environments** may have different requirements |
||||
|
3. **Team preferences** vary regarding git hook enforcement |
||||
|
4. **CI/CD systems** may not need or want git hooks |
||||
|
5. **New developers** may be surprised by unexpected hook behavior |
||||
|
|
||||
|
## Solution: Conditional Activation |
||||
|
|
||||
|
The conditional activation system solves these problems by: |
||||
|
|
||||
|
- **Committing hooks to git** for consistency and version control |
||||
|
- **Making hooks optional** by default |
||||
|
- **Providing multiple activation methods** for flexibility |
||||
|
- **Ensuring hooks exit gracefully** when disabled |
||||
|
- **Maintaining team standards** without forcing compliance |
||||
|
|
||||
|
## System Architecture |
||||
|
|
||||
|
### **Core Components** |
||||
|
|
||||
|
``` |
||||
|
.husky/ |
||||
|
├── _/husky.sh # Conditional activation logic |
||||
|
├── pre-commit # Pre-commit hook (linting) |
||||
|
├── commit-msg # Commit message validation |
||||
|
└── README.md # User activation instructions |
||||
|
``` |
||||
|
|
||||
|
### **Activation Methods** |
||||
|
|
||||
|
#### **Method 1: Environment Variable (Session Only)** |
||||
|
|
||||
|
```bash |
||||
|
export HUSKY_ENABLED=1 |
||||
|
``` |
||||
|
|
||||
|
- **Scope**: Current terminal session only |
||||
|
- **Use case**: Temporary activation for testing |
||||
|
- **Reset**: `unset HUSKY_ENABLED` |
||||
|
|
||||
|
#### **Method 2: Local File (Persistent)** |
||||
|
|
||||
|
```bash |
||||
|
touch .husky-enabled |
||||
|
``` |
||||
|
|
||||
|
- **Scope**: Current repository, persistent |
||||
|
- **Use case**: Long-term activation for development |
||||
|
- **Reset**: `rm .husky-enabled` |
||||
|
|
||||
|
#### **Method 3: Global Git Configuration** |
||||
|
|
||||
|
```bash |
||||
|
git config --global husky.enabled true |
||||
|
``` |
||||
|
|
||||
|
- **Scope**: All repositories for current user |
||||
|
- **Use case**: Developer preference across projects |
||||
|
- **Reset**: `git config --global --unset husky.enabled` |
||||
|
|
||||
|
## Implementation Details |
||||
|
|
||||
|
### **Conditional Activation Logic** |
||||
|
|
||||
|
The core logic in `.husky/_/husky.sh`: |
||||
|
|
||||
|
```bash |
||||
|
# Check if Husky is enabled for this user |
||||
|
if [ "$HUSKY_ENABLED" != "1" ] && [ ! -f .husky-enabled ]; then |
||||
|
echo "Husky is not enabled. To enable:" |
||||
|
echo " export HUSKY_ENABLED=1" |
||||
|
echo " or create .husky-enabled file" |
||||
|
exit 0 # Graceful exit, not an error |
||||
|
fi |
||||
|
``` |
||||
|
|
||||
|
### **Hook Behavior** |
||||
|
|
||||
|
When **disabled**: |
||||
|
|
||||
|
- Hooks display helpful activation instructions |
||||
|
- Exit with code 0 (success, not error) |
||||
|
- No git operations are blocked |
||||
|
- No performance impact |
||||
|
|
||||
|
When **enabled**: |
||||
|
|
||||
|
- Hooks run normally with full functionality |
||||
|
- Standard Husky behavior applies |
||||
|
- Git operations may be blocked if hooks fail |
||||
|
|
||||
|
## Available Hooks |
||||
|
|
||||
|
### **Pre-commit Hook** |
||||
|
|
||||
|
**File**: `.husky/pre-commit` |
||||
|
**Purpose**: Code quality enforcement before commits |
||||
|
**Action**: Runs `npm run lint-fix` |
||||
|
**When**: Before each commit |
||||
|
**Failure**: Prevents commit if linting fails |
||||
|
|
||||
|
**Activation Check**: |
||||
|
|
||||
|
```bash |
||||
|
if [ "$HUSKY_ENABLED" = "1" ] || [ -f .husky-enabled ]; then |
||||
|
echo "Running pre-commit hooks..." |
||||
|
npm run lint-fix |
||||
|
else |
||||
|
echo "Husky pre-commit hook skipped (not enabled)" |
||||
|
exit 0 |
||||
|
fi |
||||
|
``` |
||||
|
|
||||
|
### **Commit-msg Hook** |
||||
|
|
||||
|
**File**: `.husky/commit-msg` |
||||
|
**Purpose**: Commit message format validation |
||||
|
**Action**: Runs `npx commitlint --edit "$1"` |
||||
|
**When**: After commit message is written |
||||
|
**Failure**: Prevents commit if message format is invalid |
||||
|
|
||||
|
**Activation Check**: |
||||
|
|
||||
|
```bash |
||||
|
if [ "$HUSKY_ENABLED" = "1" ] || [ -f .husky-enabled ]; then |
||||
|
echo "Running commit-msg hooks..." |
||||
|
npx commitlint --edit "$1" |
||||
|
else |
||||
|
echo "Husky commit-msg hook skipped (not enabled)" |
||||
|
exit 0 |
||||
|
fi |
||||
|
``` |
||||
|
|
||||
|
## User Workflows |
||||
|
|
||||
|
### **New Developer Setup** |
||||
|
|
||||
|
1. **Clone repository** |
||||
|
|
||||
|
```bash |
||||
|
git clone <repository-url> |
||||
|
cd <repository-name> |
||||
|
``` |
||||
|
|
||||
|
2. **Hooks are present but inactive** |
||||
|
- Pre-commit and commit-msg hooks exist |
||||
|
- No automatic activation |
||||
|
- Git operations work normally |
||||
|
|
||||
|
3. **Optional: Enable hooks** |
||||
|
|
||||
|
```bash |
||||
|
# For current session only |
||||
|
export HUSKY_ENABLED=1 |
||||
|
|
||||
|
# For persistent activation |
||||
|
touch .husky-enabled |
||||
|
``` |
||||
|
|
||||
|
### **Daily Development** |
||||
|
|
||||
|
#### **With Hooks Disabled** |
||||
|
|
||||
|
```bash |
||||
|
git add . |
||||
|
git commit -m "feat: add new feature" |
||||
|
# Hooks are skipped, commit proceeds normally |
||||
|
``` |
||||
|
|
||||
|
#### **With Hooks Enabled** |
||||
|
|
||||
|
```bash |
||||
|
git add . |
||||
|
git commit -m "feat: add new feature" |
||||
|
# Pre-commit hook runs linting |
||||
|
# Commit-msg hook validates message format |
||||
|
# Commit only proceeds if all hooks pass |
||||
|
``` |
||||
|
|
||||
|
### **Troubleshooting** |
||||
|
|
||||
|
#### **Hooks Not Running** |
||||
|
|
||||
|
```bash |
||||
|
# Check if hooks are enabled |
||||
|
echo $HUSKY_ENABLED |
||||
|
ls -la .husky-enabled |
||||
|
|
||||
|
# Enable hooks |
||||
|
export HUSKY_ENABLED=1 |
||||
|
# or |
||||
|
touch .husky-enabled |
||||
|
``` |
||||
|
|
||||
|
#### **Hooks Running Unexpectedly** |
||||
|
|
||||
|
```bash |
||||
|
# Disable hooks |
||||
|
unset HUSKY_ENABLED |
||||
|
rm -f .husky-enabled |
||||
|
|
||||
|
# Check global configuration |
||||
|
git config --global --get husky.enabled |
||||
|
``` |
||||
|
|
||||
|
## Configuration Files |
||||
|
|
||||
|
### **`.gitignore` Entry** |
||||
|
|
||||
|
```gitignore |
||||
|
# Husky activation file (user-specific) |
||||
|
.husky-enabled |
||||
|
``` |
||||
|
|
||||
|
This ensures that: |
||||
|
|
||||
|
- Hooks are committed to git (team standard) |
||||
|
- Activation files are not committed (user preference) |
||||
|
- Each developer can control their own activation |
||||
|
|
||||
|
### **Package.json Dependencies** |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"devDependencies": { |
||||
|
"husky": "^9.0.11", |
||||
|
"@commitlint/cli": "^18.6.1", |
||||
|
"@commitlint/config-conventional": "^18.6.2" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Benefits |
||||
|
|
||||
|
### **For Development Teams** |
||||
|
|
||||
|
1. **Consistency**: All developers have the same hook configuration |
||||
|
2. **Flexibility**: Individual developers can choose activation |
||||
|
3. **Standards**: Team coding standards are enforced when enabled |
||||
|
4. **Version Control**: Hook configuration is tracked and versioned |
||||
|
5. **Onboarding**: New developers get standardized setup |
||||
|
|
||||
|
### **For Individual Developers** |
||||
|
|
||||
|
1. **Choice**: Control over when hooks are active |
||||
|
2. **Performance**: No unnecessary hook execution when disabled |
||||
|
3. **Learning**: Gradual adoption of git hook practices |
||||
|
4. **Debugging**: Easy to disable hooks for troubleshooting |
||||
|
5. **Environment**: Works across different development environments |
||||
|
|
||||
|
### **For CI/CD Systems** |
||||
|
|
||||
|
1. **No Interference**: Hooks don't run in automated environments |
||||
|
2. **Consistency**: Same hook logic available if needed |
||||
|
3. **Flexibility**: Can enable hooks in specific CI scenarios |
||||
|
4. **Reliability**: No unexpected hook failures in automation |
||||
|
|
||||
|
## Best Practices |
||||
|
|
||||
|
### **Team Adoption** |
||||
|
|
||||
|
1. **Start with disabled hooks** for new team members |
||||
|
2. **Encourage gradual adoption** of hook activation |
||||
|
3. **Document hook benefits** and usage patterns |
||||
|
4. **Provide training** on hook configuration |
||||
|
5. **Support troubleshooting** when hooks cause issues |
||||
|
|
||||
|
### **Hook Development** |
||||
|
|
||||
|
1. **Keep hooks lightweight** and fast |
||||
|
2. **Provide clear error messages** when hooks fail |
||||
|
3. **Include helpful activation instructions** in disabled state |
||||
|
4. **Test hooks in both enabled and disabled states** |
||||
|
5. **Document hook requirements** and dependencies |
||||
|
|
||||
|
### **Configuration Management** |
||||
|
|
||||
|
1. **Commit hook files** to version control |
||||
|
2. **Ignore activation files** in .gitignore |
||||
|
3. **Document activation methods** clearly |
||||
|
4. **Provide examples** for common use cases |
||||
|
5. **Maintain backward compatibility** when updating hooks |
||||
|
|
||||
|
## Troubleshooting Guide |
||||
|
|
||||
|
### **Common Issues** |
||||
|
|
||||
|
#### **Hooks Running When Not Expected** |
||||
|
|
||||
|
```bash |
||||
|
# Check all activation methods |
||||
|
echo "Environment variable: $HUSKY_ENABLED" |
||||
|
echo "Local file exists: $([ -f .husky-enabled ] && echo "yes" || echo "no")" |
||||
|
echo "Global config: $(git config --global --get husky.enabled)" |
||||
|
``` |
||||
|
|
||||
|
#### **Hooks Not Running When Expected** |
||||
|
|
||||
|
```bash |
||||
|
# Verify hook files exist and are executable |
||||
|
ls -la .husky/ |
||||
|
chmod +x .husky/pre-commit |
||||
|
chmod +x .husky/commit-msg |
||||
|
``` |
||||
|
|
||||
|
#### **Permission Denied Errors** |
||||
|
|
||||
|
```bash |
||||
|
# Fix file permissions |
||||
|
chmod +x .husky/_/husky.sh |
||||
|
chmod +x .husky/pre-commit |
||||
|
chmod +x .husky/commit-msg |
||||
|
``` |
||||
|
|
||||
|
### **Debug Mode** |
||||
|
|
||||
|
Enable debug output to troubleshoot hook issues: |
||||
|
|
||||
|
```bash |
||||
|
export HUSKY_DEBUG=1 |
||||
|
export HUSKY_ENABLED=1 |
||||
|
git commit -m "test: debug commit" |
||||
|
``` |
||||
|
|
||||
|
## Future Enhancements |
||||
|
|
||||
|
### **Planned Improvements** |
||||
|
|
||||
|
1. **Hook Configuration File**: YAML/JSON configuration for hook behavior |
||||
|
2. **Selective Hook Activation**: Enable/disable specific hooks individually |
||||
|
3. **Hook Performance Metrics**: Track execution time and success rates |
||||
|
4. **Integration with IDEs**: IDE-specific activation methods |
||||
|
5. **Remote Configuration**: Team-wide hook settings via configuration |
||||
|
|
||||
|
### **Extension Points** |
||||
|
|
||||
|
1. **Custom Hook Scripts**: Easy addition of project-specific hooks |
||||
|
2. **Hook Templates**: Reusable hook patterns for common tasks |
||||
|
3. **Conditional Logic**: Complex activation rules based on context |
||||
|
4. **Notification System**: Hook status reporting and alerts |
||||
|
5. **Analytics**: Hook usage and effectiveness tracking |
||||
|
|
||||
|
## Conclusion |
||||
|
|
||||
|
The conditional Husky activation system provides an elegant solution to the |
||||
|
challenges of git hook management in team environments. By committing |
||||
|
standardized hooks while making activation optional, it balances consistency |
||||
|
with flexibility, enabling teams to maintain coding standards without forcing compliance. |
||||
|
|
||||
|
This approach supports gradual adoption, respects individual preferences, and |
||||
|
provides a solid foundation for git hook practices that can evolve with team needs |
||||
|
and project requirements. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Related Documents**: |
||||
|
|
||||
|
- [Git Hooks Best Practices](./git-hooks-best-practices.md) |
||||
|
- [Code Quality Standards](./code-quality-standards.md) |
||||
|
- [Development Workflow](./development-workflow.md) |
||||
|
|
||||
|
**Maintainer**: Development Team |
||||
|
**Review Schedule**: Quarterly |
||||
|
**Next Review**: 2025-11-21 |
File diff suppressed because it is too large
@ -0,0 +1,180 @@ |
|||||
|
# TimeSafari Testing Coverage Tracking |
||||
|
|
||||
|
**Project**: TimeSafari |
||||
|
**Last Updated**: 2025-08-21T09:40Z |
||||
|
**Status**: Active Testing Implementation |
||||
|
|
||||
|
## Current Coverage Status |
||||
|
|
||||
|
### **Simple Components** (6/6 at 100% coverage) ✅ |
||||
|
|
||||
|
| Component | Lines | Tests | Coverage | Status | Completed Date | |
||||
|
|-----------|-------|-------|----------|---------|----------------| |
||||
|
| **RegistrationNotice.vue** | 34 | 34 | 100% | ✅ Complete | 2025-07-29 | |
||||
|
| **LargeIdenticonModal.vue** | 39 | 31 | 100% | ✅ Complete | 2025-07-29 | |
||||
|
| **ProjectIcon.vue** | 48 | 39 | 100% | ✅ Complete | 2025-07-29 | |
||||
|
| **ContactBulkActions.vue** | 43 | 43 | 100% | ✅ Complete | 2025-07-29 | |
||||
|
| **EntityIcon.vue** | 82 | 0* | 100% | ✅ Complete | 2025-07-29 | |
||||
|
| **ShowAllCard.vue** | 66 | 52 | 100% | ✅ Complete | 2025-08-21 | |
||||
|
|
||||
|
*EntityIcon.vue has 100% coverage but no dedicated test file (covered by |
||||
|
LargeIdenticonModal tests) |
||||
|
|
||||
|
### **Medium Components** (0/0 ready for expansion) |
||||
|
|
||||
|
| Component | Lines | Estimated Tests | Priority | Status | |
||||
|
|-----------|-------|-----------------|----------|---------| |
||||
|
| *Ready for testing implementation* | - | - | - | 🔄 Pending | |
||||
|
|
||||
|
### **Complex Components** (0/0 ready for expansion) |
||||
|
|
||||
|
| Component | Lines | Estimated Tests | Priority | Status | |
||||
|
|-----------|-------|-----------------|----------|---------| |
||||
|
| *Ready for testing implementation* | - | - | 🔄 Pending | |
||||
|
|
||||
|
## Test Infrastructure Status |
||||
|
|
||||
|
- **Total Tests**: 201 tests passing |
||||
|
- **Test Files**: 6 files |
||||
|
- **Mock Files**: 7 mock implementations |
||||
|
- **Test Categories**: 10 comprehensive categories |
||||
|
- **Overall Coverage**: 3.24% (focused on simple components) |
||||
|
- **Enhanced Testing**: All simple components now have comprehensive test coverage |
||||
|
|
||||
|
## Implementation Progress |
||||
|
|
||||
|
### **Phase 1: Simple Components** ✅ **COMPLETE** |
||||
|
|
||||
|
**Objective**: Establish 100% coverage for all simple components (<100 lines) |
||||
|
|
||||
|
**Status**: 100% Complete (6/6 components) |
||||
|
|
||||
|
**Components Completed**: |
||||
|
- RegistrationNotice.vue (34 lines, 34 tests) |
||||
|
- LargeIdenticonModal.vue (39 lines, 31 tests) |
||||
|
- ProjectIcon.vue (48 lines, 39 tests) |
||||
|
- ContactBulkActions.vue (43 lines, 43 tests) |
||||
|
- EntityIcon.vue (82 lines, 0 tests - covered by LargeIdenticonModal) |
||||
|
- ShowAllCard.vue (66 lines, 52 tests) |
||||
|
|
||||
|
**Key Achievements**: |
||||
|
- Established three-tier mock architecture (Simple/Standard/Complex) |
||||
|
- Implemented comprehensive test patterns across 10 categories |
||||
|
- Achieved 100% coverage for all simple components |
||||
|
- Created reusable mock utilities and testing patterns |
||||
|
|
||||
|
### **Phase 2: Medium Components** 🔄 **READY TO START** |
||||
|
|
||||
|
**Objective**: Expand testing to medium complexity components (100-300 lines) |
||||
|
|
||||
|
**Status**: Ready to begin |
||||
|
|
||||
|
**Target Components**: |
||||
|
- Components with 100-300 lines |
||||
|
- Focus on business logic components |
||||
|
- Priority: High-value, frequently used components |
||||
|
|
||||
|
**Coverage Goals**: |
||||
|
- Line Coverage: 95% |
||||
|
- Branch Coverage: 90% |
||||
|
- Function Coverage: 100% |
||||
|
|
||||
|
### **Phase 3: Complex Components** 🔄 **PLANNED** |
||||
|
|
||||
|
**Objective**: Implement testing for complex components (300+ lines) |
||||
|
|
||||
|
**Status**: Planned for future |
||||
|
|
||||
|
**Target Components**: |
||||
|
- Components with 300+ lines |
||||
|
- Complex business logic components |
||||
|
- Integration-heavy components |
||||
|
|
||||
|
**Coverage Goals**: |
||||
|
- Line Coverage: 90% |
||||
|
- Branch Coverage: 85% |
||||
|
- Function Coverage: 100% |
||||
|
|
||||
|
## Testing Patterns Established |
||||
|
|
||||
|
### **Mock Architecture** ✅ |
||||
|
|
||||
|
- **Three-tier system**: Simple/Standard/Complex mocks |
||||
|
- **Factory functions**: Specialized mock creation |
||||
|
- **Interface compliance**: Full compatibility with original components |
||||
|
- **Helper methods**: Common test scenario support |
||||
|
|
||||
|
### **Test Categories** ✅ |
||||
|
|
||||
|
1. **Component Rendering** - Structure and conditional rendering |
||||
|
2. **Component Styling** - CSS classes and responsive design |
||||
|
3. **Component Props** - Validation and handling |
||||
|
4. **User Interactions** - Events and accessibility |
||||
|
5. **Component Methods** - Functionality and return values |
||||
|
6. **Edge Cases** - Null/undefined and rapid changes |
||||
|
7. **Error Handling** - Invalid props and graceful degradation |
||||
|
8. **Accessibility** - Semantic HTML and ARIA |
||||
|
9. **Performance** - Render time and memory leaks |
||||
|
10. **Integration** - Parent-child and dependency injection |
||||
|
|
||||
|
### **Advanced Testing Features** ✅ |
||||
|
|
||||
|
- **Performance Testing**: Memory leak detection, render time benchmarking |
||||
|
- **Snapshot Testing**: DOM structure validation and regression prevention |
||||
|
- **Mock Integration**: Mock component validation and testing |
||||
|
- **Edge Case Coverage**: Comprehensive error scenario testing |
||||
|
|
||||
|
## Next Steps |
||||
|
|
||||
|
### **Immediate Priorities** |
||||
|
|
||||
|
1. **Identify medium complexity components** for Phase 2 |
||||
|
2. **Prioritize components** by business value and usage frequency |
||||
|
3. **Apply established patterns** to medium components |
||||
|
4. **Expand mock architecture** for medium complexity needs |
||||
|
|
||||
|
### **Medium Term Goals** |
||||
|
|
||||
|
1. **Achieve 90%+ coverage** for medium components |
||||
|
2. **Establish testing patterns** for complex components |
||||
|
3. **Implement service layer testing** |
||||
|
4. **Add API integration testing** |
||||
|
|
||||
|
### **Long Term Vision** |
||||
|
|
||||
|
1. **Comprehensive test coverage** across all component types |
||||
|
2. **Automated testing pipeline** integration |
||||
|
3. **Performance regression testing** |
||||
|
4. **Cross-browser compatibility testing** |
||||
|
|
||||
|
## Lessons Learned |
||||
|
|
||||
|
### **Success Factors** |
||||
|
|
||||
|
1. **Three-tier mock architecture** provides flexibility and scalability |
||||
|
2. **Comprehensive test categories** ensure thorough coverage |
||||
|
3. **Performance testing** catches real-world issues early |
||||
|
4. **Snapshot testing** prevents regression issues |
||||
|
5. **Mock integration testing** validates testing infrastructure |
||||
|
|
||||
|
### **Best Practices Established** |
||||
|
|
||||
|
1. **Start with simple components** to establish patterns |
||||
|
2. **Use factory functions** for specialized mock creation |
||||
|
3. **Test mocks themselves** to ensure reliability |
||||
|
4. **Include performance testing** for stability |
||||
|
5. **Document patterns** for team adoption |
||||
|
|
||||
|
## Resources |
||||
|
|
||||
|
- **MDC Guide**: `.cursor/rules/unit_testing_mocks.mdc` |
||||
|
- **Test Directory**: `src/test/` |
||||
|
- **Mock Implementations**: `src/test/__mocks__/` |
||||
|
- **Test Utilities**: `src/test/utils/` |
||||
|
- **Examples**: `src/test/examples/` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Maintainer**: Development Team |
||||
|
**Review Schedule**: Monthly |
||||
|
**Next Review**: 2025-09-21 |
@ -0,0 +1,494 @@ |
|||||
|
/** |
||||
|
* ShowAllCard Component Tests |
||||
|
* |
||||
|
* Comprehensive unit tests covering all required test categories: |
||||
|
* - Component Rendering |
||||
|
* - Component Styling |
||||
|
* - Component Props |
||||
|
* - User Interactions |
||||
|
* - Component Methods |
||||
|
* - Edge Cases |
||||
|
* - Error Handling |
||||
|
* - Accessibility |
||||
|
* - Performance |
||||
|
* - Integration |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
*/ |
||||
|
|
||||
|
import { mount, VueWrapper } from '@vue/test-utils' |
||||
|
import ShowAllCard from '@/components/ShowAllCard.vue' |
||||
|
import { |
||||
|
ShowAllCardSimpleMock, |
||||
|
ShowAllCardStandardMock, |
||||
|
ShowAllCardComplexMock, |
||||
|
createPeopleShowAllCardMock, |
||||
|
createProjectsShowAllCardMock, |
||||
|
createShowAllCardMockWithComplexQuery |
||||
|
} from './__mocks__/ShowAllCard.mock' |
||||
|
|
||||
|
describe('ShowAllCard', () => { |
||||
|
let wrapper: VueWrapper<any> |
||||
|
|
||||
|
// Default props for testing
|
||||
|
const defaultProps = { |
||||
|
entityType: 'people' as const, |
||||
|
routeName: 'contacts', |
||||
|
queryParams: {} |
||||
|
} |
||||
|
|
||||
|
// Component wrapper factory
|
||||
|
const mountComponent = (props = {}) => { |
||||
|
return mount(ShowAllCard, { |
||||
|
props: { ...defaultProps, ...props } |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
wrapper = mountComponent() |
||||
|
}) |
||||
|
|
||||
|
afterEach(() => { |
||||
|
wrapper?.unmount() |
||||
|
}) |
||||
|
|
||||
|
describe('Component Rendering', () => { |
||||
|
it('should render correctly', () => { |
||||
|
expect(wrapper.exists()).toBe(true) |
||||
|
expect(wrapper.find('li').exists()).toBe(true) |
||||
|
expect(wrapper.find('router-link').exists()).toBe(true) |
||||
|
}) |
||||
|
|
||||
|
it('should render with correct structure', () => { |
||||
|
const listItem = wrapper.find('li') |
||||
|
const routerLink = wrapper.find('router-link') |
||||
|
const icon = wrapper.find('font-awesome') |
||||
|
const title = wrapper.find('h3') |
||||
|
|
||||
|
expect(listItem.exists()).toBe(true) |
||||
|
expect(routerLink.exists()).toBe(true) |
||||
|
expect(icon.exists()).toBe(true) |
||||
|
expect(title.exists()).toBe(true) |
||||
|
expect(title.text()).toBe('Show All') |
||||
|
}) |
||||
|
|
||||
|
it('should render conditionally based on props', () => { |
||||
|
wrapper = mountComponent({ entityType: 'projects' }) |
||||
|
expect(wrapper.exists()).toBe(true) |
||||
|
|
||||
|
wrapper = mountComponent({ entityType: 'people' }) |
||||
|
expect(wrapper.exists()).toBe(true) |
||||
|
}) |
||||
|
|
||||
|
it('should render with different entity types', () => { |
||||
|
const peopleWrapper = mountComponent({ entityType: 'people' }) |
||||
|
const projectsWrapper = mountComponent({ entityType: 'projects' }) |
||||
|
|
||||
|
expect(peopleWrapper.exists()).toBe(true) |
||||
|
expect(projectsWrapper.exists()).toBe(true) |
||||
|
|
||||
|
peopleWrapper.unmount() |
||||
|
projectsWrapper.unmount() |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Component Styling', () => { |
||||
|
it('should have correct CSS classes on list item', () => { |
||||
|
const listItem = wrapper.find('li') |
||||
|
expect(listItem.classes()).toContain('cursor-pointer') |
||||
|
}) |
||||
|
|
||||
|
it('should have correct CSS classes on icon', () => { |
||||
|
const icon = wrapper.find('font-awesome') |
||||
|
expect(icon.exists()).toBe(true) |
||||
|
expect(icon.attributes('icon')).toBe('circle-right') |
||||
|
expect(icon.classes()).toContain('text-blue-500') |
||||
|
expect(icon.classes()).toContain('text-5xl') |
||||
|
expect(icon.classes()).toContain('mb-1') |
||||
|
}) |
||||
|
|
||||
|
it('should have correct CSS classes on title', () => { |
||||
|
const title = wrapper.find('h3') |
||||
|
expect(title.classes()).toContain('text-xs') |
||||
|
expect(title.classes()).toContain('text-slate-500') |
||||
|
expect(title.classes()).toContain('font-medium') |
||||
|
expect(title.classes()).toContain('italic') |
||||
|
expect(title.classes()).toContain('text-ellipsis') |
||||
|
expect(title.classes()).toContain('whitespace-nowrap') |
||||
|
expect(title.classes()).toContain('overflow-hidden') |
||||
|
}) |
||||
|
|
||||
|
it('should have responsive design classes', () => { |
||||
|
const title = wrapper.find('h3') |
||||
|
expect(title.classes()).toContain('text-ellipsis') |
||||
|
expect(title.classes()).toContain('whitespace-nowrap') |
||||
|
expect(title.classes()).toContain('overflow-hidden') |
||||
|
}) |
||||
|
|
||||
|
it('should have Tailwind CSS integration', () => { |
||||
|
const icon = wrapper.find('font-awesome') |
||||
|
const title = wrapper.find('h3') |
||||
|
|
||||
|
expect(icon.classes()).toContain('text-blue-500') |
||||
|
expect(icon.classes()).toContain('text-5xl') |
||||
|
expect(title.classes()).toContain('text-slate-500') |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Component Props', () => { |
||||
|
it('should accept all required props', () => { |
||||
|
expect(wrapper.vm.entityType).toBe('people') |
||||
|
expect(wrapper.vm.routeName).toBe('contacts') |
||||
|
expect(wrapper.vm.queryParams).toEqual({}) |
||||
|
}) |
||||
|
|
||||
|
it('should handle required entityType prop', () => { |
||||
|
wrapper = mountComponent({ entityType: 'projects' }) |
||||
|
expect(wrapper.vm.entityType).toBe('projects') |
||||
|
|
||||
|
wrapper = mountComponent({ entityType: 'people' }) |
||||
|
expect(wrapper.vm.entityType).toBe('people') |
||||
|
}) |
||||
|
|
||||
|
it('should handle required routeName prop', () => { |
||||
|
wrapper = mountComponent({ routeName: 'projects' }) |
||||
|
expect(wrapper.vm.routeName).toBe('projects') |
||||
|
|
||||
|
wrapper = mountComponent({ routeName: 'contacts' }) |
||||
|
expect(wrapper.vm.routeName).toBe('contacts') |
||||
|
}) |
||||
|
|
||||
|
it('should handle optional queryParams prop', () => { |
||||
|
const queryParams = { filter: 'active', sort: 'name' } |
||||
|
wrapper = mountComponent({ queryParams }) |
||||
|
expect(wrapper.vm.queryParams).toEqual(queryParams) |
||||
|
}) |
||||
|
|
||||
|
it('should handle empty queryParams prop', () => { |
||||
|
wrapper = mountComponent({ queryParams: {} }) |
||||
|
expect(wrapper.vm.queryParams).toEqual({}) |
||||
|
}) |
||||
|
|
||||
|
it('should handle undefined queryParams prop', () => { |
||||
|
wrapper = mountComponent({ queryParams: undefined }) |
||||
|
expect(wrapper.vm.queryParams).toEqual({}) |
||||
|
}) |
||||
|
|
||||
|
it('should validate prop types correctly', () => { |
||||
|
expect(typeof wrapper.vm.entityType).toBe('string') |
||||
|
expect(typeof wrapper.vm.routeName).toBe('string') |
||||
|
expect(typeof wrapper.vm.queryParams).toBe('object') |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('User Interactions', () => { |
||||
|
it('should have clickable router link', () => { |
||||
|
const routerLink = wrapper.find('router-link') |
||||
|
expect(routerLink.exists()).toBe(true) |
||||
|
expect(routerLink.attributes('to')).toBeDefined() |
||||
|
}) |
||||
|
|
||||
|
it('should have accessible cursor pointer', () => { |
||||
|
const listItem = wrapper.find('li') |
||||
|
expect(listItem.classes()).toContain('cursor-pointer') |
||||
|
}) |
||||
|
|
||||
|
it('should support keyboard navigation', () => { |
||||
|
const routerLink = wrapper.find('router-link') |
||||
|
expect(routerLink.exists()).toBe(true) |
||||
|
// Router link should be keyboard accessible by default
|
||||
|
}) |
||||
|
|
||||
|
it('should have hover effects defined in CSS', () => { |
||||
|
// Check that hover effects are defined in the component's style section
|
||||
|
const component = wrapper.vm |
||||
|
expect(component).toBeDefined() |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Component Methods', () => { |
||||
|
it('should have navigationRoute computed property', () => { |
||||
|
expect(wrapper.vm.navigationRoute).toBeDefined() |
||||
|
expect(typeof wrapper.vm.navigationRoute).toBe('object') |
||||
|
}) |
||||
|
|
||||
|
it('should compute navigationRoute correctly', () => { |
||||
|
const expectedRoute = { |
||||
|
name: 'contacts', |
||||
|
query: {} |
||||
|
} |
||||
|
expect(wrapper.vm.navigationRoute).toEqual(expectedRoute) |
||||
|
}) |
||||
|
|
||||
|
it('should compute navigationRoute with custom props', () => { |
||||
|
wrapper = mountComponent({ |
||||
|
routeName: 'projects', |
||||
|
queryParams: { filter: 'active' } |
||||
|
}) |
||||
|
|
||||
|
const expectedRoute = { |
||||
|
name: 'projects', |
||||
|
query: { filter: 'active' } |
||||
|
} |
||||
|
expect(wrapper.vm.navigationRoute).toEqual(expectedRoute) |
||||
|
}) |
||||
|
|
||||
|
it('should handle complex query parameters', () => { |
||||
|
const complexQuery = { |
||||
|
filter: 'active', |
||||
|
sort: 'name', |
||||
|
page: '1', |
||||
|
limit: '20' |
||||
|
} |
||||
|
|
||||
|
wrapper = mountComponent({ queryParams: complexQuery }) |
||||
|
|
||||
|
const expectedRoute = { |
||||
|
name: 'contacts', |
||||
|
query: complexQuery |
||||
|
} |
||||
|
expect(wrapper.vm.navigationRoute).toEqual(expectedRoute) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Edge Cases', () => { |
||||
|
it('should handle empty string routeName', () => { |
||||
|
wrapper = mountComponent({ routeName: '' }) |
||||
|
expect(wrapper.vm.navigationRoute).toEqual({ |
||||
|
name: '', |
||||
|
query: {} |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it('should handle null queryParams', () => { |
||||
|
wrapper = mountComponent({ queryParams: null as any }) |
||||
|
expect(wrapper.vm.navigationRoute).toEqual({ |
||||
|
name: 'contacts', |
||||
|
query: null |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it('should handle undefined queryParams', () => { |
||||
|
wrapper = mountComponent({ queryParams: undefined }) |
||||
|
expect(wrapper.vm.navigationRoute).toEqual({ |
||||
|
name: 'contacts', |
||||
|
query: {} |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it('should handle empty object queryParams', () => { |
||||
|
wrapper = mountComponent({ queryParams: {} }) |
||||
|
expect(wrapper.vm.navigationRoute).toEqual({ |
||||
|
name: 'contacts', |
||||
|
query: {} |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it('should handle rapid prop changes', async () => { |
||||
|
for (let i = 0; i < 10; i++) { |
||||
|
await wrapper.setProps({ |
||||
|
entityType: i % 2 === 0 ? 'people' : 'projects', |
||||
|
routeName: `route-${i}`, |
||||
|
queryParams: { index: i.toString() } |
||||
|
}) |
||||
|
|
||||
|
expect(wrapper.vm.entityType).toBe(i % 2 === 0 ? 'people' : 'projects') |
||||
|
expect(wrapper.vm.routeName).toBe(`route-${i}`) |
||||
|
expect(wrapper.vm.queryParams).toEqual({ index: i.toString() }) |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Error Handling', () => { |
||||
|
it('should handle invalid entityType gracefully', () => { |
||||
|
wrapper = mountComponent({ entityType: 'invalid' as any }) |
||||
|
expect(wrapper.exists()).toBe(true) |
||||
|
expect(wrapper.vm.entityType).toBe('invalid') |
||||
|
}) |
||||
|
|
||||
|
it('should handle malformed queryParams gracefully', () => { |
||||
|
wrapper = mountComponent({ queryParams: 'invalid' as any }) |
||||
|
expect(wrapper.exists()).toBe(true) |
||||
|
// Should handle gracefully even with invalid queryParams
|
||||
|
}) |
||||
|
|
||||
|
it('should handle missing props gracefully', () => { |
||||
|
// Component should not crash with missing props
|
||||
|
expect(() => mountComponent({})).not.toThrow() |
||||
|
}) |
||||
|
|
||||
|
it('should handle extreme prop values', () => { |
||||
|
const extremeProps = { |
||||
|
entityType: 'people', |
||||
|
routeName: 'a'.repeat(1000), |
||||
|
queryParams: { key: 'value'.repeat(1000) } |
||||
|
} |
||||
|
|
||||
|
wrapper = mountComponent(extremeProps) |
||||
|
expect(wrapper.exists()).toBe(true) |
||||
|
expect(wrapper.vm.routeName).toBe(extremeProps.routeName) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Accessibility', () => { |
||||
|
it('should have semantic HTML structure', () => { |
||||
|
expect(wrapper.find('li').exists()).toBe(true) |
||||
|
expect(wrapper.find('h3').exists()).toBe(true) |
||||
|
}) |
||||
|
|
||||
|
it('should have proper heading hierarchy', () => { |
||||
|
const heading = wrapper.find('h3') |
||||
|
expect(heading.exists()).toBe(true) |
||||
|
expect(heading.text()).toBe('Show All') |
||||
|
}) |
||||
|
|
||||
|
it('should have accessible icon', () => { |
||||
|
const icon = wrapper.find('font-awesome') |
||||
|
expect(icon.exists()).toBe(true) |
||||
|
expect(icon.attributes('icon')).toBe('circle-right') |
||||
|
}) |
||||
|
|
||||
|
it('should have proper text content', () => { |
||||
|
const title = wrapper.find('h3') |
||||
|
expect(title.text()).toBe('Show All') |
||||
|
expect(title.text().trim()).toBe('Show All') |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Performance', () => { |
||||
|
it('should render within acceptable time', () => { |
||||
|
const start = performance.now() |
||||
|
wrapper = mountComponent() |
||||
|
const end = performance.now() |
||||
|
|
||||
|
expect(end - start).toBeLessThan(100) // 100ms threshold
|
||||
|
}) |
||||
|
|
||||
|
it('should handle rapid re-renders efficiently', async () => { |
||||
|
const start = performance.now() |
||||
|
|
||||
|
for (let i = 0; i < 50; i++) { |
||||
|
await wrapper.setProps({ |
||||
|
entityType: i % 2 === 0 ? 'people' : 'projects', |
||||
|
queryParams: { index: i.toString() } |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const end = performance.now() |
||||
|
expect(end - start).toBeLessThan(500) // 500ms threshold for 50 updates
|
||||
|
}) |
||||
|
|
||||
|
it('should not cause memory leaks during prop changes', async () => { |
||||
|
const initialMemory = (performance as any).memory?.usedJSHeapSize || 0 |
||||
|
|
||||
|
for (let i = 0; i < 100; i++) { |
||||
|
await wrapper.setProps({ |
||||
|
queryParams: { iteration: i.toString() } |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const finalMemory = (performance as any).memory?.usedJSHeapSize || 0 |
||||
|
const memoryIncrease = finalMemory - initialMemory |
||||
|
|
||||
|
// Memory increase should be reasonable (less than 10MB)
|
||||
|
expect(memoryIncrease).toBeLessThan(10 * 1024 * 1024) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Integration', () => { |
||||
|
it('should work with router-link integration', () => { |
||||
|
const routerLink = wrapper.find('router-link') |
||||
|
expect(routerLink.exists()).toBe(true) |
||||
|
expect(routerLink.attributes('to')).toBeDefined() |
||||
|
}) |
||||
|
|
||||
|
it('should work with FontAwesome icon integration', () => { |
||||
|
const icon = wrapper.find('font-awesome') |
||||
|
expect(icon.exists()).toBe(true) |
||||
|
expect(icon.attributes('icon')).toBe('circle-right') |
||||
|
}) |
||||
|
|
||||
|
it('should work with Vue Router navigation', () => { |
||||
|
const navigationRoute = wrapper.vm.navigationRoute |
||||
|
expect(navigationRoute).toHaveProperty('name') |
||||
|
expect(navigationRoute).toHaveProperty('query') |
||||
|
}) |
||||
|
|
||||
|
it('should integrate with parent component props', () => { |
||||
|
const parentProps = { |
||||
|
entityType: 'projects' as const, |
||||
|
routeName: 'project-list', |
||||
|
queryParams: { category: 'featured' } |
||||
|
} |
||||
|
|
||||
|
wrapper = mountComponent(parentProps) |
||||
|
|
||||
|
expect(wrapper.vm.entityType).toBe(parentProps.entityType) |
||||
|
expect(wrapper.vm.routeName).toBe(parentProps.routeName) |
||||
|
expect(wrapper.vm.queryParams).toEqual(parentProps.queryParams) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Mock Integration Testing', () => { |
||||
|
it('should work with simple mock', () => { |
||||
|
const mock = new ShowAllCardSimpleMock() |
||||
|
expect(mock.navigationRoute).toEqual({ |
||||
|
name: 'contacts', |
||||
|
query: {} |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it('should work with standard mock', () => { |
||||
|
const mock = new ShowAllCardStandardMock({ |
||||
|
entityType: 'projects', |
||||
|
routeName: 'projects' |
||||
|
}) |
||||
|
expect(mock.getEntityType()).toBe('projects') |
||||
|
expect(mock.getRouteName()).toBe('projects') |
||||
|
}) |
||||
|
|
||||
|
it('should work with complex mock', () => { |
||||
|
const mock = new ShowAllCardComplexMock({ |
||||
|
entityType: 'people', |
||||
|
routeName: 'contacts', |
||||
|
queryParams: { filter: 'active' } |
||||
|
}) |
||||
|
|
||||
|
expect(mock.isValidState()).toBe(true) |
||||
|
expect(mock.getValidationErrors()).toEqual([]) |
||||
|
}) |
||||
|
|
||||
|
it('should work with factory functions', () => { |
||||
|
const peopleMock = createPeopleShowAllCardMock() |
||||
|
const projectsMock = createProjectsShowAllCardMock() |
||||
|
|
||||
|
expect(peopleMock.getEntityType()).toBe('people') |
||||
|
expect(projectsMock.getEntityType()).toBe('projects') |
||||
|
}) |
||||
|
|
||||
|
it('should work with complex query mock', () => { |
||||
|
const mock = createShowAllCardMockWithComplexQuery() |
||||
|
expect(mock.getQueryParams()).toHaveProperty('filter') |
||||
|
expect(mock.getQueryParams()).toHaveProperty('sort') |
||||
|
expect(mock.getQueryParams()).toHaveProperty('page') |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Snapshot Testing', () => { |
||||
|
it('should maintain consistent DOM structure', () => { |
||||
|
expect(wrapper.html()).toMatchSnapshot() |
||||
|
}) |
||||
|
|
||||
|
it('should maintain consistent structure with different props', () => { |
||||
|
wrapper = mountComponent({ entityType: 'projects' }) |
||||
|
expect(wrapper.html()).toMatchSnapshot() |
||||
|
}) |
||||
|
|
||||
|
it('should maintain consistent structure with query params', () => { |
||||
|
wrapper = mountComponent({ |
||||
|
queryParams: { filter: 'active', sort: 'name' } |
||||
|
}) |
||||
|
expect(wrapper.html()).toMatchSnapshot() |
||||
|
}) |
||||
|
}) |
||||
|
}) |
@ -0,0 +1,298 @@ |
|||||
|
/** |
||||
|
* ShowAllCard Mock Component |
||||
|
* |
||||
|
* Provides three-tier mock architecture for testing: |
||||
|
* - Simple: Basic interface compliance |
||||
|
* - Standard: Full interface with realistic behavior |
||||
|
* - Complex: Enhanced testing capabilities |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
*/ |
||||
|
|
||||
|
import { RouteLocationRaw } from "vue-router"; |
||||
|
|
||||
|
export interface ShowAllCardProps { |
||||
|
entityType: "people" | "projects"; |
||||
|
routeName: string; |
||||
|
queryParams?: Record<string, string>; |
||||
|
} |
||||
|
|
||||
|
export interface ShowAllCardMock { |
||||
|
props: ShowAllCardProps; |
||||
|
navigationRoute: RouteLocationRaw; |
||||
|
getCssClasses(): string[]; |
||||
|
getIconClasses(): string[]; |
||||
|
getTitleClasses(): string[]; |
||||
|
simulateClick(): void; |
||||
|
simulateHover(): void; |
||||
|
getComputedNavigationRoute(): RouteLocationRaw; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Simple Mock - Basic interface compliance |
||||
|
*/ |
||||
|
export class ShowAllCardSimpleMock implements ShowAllCardMock { |
||||
|
props: ShowAllCardProps = { |
||||
|
entityType: "people", |
||||
|
routeName: "contacts", |
||||
|
queryParams: {} |
||||
|
}; |
||||
|
|
||||
|
get navigationRoute(): RouteLocationRaw { |
||||
|
return { |
||||
|
name: this.props.routeName, |
||||
|
query: this.props.queryParams || {} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
getCssClasses(): string[] { |
||||
|
return ["cursor-pointer"]; |
||||
|
} |
||||
|
|
||||
|
getIconClasses(): string[] { |
||||
|
return ["text-blue-500", "text-5xl", "mb-1"]; |
||||
|
} |
||||
|
|
||||
|
getTitleClasses(): string[] { |
||||
|
return ["text-xs", "text-slate-500", "font-medium", "italic", "text-ellipsis", "whitespace-nowrap", "overflow-hidden"]; |
||||
|
} |
||||
|
|
||||
|
simulateClick(): void { |
||||
|
// Basic click simulation
|
||||
|
} |
||||
|
|
||||
|
simulateHover(): void { |
||||
|
// Basic hover simulation
|
||||
|
} |
||||
|
|
||||
|
getComputedNavigationRoute(): RouteLocationRaw { |
||||
|
return this.navigationRoute; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Standard Mock - Full interface compliance with realistic behavior |
||||
|
*/ |
||||
|
export class ShowAllCardStandardMock extends ShowAllCardSimpleMock { |
||||
|
constructor(props?: Partial<ShowAllCardProps>) { |
||||
|
super(); |
||||
|
if (props) { |
||||
|
this.props = { ...this.props, ...props }; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getCssClasses(): string[] { |
||||
|
return [ |
||||
|
"cursor-pointer", |
||||
|
"show-all-card", |
||||
|
`entity-type-${this.props.entityType}` |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
getIconClasses(): string[] { |
||||
|
return [ |
||||
|
"text-blue-500", |
||||
|
"text-5xl", |
||||
|
"mb-1", |
||||
|
"fa-circle-right", |
||||
|
"transition-transform" |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
getTitleClasses(): string[] { |
||||
|
return [ |
||||
|
"text-xs", |
||||
|
"text-slate-500", |
||||
|
"font-medium", |
||||
|
"italic", |
||||
|
"text-ellipsis", |
||||
|
"whitespace-nowrap", |
||||
|
"overflow-hidden", |
||||
|
"show-all-title" |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
simulateClick(): void { |
||||
|
// Simulate router navigation
|
||||
|
this.getComputedNavigationRoute(); |
||||
|
} |
||||
|
|
||||
|
simulateHover(): void { |
||||
|
// Simulate hover effects
|
||||
|
this.getIconClasses().push("hover:scale-110"); |
||||
|
} |
||||
|
|
||||
|
getComputedNavigationRoute(): RouteLocationRaw { |
||||
|
return { |
||||
|
name: this.props.routeName, |
||||
|
query: this.props.queryParams || {} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// Helper methods for test scenarios
|
||||
|
setEntityType(entityType: "people" | "projects"): void { |
||||
|
this.props.entityType = entityType; |
||||
|
} |
||||
|
|
||||
|
setRouteName(routeName: string): void { |
||||
|
this.props.routeName = routeName; |
||||
|
} |
||||
|
|
||||
|
setQueryParams(queryParams: Record<string, string>): void { |
||||
|
this.props.queryParams = queryParams; |
||||
|
} |
||||
|
|
||||
|
getEntityType(): string { |
||||
|
return this.props.entityType; |
||||
|
} |
||||
|
|
||||
|
getRouteName(): string { |
||||
|
return this.props.routeName; |
||||
|
} |
||||
|
|
||||
|
getQueryParams(): Record<string, string> { |
||||
|
return this.props.queryParams || {}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Complex Mock - Enhanced testing capabilities |
||||
|
*/ |
||||
|
export class ShowAllCardComplexMock extends ShowAllCardStandardMock { |
||||
|
private clickCount: number = 0; |
||||
|
private hoverCount: number = 0; |
||||
|
private navigationHistory: RouteLocationRaw[] = []; |
||||
|
|
||||
|
constructor(props?: Partial<ShowAllCardProps>) { |
||||
|
super(props); |
||||
|
} |
||||
|
|
||||
|
simulateClick(): void { |
||||
|
this.clickCount++; |
||||
|
const route = this.getComputedNavigationRoute(); |
||||
|
this.navigationHistory.push(route); |
||||
|
|
||||
|
// Simulate click event with additional context
|
||||
|
this.getIconClasses().push("clicked"); |
||||
|
} |
||||
|
|
||||
|
simulateHover(): void { |
||||
|
this.hoverCount++; |
||||
|
this.getIconClasses().push("hovered", "scale-110"); |
||||
|
} |
||||
|
|
||||
|
// Performance testing hooks
|
||||
|
getClickCount(): number { |
||||
|
return this.clickCount; |
||||
|
} |
||||
|
|
||||
|
getHoverCount(): number { |
||||
|
return this.hoverCount; |
||||
|
} |
||||
|
|
||||
|
getNavigationHistory(): RouteLocationRaw[] { |
||||
|
return [...this.navigationHistory]; |
||||
|
} |
||||
|
|
||||
|
// Error scenario simulation
|
||||
|
simulateInvalidRoute(): void { |
||||
|
this.props.routeName = "invalid-route"; |
||||
|
} |
||||
|
|
||||
|
simulateEmptyQueryParams(): void { |
||||
|
this.props.queryParams = {}; |
||||
|
} |
||||
|
|
||||
|
simulateComplexQueryParams(): void { |
||||
|
this.props.queryParams = { |
||||
|
filter: "active", |
||||
|
sort: "name", |
||||
|
page: "1", |
||||
|
limit: "20" |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// Accessibility testing support
|
||||
|
getAccessibilityAttributes(): Record<string, string> { |
||||
|
return { |
||||
|
role: "listitem", |
||||
|
"aria-label": `Show all ${this.props.entityType}`, |
||||
|
tabindex: "0" |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// State validation helpers
|
||||
|
isValidState(): boolean { |
||||
|
return !!this.props.entityType && |
||||
|
!!this.props.routeName && |
||||
|
typeof this.props.queryParams === "object"; |
||||
|
} |
||||
|
|
||||
|
getValidationErrors(): string[] { |
||||
|
const errors: string[] = []; |
||||
|
|
||||
|
if (!this.props.entityType) { |
||||
|
errors.push("entityType is required"); |
||||
|
} |
||||
|
|
||||
|
if (!this.props.routeName) { |
||||
|
errors.push("routeName is required"); |
||||
|
} |
||||
|
|
||||
|
if (this.props.queryParams && typeof this.props.queryParams !== "object") { |
||||
|
errors.push("queryParams must be an object"); |
||||
|
} |
||||
|
|
||||
|
return errors; |
||||
|
} |
||||
|
|
||||
|
// Reset functionality for test isolation
|
||||
|
reset(): void { |
||||
|
this.clickCount = 0; |
||||
|
this.hoverCount = 0; |
||||
|
this.navigationHistory = []; |
||||
|
this.props = { |
||||
|
entityType: "people", |
||||
|
routeName: "contacts", |
||||
|
queryParams: {} |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Default export for convenience
|
||||
|
export default ShowAllCardComplexMock; |
||||
|
|
||||
|
// Factory functions for common test scenarios
|
||||
|
export const createShowAllCardMock = (props?: Partial<ShowAllCardProps>): ShowAllCardComplexMock => { |
||||
|
return new ShowAllCardComplexMock(props); |
||||
|
}; |
||||
|
|
||||
|
export const createPeopleShowAllCardMock = (): ShowAllCardComplexMock => { |
||||
|
return new ShowAllCardComplexMock({ |
||||
|
entityType: "people", |
||||
|
routeName: "contacts", |
||||
|
queryParams: { filter: "all" } |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
export const createProjectsShowAllCardMock = (): ShowAllCardComplexMock => { |
||||
|
return new ShowAllCardComplexMock({ |
||||
|
entityType: "projects", |
||||
|
routeName: "projects", |
||||
|
queryParams: { sort: "name" } |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
export const createShowAllCardMockWithComplexQuery = (): ShowAllCardComplexMock => { |
||||
|
return new ShowAllCardComplexMock({ |
||||
|
entityType: "people", |
||||
|
routeName: "contacts", |
||||
|
queryParams: { |
||||
|
filter: "active", |
||||
|
sort: "name", |
||||
|
page: "1", |
||||
|
limit: "20", |
||||
|
search: "test" |
||||
|
} |
||||
|
}); |
||||
|
}; |
@ -0,0 +1,28 @@ |
|||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html |
||||
|
|
||||
|
exports[`ShowAllCard > Snapshot Testing > should maintain consistent DOM structure 1`] = ` |
||||
|
"<li data-v-18958371="" class="cursor-pointer"> |
||||
|
<router-link data-v-18958371="" to="[object Object]" class="block text-center"> |
||||
|
<font-awesome data-v-18958371="" icon="circle-right" class="text-blue-500 text-5xl mb-1"></font-awesome> |
||||
|
<h3 data-v-18958371="" class="text-xs text-slate-500 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden"> Show All </h3> |
||||
|
</router-link> |
||||
|
</li>" |
||||
|
`; |
||||
|
|
||||
|
exports[`ShowAllCard > Snapshot Testing > should maintain consistent structure with different props 1`] = ` |
||||
|
"<li data-v-18958371="" class="cursor-pointer"> |
||||
|
<router-link data-v-18958371="" to="[object Object]" class="block text-center"> |
||||
|
<font-awesome data-v-18958371="" icon="circle-right" class="text-blue-500 text-5xl mb-1"></font-awesome> |
||||
|
<h3 data-v-18958371="" class="text-xs text-slate-500 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden"> Show All </h3> |
||||
|
</router-link> |
||||
|
</li>" |
||||
|
`; |
||||
|
|
||||
|
exports[`ShowAllCard > Snapshot Testing > should maintain consistent structure with query params 1`] = ` |
||||
|
"<li data-v-18958371="" class="cursor-pointer"> |
||||
|
<router-link data-v-18958371="" to="[object Object]" class="block text-center"> |
||||
|
<font-awesome data-v-18958371="" icon="circle-right" class="text-blue-500 text-5xl mb-1"></font-awesome> |
||||
|
<h3 data-v-18958371="" class="text-xs text-slate-500 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden"> Show All </h3> |
||||
|
</router-link> |
||||
|
</li>" |
||||
|
`; |
Loading…
Reference in new issue