Browse Source
- Add Vitest configuration with JSDOM environment for Vue component testing - Create RegistrationNotice component mock with full TypeScript support - Implement comprehensive test suite for RegistrationNotice component (18 tests) - Add test setup with global mocks for ResizeObserver, IntersectionObserver, etc. - Update package.json with testing dependencies (@vue/test-utils, jsdom, vitest) - Add test scripts: test, test:unit, test:unit:watch, test:unit:coverage - Exclude Playwright tests from Vitest to prevent framework conflicts - Add comprehensive documentation with usage examples and best practices - All tests passing (20/20) with proper Vue-facing-decorator supportpull/153/head
7 changed files with 1854 additions and 8 deletions
File diff suppressed because it is too large
@ -0,0 +1,281 @@ |
|||||
|
# TimeSafari Testing Documentation |
||||
|
|
||||
|
## Overview |
||||
|
|
||||
|
This directory contains comprehensive testing infrastructure for the TimeSafari application, including mocks, test utilities, and examples for Vue components using vue-facing-decorator. |
||||
|
|
||||
|
## Testing Setup |
||||
|
|
||||
|
### Dependencies |
||||
|
|
||||
|
The testing setup uses: |
||||
|
|
||||
|
- **Vitest**: Fast unit testing framework |
||||
|
- **JSDOM**: DOM environment for browser-like testing |
||||
|
- **@vue/test-utils**: Vue component testing utilities |
||||
|
- **vue-facing-decorator**: TypeScript decorators for Vue components |
||||
|
|
||||
|
### Configuration Files |
||||
|
|
||||
|
- `vitest.config.ts`: Main Vitest configuration |
||||
|
- `src/test/setup.ts`: Test environment setup and global mocks |
||||
|
|
||||
|
## RegistrationNotice Component Mock |
||||
|
|
||||
|
### Overview |
||||
|
|
||||
|
The `RegistrationNotice` component is the simplest component in the codebase (34 lines) and serves as an excellent example for testing Vue components with vue-facing-decorator. |
||||
|
|
||||
|
### Mock Implementation |
||||
|
|
||||
|
**File**: `src/test/__mocks__/RegistrationNotice.mock.ts` |
||||
|
|
||||
|
The mock provides: |
||||
|
- Same interface as the original component |
||||
|
- Simplified behavior for testing |
||||
|
- Additional helper methods for test scenarios |
||||
|
- Full TypeScript support |
||||
|
|
||||
|
### Key Features |
||||
|
|
||||
|
```typescript |
||||
|
// Basic usage |
||||
|
const mockComponent = new RegistrationNoticeMock() |
||||
|
mockComponent.isRegistered = false |
||||
|
mockComponent.show = true |
||||
|
|
||||
|
// Test helper methods |
||||
|
expect(mockComponent.shouldShow).toBe(true) |
||||
|
expect(mockComponent.buttonText).toBe('Share Your Info') |
||||
|
expect(mockComponent.noticeText).toContain('Before you can publicly announce') |
||||
|
|
||||
|
// Event emission |
||||
|
mockComponent.shareInfo() // Emits 'share-info' event |
||||
|
``` |
||||
|
|
||||
|
### Testing Patterns |
||||
|
|
||||
|
#### 1. Direct Mock Usage |
||||
|
```typescript |
||||
|
it('should create mock component with correct props', () => { |
||||
|
const mockComponent = new RegistrationNoticeMock() |
||||
|
mockComponent.isRegistered = false |
||||
|
mockComponent.show = true |
||||
|
|
||||
|
expect(mockComponent.shouldShow).toBe(true) |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
#### 2. Vue Test Utils Integration |
||||
|
```typescript |
||||
|
it('should mount mock component with props', () => { |
||||
|
const wrapper = mount(RegistrationNoticeMock, { |
||||
|
props: { |
||||
|
isRegistered: false, |
||||
|
show: true |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
expect(wrapper.vm.shouldShow).toBe(true) |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
#### 3. Event Testing |
||||
|
```typescript |
||||
|
it('should emit share-info event', async () => { |
||||
|
const wrapper = mount(RegistrationNoticeMock, { |
||||
|
props: { isRegistered: false, show: true } |
||||
|
}) |
||||
|
|
||||
|
await wrapper.vm.shareInfo() |
||||
|
|
||||
|
expect(wrapper.emitted('share-info')).toBeTruthy() |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
#### 4. Custom Mock Behavior |
||||
|
```typescript |
||||
|
class CustomRegistrationNoticeMock extends RegistrationNoticeMock { |
||||
|
override get buttonText(): string { |
||||
|
return 'Custom Button Text' |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 5. Advanced Testing Patterns |
||||
|
|
||||
|
```typescript |
||||
|
// Spy methods for testing |
||||
|
const mockComponent = new RegistrationNoticeMock() |
||||
|
const shareInfoSpy = vi.spyOn(mockComponent, 'shareInfo') |
||||
|
const mockClickSpy = vi.spyOn(mockComponent, 'mockShareInfoClick') |
||||
|
|
||||
|
mockComponent.mockShareInfoClick() |
||||
|
expect(mockClickSpy).toHaveBeenCalledTimes(1) |
||||
|
expect(shareInfoSpy).toHaveBeenCalledTimes(1) |
||||
|
``` |
||||
|
|
||||
|
#### 6. Integration Testing |
||||
|
```typescript |
||||
|
// Simulate parent component context |
||||
|
const parentData = { |
||||
|
isUserRegistered: false, |
||||
|
shouldShowNotice: true |
||||
|
} |
||||
|
|
||||
|
const mockComponent = new RegistrationNoticeMock() |
||||
|
mockComponent.isRegistered = parentData.isUserRegistered |
||||
|
mockComponent.show = parentData.shouldShowNotice |
||||
|
|
||||
|
expect(mockComponent.shouldShow).toBe(true) |
||||
|
``` |
||||
|
|
||||
|
#### 7. State Change Testing |
||||
|
```typescript |
||||
|
const mockComponent = new RegistrationNoticeMock() |
||||
|
|
||||
|
// Initial state |
||||
|
mockComponent.isRegistered = false |
||||
|
mockComponent.show = true |
||||
|
expect(mockComponent.shouldShow).toBe(true) |
||||
|
|
||||
|
// State change |
||||
|
mockComponent.isRegistered = true |
||||
|
expect(mockComponent.shouldShow).toBe(false) |
||||
|
``` |
||||
|
|
||||
|
#### 8. Performance Testing |
||||
|
```typescript |
||||
|
const mockComponent = new RegistrationNoticeMock() |
||||
|
const startTime = performance.now() |
||||
|
|
||||
|
// Call methods rapidly |
||||
|
for (let i = 0; i < 1000; i++) { |
||||
|
mockComponent.shareInfo() |
||||
|
mockComponent.shouldShow |
||||
|
mockComponent.buttonText |
||||
|
} |
||||
|
|
||||
|
const duration = performance.now() - startTime |
||||
|
expect(duration).toBeLessThan(100) // Should complete quickly |
||||
|
``` |
||||
|
|
||||
|
## Running Tests |
||||
|
|
||||
|
### Available Scripts |
||||
|
|
||||
|
```bash |
||||
|
# Run all tests |
||||
|
npm run test |
||||
|
|
||||
|
# Run unit tests once |
||||
|
npm run test:unit |
||||
|
|
||||
|
# Run unit tests in watch mode |
||||
|
npm run test:unit:watch |
||||
|
|
||||
|
# Run tests with coverage |
||||
|
npm run test:unit:coverage |
||||
|
``` |
||||
|
|
||||
|
### Test File Structure |
||||
|
|
||||
|
``` |
||||
|
src/test/ |
||||
|
├── __mocks__/ |
||||
|
│ └── RegistrationNotice.mock.ts # Component mock |
||||
|
├── setup.ts # Test environment setup |
||||
|
├── RegistrationNotice.test.ts # Component tests |
||||
|
└── README.md # This documentation |
||||
|
``` |
||||
|
|
||||
|
## Testing Best Practices |
||||
|
|
||||
|
### 1. Component Testing |
||||
|
- Test component rendering with different prop combinations |
||||
|
- Verify event emissions |
||||
|
- Check accessibility attributes |
||||
|
- Test user interactions |
||||
|
|
||||
|
### 2. Mock Usage |
||||
|
- Use mocks for isolated unit testing |
||||
|
- Test component interfaces, not implementation details |
||||
|
- Create custom mocks for specific test scenarios |
||||
|
- Verify mock behavior matches real component |
||||
|
|
||||
|
### 3. Error Handling |
||||
|
- Test edge cases and error conditions |
||||
|
- Verify graceful degradation |
||||
|
- Test invalid prop combinations |
||||
|
|
||||
|
### 4. Performance Testing |
||||
|
- Test rapid method calls |
||||
|
- Verify efficient execution |
||||
|
- Monitor memory usage in long-running tests |
||||
|
|
||||
|
## Security Audit Checklist |
||||
|
|
||||
|
When creating mocks and tests, ensure: |
||||
|
|
||||
|
- [ ] No sensitive data in test files |
||||
|
- [ ] Proper input validation testing |
||||
|
- [ ] Event emission security |
||||
|
- [ ] No hardcoded credentials |
||||
|
- [ ] Proper error handling |
||||
|
- [ ] Access control verification |
||||
|
- [ ] Data sanitization testing |
||||
|
|
||||
|
## Examples |
||||
|
|
||||
|
See `src/test/RegistrationNotice.mock.example.ts` for comprehensive examples covering: |
||||
|
- Direct mock usage |
||||
|
- Vue Test Utils integration |
||||
|
- Event testing |
||||
|
- Custom mock behavior |
||||
|
- Integration testing |
||||
|
- Error handling |
||||
|
- Performance testing |
||||
|
|
||||
|
## Troubleshooting |
||||
|
|
||||
|
### Common Issues |
||||
|
|
||||
|
1. **JSDOM Environment Issues** |
||||
|
- Ensure `vitest.config.ts` has `environment: 'jsdom'` |
||||
|
- Check `src/test/setup.ts` for proper global mocks |
||||
|
|
||||
|
2. **Vue-facing-decorator Issues** |
||||
|
- Ensure TypeScript configuration supports decorators |
||||
|
- Verify import paths are correct |
||||
|
|
||||
|
3. **Test Utils Issues** |
||||
|
- Check component mounting syntax |
||||
|
- Verify prop passing |
||||
|
- Ensure proper async/await usage |
||||
|
|
||||
|
### Debug Tips |
||||
|
|
||||
|
```bash |
||||
|
# Run tests with verbose output |
||||
|
npm run test:unit -- --reporter=verbose |
||||
|
|
||||
|
# Run specific test file |
||||
|
npm run test:unit src/test/RegistrationNotice.test.ts |
||||
|
|
||||
|
# Debug with console output |
||||
|
npm run test:unit -- --reporter=verbose --no-coverage |
||||
|
``` |
||||
|
|
||||
|
## Contributing |
||||
|
|
||||
|
When adding new mocks or tests: |
||||
|
|
||||
|
1. Follow the existing patterns in `RegistrationNotice.mock.ts` |
||||
|
2. Add comprehensive documentation |
||||
|
3. Include usage examples |
||||
|
4. Update this README with new information |
||||
|
5. Add security audit checklist items |
||||
|
|
||||
|
## Author |
||||
|
|
||||
|
Matthew Raymer |
@ -0,0 +1,219 @@ |
|||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest' |
||||
|
import { mount } from '@vue/test-utils' |
||||
|
import RegistrationNotice from '@/components/RegistrationNotice.vue' |
||||
|
|
||||
|
/** |
||||
|
* RegistrationNotice Component Tests |
||||
|
* |
||||
|
* Comprehensive test suite for the RegistrationNotice component. |
||||
|
* Tests component rendering, props, events, and user interactions. |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
*/ |
||||
|
describe('RegistrationNotice', () => { |
||||
|
let wrapper: any |
||||
|
|
||||
|
/** |
||||
|
* Test setup - creates a fresh component instance before each test |
||||
|
*/ |
||||
|
beforeEach(() => { |
||||
|
wrapper = null |
||||
|
}) |
||||
|
|
||||
|
/** |
||||
|
* Helper function to mount component with props |
||||
|
* @param props - Component props |
||||
|
* @returns Vue test wrapper |
||||
|
*/ |
||||
|
const mountComponent = (props = {}) => { |
||||
|
return mount(RegistrationNotice, { |
||||
|
props: { |
||||
|
isRegistered: false, |
||||
|
show: true, |
||||
|
...props |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
describe('Component Rendering', () => { |
||||
|
it('should render when not registered and show is true', () => { |
||||
|
wrapper = mountComponent() |
||||
|
|
||||
|
expect(wrapper.exists()).toBe(true) |
||||
|
expect(wrapper.find('#noticeBeforeAnnounce').exists()).toBe(true) |
||||
|
expect(wrapper.text()).toContain('Before you can publicly announce') |
||||
|
expect(wrapper.find('button').exists()).toBe(true) |
||||
|
expect(wrapper.find('button').text()).toBe('Share Your Info') |
||||
|
}) |
||||
|
|
||||
|
it('should not render when user is registered', () => { |
||||
|
wrapper = mountComponent({ isRegistered: true }) |
||||
|
|
||||
|
expect(wrapper.find('#noticeBeforeAnnounce').exists()).toBe(false) |
||||
|
}) |
||||
|
|
||||
|
it('should not render when show is false', () => { |
||||
|
wrapper = mountComponent({ show: false }) |
||||
|
|
||||
|
expect(wrapper.find('#noticeBeforeAnnounce').exists()).toBe(false) |
||||
|
}) |
||||
|
|
||||
|
it('should not render when both registered and show is false', () => { |
||||
|
wrapper = mountComponent({ isRegistered: true, show: false }) |
||||
|
|
||||
|
expect(wrapper.find('#noticeBeforeAnnounce').exists()).toBe(false) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Component Styling', () => { |
||||
|
it('should have correct CSS classes', () => { |
||||
|
wrapper = mountComponent() |
||||
|
const notice = wrapper.find('#noticeBeforeAnnounce') |
||||
|
|
||||
|
expect(notice.classes()).toContain('bg-amber-200') |
||||
|
expect(notice.classes()).toContain('text-amber-900') |
||||
|
expect(notice.classes()).toContain('border-amber-500') |
||||
|
expect(notice.classes()).toContain('border-dashed') |
||||
|
expect(notice.classes()).toContain('text-center') |
||||
|
expect(notice.classes()).toContain('rounded-md') |
||||
|
expect(notice.classes()).toContain('overflow-hidden') |
||||
|
expect(notice.classes()).toContain('px-4') |
||||
|
expect(notice.classes()).toContain('py-3') |
||||
|
expect(notice.classes()).toContain('mt-4') |
||||
|
}) |
||||
|
|
||||
|
it('should have correct accessibility attributes', () => { |
||||
|
wrapper = mountComponent() |
||||
|
const notice = wrapper.find('#noticeBeforeAnnounce') |
||||
|
|
||||
|
expect(notice.attributes('role')).toBe('alert') |
||||
|
expect(notice.attributes('aria-live')).toBe('polite') |
||||
|
}) |
||||
|
|
||||
|
it('should have correct button styling', () => { |
||||
|
wrapper = mountComponent() |
||||
|
const button = wrapper.find('button') |
||||
|
|
||||
|
expect(button.classes()).toContain('inline-block') |
||||
|
expect(button.classes()).toContain('text-md') |
||||
|
expect(button.classes()).toContain('bg-gradient-to-b') |
||||
|
expect(button.classes()).toContain('from-blue-400') |
||||
|
expect(button.classes()).toContain('to-blue-700') |
||||
|
expect(button.classes()).toContain('text-white') |
||||
|
expect(button.classes()).toContain('px-4') |
||||
|
expect(button.classes()).toContain('py-2') |
||||
|
expect(button.classes()).toContain('rounded-md') |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('User Interactions', () => { |
||||
|
it('should emit share-info event when button is clicked', async () => { |
||||
|
wrapper = mountComponent() |
||||
|
const button = wrapper.find('button') |
||||
|
|
||||
|
await button.trigger('click') |
||||
|
|
||||
|
expect(wrapper.emitted('share-info')).toBeTruthy() |
||||
|
expect(wrapper.emitted('share-info')).toHaveLength(1) |
||||
|
}) |
||||
|
|
||||
|
it('should emit share-info event multiple times when button is clicked multiple times', async () => { |
||||
|
wrapper = mountComponent() |
||||
|
const button = wrapper.find('button') |
||||
|
|
||||
|
await button.trigger('click') |
||||
|
await button.trigger('click') |
||||
|
await button.trigger('click') |
||||
|
|
||||
|
expect(wrapper.emitted('share-info')).toBeTruthy() |
||||
|
expect(wrapper.emitted('share-info')).toHaveLength(3) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Component Props', () => { |
||||
|
it('should accept isRegistered prop', () => { |
||||
|
wrapper = mountComponent({ isRegistered: false }) |
||||
|
expect(wrapper.vm.isRegistered).toBe(false) |
||||
|
|
||||
|
wrapper = mountComponent({ isRegistered: true }) |
||||
|
expect(wrapper.vm.isRegistered).toBe(true) |
||||
|
}) |
||||
|
|
||||
|
it('should accept show prop', () => { |
||||
|
wrapper = mountComponent({ show: true }) |
||||
|
expect(wrapper.vm.show).toBe(true) |
||||
|
|
||||
|
wrapper = mountComponent({ show: false }) |
||||
|
expect(wrapper.vm.show).toBe(false) |
||||
|
}) |
||||
|
|
||||
|
it('should handle both props together', () => { |
||||
|
wrapper = mountComponent({ isRegistered: false, show: true }) |
||||
|
expect(wrapper.vm.isRegistered).toBe(false) |
||||
|
expect(wrapper.vm.show).toBe(true) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Component Methods', () => { |
||||
|
it('should have shareInfo method', () => { |
||||
|
wrapper = mountComponent() |
||||
|
expect(typeof wrapper.vm.shareInfo).toBe('function') |
||||
|
}) |
||||
|
|
||||
|
it('should emit event when shareInfo is called', () => { |
||||
|
wrapper = mountComponent() |
||||
|
wrapper.vm.shareInfo() |
||||
|
|
||||
|
expect(wrapper.emitted('share-info')).toBeTruthy() |
||||
|
expect(wrapper.emitted('share-info')).toHaveLength(1) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Edge Cases', () => { |
||||
|
it('should handle rapid button clicks', async () => { |
||||
|
wrapper = mountComponent() |
||||
|
const button = wrapper.find('button') |
||||
|
|
||||
|
// Simulate rapid clicks
|
||||
|
await Promise.all([ |
||||
|
button.trigger('click'), |
||||
|
button.trigger('click'), |
||||
|
button.trigger('click') |
||||
|
]) |
||||
|
|
||||
|
expect(wrapper.emitted('share-info')).toBeTruthy() |
||||
|
expect(wrapper.emitted('share-info')).toHaveLength(3) |
||||
|
}) |
||||
|
|
||||
|
it('should maintain component state after prop changes', async () => { |
||||
|
wrapper = mountComponent({ isRegistered: false, show: true }) |
||||
|
expect(wrapper.find('#noticeBeforeAnnounce').exists()).toBe(true) |
||||
|
|
||||
|
await wrapper.setProps({ isRegistered: true }) |
||||
|
expect(wrapper.find('#noticeBeforeAnnounce').exists()).toBe(false) |
||||
|
|
||||
|
await wrapper.setProps({ isRegistered: false }) |
||||
|
expect(wrapper.find('#noticeBeforeAnnounce').exists()).toBe(true) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe('Accessibility', () => { |
||||
|
it('should have proper semantic structure', () => { |
||||
|
wrapper = mountComponent() |
||||
|
const notice = wrapper.find('#noticeBeforeAnnounce') |
||||
|
const button = wrapper.find('button') |
||||
|
|
||||
|
expect(notice.exists()).toBe(true) |
||||
|
expect(button.exists()).toBe(true) |
||||
|
expect(button.text()).toBe('Share Your Info') |
||||
|
}) |
||||
|
|
||||
|
it('should have proper ARIA attributes', () => { |
||||
|
wrapper = mountComponent() |
||||
|
const notice = wrapper.find('#noticeBeforeAnnounce') |
||||
|
|
||||
|
expect(notice.attributes('role')).toBe('alert') |
||||
|
expect(notice.attributes('aria-live')).toBe('polite') |
||||
|
}) |
||||
|
}) |
||||
|
}) |
@ -0,0 +1,54 @@ |
|||||
|
import { Component, Vue, Prop, Emit } from "vue-facing-decorator"; |
||||
|
|
||||
|
/** |
||||
|
* RegistrationNotice Mock Component |
||||
|
* |
||||
|
* A mock implementation of the RegistrationNotice component for testing purposes. |
||||
|
* Provides the same interface as the original component but with simplified behavior |
||||
|
* for unit testing scenarios. |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
*/ |
||||
|
@Component({ name: "RegistrationNotice" }) |
||||
|
export default class RegistrationNoticeMock extends Vue { |
||||
|
@Prop({ required: true }) isRegistered!: boolean; |
||||
|
@Prop({ required: true }) show!: boolean; |
||||
|
|
||||
|
@Emit("share-info") |
||||
|
shareInfo() { |
||||
|
// Mock implementation - just emits the event
|
||||
|
return undefined; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Mock method to simulate button click for testing |
||||
|
* @returns void |
||||
|
*/ |
||||
|
mockShareInfoClick(): void { |
||||
|
this.shareInfo(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Mock method to check if component should be visible |
||||
|
* @returns boolean - true if component should be shown |
||||
|
*/ |
||||
|
get shouldShow(): boolean { |
||||
|
return !this.isRegistered && this.show; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Mock method to get button text |
||||
|
* @returns string - the button text |
||||
|
*/ |
||||
|
get buttonText(): string { |
||||
|
return "Share Your Info"; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Mock method to get notice text |
||||
|
* @returns string - the notice message |
||||
|
*/ |
||||
|
get noticeText(): string { |
||||
|
return "Before you can publicly announce a new project or time commitment, a friend needs to register you."; |
||||
|
} |
||||
|
} |
@ -0,0 +1,75 @@ |
|||||
|
import { config } from '@vue/test-utils' |
||||
|
import { vi } from 'vitest' |
||||
|
|
||||
|
/** |
||||
|
* Test Setup Configuration for TimeSafari |
||||
|
* |
||||
|
* Configures the testing environment for Vue components with proper mocking |
||||
|
* and global test utilities. Sets up JSDOM environment for component testing. |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
*/ |
||||
|
|
||||
|
// Mock global objects that might not be available in JSDOM
|
||||
|
global.ResizeObserver = vi.fn().mockImplementation(() => ({ |
||||
|
observe: vi.fn(), |
||||
|
unobserve: vi.fn(), |
||||
|
disconnect: vi.fn(), |
||||
|
})) |
||||
|
|
||||
|
// Mock IntersectionObserver
|
||||
|
global.IntersectionObserver = vi.fn().mockImplementation(() => ({ |
||||
|
observe: vi.fn(), |
||||
|
unobserve: vi.fn(), |
||||
|
disconnect: vi.fn(), |
||||
|
})) |
||||
|
|
||||
|
// Mock matchMedia
|
||||
|
Object.defineProperty(window, 'matchMedia', { |
||||
|
writable: true, |
||||
|
value: vi.fn().mockImplementation(query => ({ |
||||
|
matches: false, |
||||
|
media: query, |
||||
|
onchange: null, |
||||
|
addListener: vi.fn(), // deprecated
|
||||
|
removeListener: vi.fn(), // deprecated
|
||||
|
addEventListener: vi.fn(), |
||||
|
removeEventListener: vi.fn(), |
||||
|
dispatchEvent: vi.fn(), |
||||
|
})), |
||||
|
}) |
||||
|
|
||||
|
// Mock localStorage
|
||||
|
const localStorageMock = { |
||||
|
getItem: vi.fn(), |
||||
|
setItem: vi.fn(), |
||||
|
removeItem: vi.fn(), |
||||
|
clear: vi.fn(), |
||||
|
} |
||||
|
global.localStorage = localStorageMock |
||||
|
|
||||
|
// Mock sessionStorage
|
||||
|
const sessionStorageMock = { |
||||
|
getItem: vi.fn(), |
||||
|
setItem: vi.fn(), |
||||
|
removeItem: vi.fn(), |
||||
|
clear: vi.fn(), |
||||
|
} |
||||
|
global.sessionStorage = sessionStorageMock |
||||
|
|
||||
|
// Configure Vue Test Utils
|
||||
|
config.global.stubs = { |
||||
|
// Add any global component stubs here
|
||||
|
} |
||||
|
|
||||
|
// Mock console methods to reduce noise in tests
|
||||
|
const originalConsole = { ...console } |
||||
|
beforeEach(() => { |
||||
|
console.warn = vi.fn() |
||||
|
console.error = vi.fn() |
||||
|
}) |
||||
|
|
||||
|
afterEach(() => { |
||||
|
console.warn = originalConsole.warn |
||||
|
console.error = originalConsole.error |
||||
|
}) |
@ -0,0 +1,50 @@ |
|||||
|
import { defineConfig } from 'vitest/config' |
||||
|
import vue from '@vitejs/plugin-vue' |
||||
|
import { resolve } from 'path' |
||||
|
|
||||
|
/** |
||||
|
* Vitest Configuration for TimeSafari |
||||
|
* |
||||
|
* Configures testing environment for Vue components with JSDOM support. |
||||
|
* Enables testing of Vue-facing-decorator components with proper TypeScript support. |
||||
|
* Excludes Playwright tests which use a different testing framework. |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
*/ |
||||
|
export default defineConfig({ |
||||
|
plugins: [vue()], |
||||
|
test: { |
||||
|
environment: 'jsdom', |
||||
|
globals: true, |
||||
|
setupFiles: ['./src/test/setup.ts'], |
||||
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], |
||||
|
exclude: [ |
||||
|
'node_modules', |
||||
|
'dist', |
||||
|
'.idea', |
||||
|
'.git', |
||||
|
'.cache', |
||||
|
'test-playwright/**/*', |
||||
|
'test-scripts/**/*', |
||||
|
'test-results/**/*', |
||||
|
'test-playwright-results/**/*' |
||||
|
], |
||||
|
coverage: { |
||||
|
provider: 'v8', |
||||
|
reporter: ['text', 'json', 'html'], |
||||
|
exclude: [ |
||||
|
'node_modules/', |
||||
|
'src/test/', |
||||
|
'**/*.d.ts', |
||||
|
'**/*.config.*', |
||||
|
'**/coverage/**', |
||||
|
'test-playwright/**/*' |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
resolve: { |
||||
|
alias: { |
||||
|
'@': resolve(__dirname, './src') |
||||
|
} |
||||
|
} |
||||
|
}) |
Loading…
Reference in new issue