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