Compare commits
8 Commits
ee35719cd5
...
886baa8bea
Author | SHA1 | Date |
---|---|---|
|
886baa8bea | 3 days ago |
|
aee53242a0 | 3 days ago |
|
4829582584 | 3 days ago |
|
6cf5183371 | 3 days ago |
|
75ddea4071 | 3 days ago |
|
5aceab434f | 3 days ago |
|
fca4bf5d16 | 3 days ago |
|
e2c812a5a6 | 3 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 |
|||
# |
|||
# Husky Commit Message Hook |
|||
# Validates commit message format using commitlint |
|||
# |
|||
#!/usr/bin/env sh |
|||
. "$(dirname -- "$0")/_/husky.sh" |
|||
|
|||
# Run commitlint but don't fail the commit (|| true) |
|||
# This provides helpful feedback without blocking commits |
|||
npx commitlint --edit "$1" || true |
|||
# Only run if Husky is enabled |
|||
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 |
|||
|
@ -1,15 +1,11 @@ |
|||
#!/usr/bin/env bash |
|||
# |
|||
# Husky Pre-commit Hook |
|||
# Runs Build Architecture Guard to check staged files |
|||
# |
|||
#!/usr/bin/env sh |
|||
. "$(dirname -- "$0")/_/husky.sh" |
|||
|
|||
echo "🔍 Running Build Architecture Guard (pre-commit)..." |
|||
bash ./scripts/build-arch-guard.sh --staged || { |
|||
echo |
|||
echo "💡 To bypass this check for emergency commits, use:" |
|||
echo " git commit --no-verify" |
|||
echo |
|||
exit 1 |
|||
} |
|||
# Only run if Husky is enabled |
|||
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 |
|||
|
@ -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