test: Add Vitest and tests for Toast system
- Configure Vitest with jsdom environment - Add test setup with @testing-library/jest-dom - Add 6 tests for toastStore (addToast, removeToast, clearToasts) - Add 8 tests for useToast hook (success, error, warning, info, dismiss) All 14 tests passing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
380b96e159
commit
434990972e
@ -9,7 +9,10 @@
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"lint:fix": "eslint . --ext ts,tsx --fix",
|
||||
"type-check": "tsc --noEmit"
|
||||
"type-check": "tsc --noEmit",
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run",
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.3.3",
|
||||
@ -27,6 +30,9 @@
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
@ -36,10 +42,12 @@
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"jsdom": "^28.0.0",
|
||||
"postcss": "^8.4.32",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.8"
|
||||
"vite": "^5.0.8",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
|
||||
134
web/src/hooks/useToast.test.ts
Normal file
134
web/src/hooks/useToast.test.ts
Normal file
@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Tests for useToast hook
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { useToast } from './useToast';
|
||||
import { useToastStore } from '../stores/toastStore';
|
||||
|
||||
describe('useToast', () => {
|
||||
beforeEach(() => {
|
||||
// Reset store before each test
|
||||
useToastStore.setState({ toasts: [] });
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
it('should add a success toast', () => {
|
||||
const { result } = renderHook(() => useToast());
|
||||
|
||||
act(() => {
|
||||
result.current.success('Operation successful');
|
||||
});
|
||||
|
||||
const state = useToastStore.getState();
|
||||
expect(state.toasts).toHaveLength(1);
|
||||
expect(state.toasts[0].type).toBe('success');
|
||||
expect(state.toasts[0].message).toBe('Operation successful');
|
||||
});
|
||||
|
||||
it('should support custom title and duration', () => {
|
||||
const { result } = renderHook(() => useToast());
|
||||
|
||||
act(() => {
|
||||
result.current.success('Saved', { title: 'Success!', duration: 3000 });
|
||||
});
|
||||
|
||||
const state = useToastStore.getState();
|
||||
expect(state.toasts[0].title).toBe('Success!');
|
||||
expect(state.toasts[0].duration).toBe(3000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error', () => {
|
||||
it('should add an error toast', () => {
|
||||
const { result } = renderHook(() => useToast());
|
||||
|
||||
act(() => {
|
||||
result.current.error('Something went wrong');
|
||||
});
|
||||
|
||||
const state = useToastStore.getState();
|
||||
expect(state.toasts[0].type).toBe('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('warning', () => {
|
||||
it('should add a warning toast', () => {
|
||||
const { result } = renderHook(() => useToast());
|
||||
|
||||
act(() => {
|
||||
result.current.warning('Please check your input');
|
||||
});
|
||||
|
||||
const state = useToastStore.getState();
|
||||
expect(state.toasts[0].type).toBe('warning');
|
||||
});
|
||||
});
|
||||
|
||||
describe('info', () => {
|
||||
it('should add an info toast', () => {
|
||||
const { result } = renderHook(() => useToast());
|
||||
|
||||
act(() => {
|
||||
result.current.info('FYI: New feature available');
|
||||
});
|
||||
|
||||
const state = useToastStore.getState();
|
||||
expect(state.toasts[0].type).toBe('info');
|
||||
});
|
||||
});
|
||||
|
||||
describe('dismiss', () => {
|
||||
it('should dismiss a specific toast', () => {
|
||||
const { result } = renderHook(() => useToast());
|
||||
let toastId: string;
|
||||
|
||||
act(() => {
|
||||
toastId = result.current.success('Test');
|
||||
});
|
||||
|
||||
expect(useToastStore.getState().toasts).toHaveLength(1);
|
||||
|
||||
act(() => {
|
||||
result.current.dismiss(toastId);
|
||||
});
|
||||
|
||||
expect(useToastStore.getState().toasts).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dismissAll', () => {
|
||||
it('should dismiss all toasts', () => {
|
||||
const { result } = renderHook(() => useToast());
|
||||
|
||||
act(() => {
|
||||
result.current.success('First');
|
||||
result.current.error('Second');
|
||||
result.current.warning('Third');
|
||||
});
|
||||
|
||||
expect(useToastStore.getState().toasts).toHaveLength(3);
|
||||
|
||||
act(() => {
|
||||
result.current.dismissAll();
|
||||
});
|
||||
|
||||
expect(useToastStore.getState().toasts).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toast (generic)', () => {
|
||||
it('should add toast with specified type', () => {
|
||||
const { result } = renderHook(() => useToast());
|
||||
|
||||
act(() => {
|
||||
result.current.toast('info', 'Generic toast');
|
||||
});
|
||||
|
||||
const state = useToastStore.getState();
|
||||
expect(state.toasts[0].type).toBe('info');
|
||||
expect(state.toasts[0].message).toBe('Generic toast');
|
||||
});
|
||||
});
|
||||
});
|
||||
102
web/src/stores/toastStore.test.ts
Normal file
102
web/src/stores/toastStore.test.ts
Normal file
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Tests for toastStore
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useToastStore } from './toastStore';
|
||||
|
||||
describe('toastStore', () => {
|
||||
beforeEach(() => {
|
||||
// Reset store before each test
|
||||
useToastStore.setState({ toasts: [] });
|
||||
});
|
||||
|
||||
describe('addToast', () => {
|
||||
it('should add a toast with generated id', () => {
|
||||
const { addToast } = useToastStore.getState();
|
||||
|
||||
const id = addToast({
|
||||
type: 'success',
|
||||
message: 'Test message',
|
||||
});
|
||||
|
||||
const state = useToastStore.getState();
|
||||
expect(state.toasts).toHaveLength(1);
|
||||
expect(state.toasts[0]).toMatchObject({
|
||||
id,
|
||||
type: 'success',
|
||||
message: 'Test message',
|
||||
});
|
||||
});
|
||||
|
||||
it('should add toast with optional title and duration', () => {
|
||||
const { addToast } = useToastStore.getState();
|
||||
|
||||
addToast({
|
||||
type: 'error',
|
||||
message: 'Error message',
|
||||
title: 'Error Title',
|
||||
duration: 3000,
|
||||
});
|
||||
|
||||
const state = useToastStore.getState();
|
||||
expect(state.toasts[0]).toMatchObject({
|
||||
type: 'error',
|
||||
message: 'Error message',
|
||||
title: 'Error Title',
|
||||
duration: 3000,
|
||||
});
|
||||
});
|
||||
|
||||
it('should add multiple toasts', () => {
|
||||
const { addToast } = useToastStore.getState();
|
||||
|
||||
addToast({ type: 'success', message: 'First' });
|
||||
addToast({ type: 'error', message: 'Second' });
|
||||
addToast({ type: 'warning', message: 'Third' });
|
||||
|
||||
const state = useToastStore.getState();
|
||||
expect(state.toasts).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeToast', () => {
|
||||
it('should remove a toast by id', () => {
|
||||
const { addToast, removeToast } = useToastStore.getState();
|
||||
|
||||
const id = addToast({ type: 'info', message: 'Test' });
|
||||
expect(useToastStore.getState().toasts).toHaveLength(1);
|
||||
|
||||
removeToast(id);
|
||||
expect(useToastStore.getState().toasts).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should only remove specified toast', () => {
|
||||
const { addToast, removeToast } = useToastStore.getState();
|
||||
|
||||
const id1 = addToast({ type: 'success', message: 'First' });
|
||||
const id2 = addToast({ type: 'error', message: 'Second' });
|
||||
|
||||
removeToast(id1);
|
||||
|
||||
const state = useToastStore.getState();
|
||||
expect(state.toasts).toHaveLength(1);
|
||||
expect(state.toasts[0].id).toBe(id2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearToasts', () => {
|
||||
it('should remove all toasts', () => {
|
||||
const { addToast, clearToasts } = useToastStore.getState();
|
||||
|
||||
addToast({ type: 'success', message: 'First' });
|
||||
addToast({ type: 'error', message: 'Second' });
|
||||
addToast({ type: 'warning', message: 'Third' });
|
||||
|
||||
expect(useToastStore.getState().toasts).toHaveLength(3);
|
||||
|
||||
clearToasts();
|
||||
expect(useToastStore.getState().toasts).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
1
web/src/test/setup.ts
Normal file
1
web/src/test/setup.ts
Normal file
@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom';
|
||||
17
web/vitest.config.ts
Normal file
17
web/vitest.config.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./src/test/setup.ts'],
|
||||
include: ['src/**/*.{test,spec}.{ts,tsx}'],
|
||||
coverage: {
|
||||
reporter: ['text', 'json', 'html'],
|
||||
exclude: ['node_modules/', 'src/test/'],
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user