fix(test): replace vi.mocked with type casting for Bun compatibility
Fixes technical debt from Tasks 10, 18-20.
Problem:
- 38/46 frontend tests failing due to vi.mocked() not supported in Bun
- happy-dom environment causing document errors in some tests
Solution:
1. Replaced all vi.mocked(fn) calls with (fn as any) type casting
- useActiveClub.test.ts: 3 occurrences (localStorage mocks)
- auth-guard.test.tsx: 13 occurrences (useSession, useTenant, useRouter)
- club-switcher.test.tsx: 3 occurrences (useRouter)
- task-detail.test.tsx: 4 occurrences (useRouter, useTasks)
- task-list.test.tsx: 1 occurrence (useTasks)
2. Updated vitest.config.ts:
- Changed environment from happy-dom to jsdom (better DOM support)
- Added exclude pattern to prevent e2e tests interference
Test Results:
- Before: 8/46 passing (38 failures)
- After: 45/45 passing (0 failures) ✅
No source code changes - only test infrastructure fixes.
This commit is contained in:
@@ -22,28 +22,28 @@ describe('AuthGuard', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
vi.mocked(useRouter).mockReturnValue({ push: mockPush } as any);
|
(useRouter as any).mockReturnValue({ push: mockPush } as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders loading state when session is loading', () => {
|
it('renders loading state when session is loading', () => {
|
||||||
vi.mocked(useSession).mockReturnValue({ data: null, status: 'loading' } as any);
|
(useSession as any).mockReturnValue({ data: null, status: 'loading' } as any);
|
||||||
vi.mocked(useTenant).mockReturnValue({ activeClubId: null, clubs: [], setActiveClub: vi.fn(), userRole: null });
|
(useTenant as any).mockReturnValue({ activeClubId: null, clubs: [], setActiveClub: vi.fn(), userRole: null });
|
||||||
|
|
||||||
render(<AuthGuard><div>Protected</div></AuthGuard>);
|
render(<AuthGuard><div>Protected</div></AuthGuard>);
|
||||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('redirects to /login when unauthenticated', () => {
|
it('redirects to /login when unauthenticated', () => {
|
||||||
vi.mocked(useSession).mockReturnValue({ data: null, status: 'unauthenticated' } as any);
|
(useSession as any).mockReturnValue({ data: null, status: 'unauthenticated' } as any);
|
||||||
vi.mocked(useTenant).mockReturnValue({ activeClubId: null, clubs: [], setActiveClub: vi.fn(), userRole: null });
|
(useTenant as any).mockReturnValue({ activeClubId: null, clubs: [], setActiveClub: vi.fn(), userRole: null });
|
||||||
|
|
||||||
render(<AuthGuard><div>Protected</div></AuthGuard>);
|
render(<AuthGuard><div>Protected</div></AuthGuard>);
|
||||||
expect(mockPush).toHaveBeenCalledWith('/login');
|
expect(mockPush).toHaveBeenCalledWith('/login');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows Contact admin when 0 clubs', () => {
|
it('shows Contact admin when 0 clubs', () => {
|
||||||
vi.mocked(useSession).mockReturnValue({ data: { user: {} }, status: 'authenticated' } as any);
|
(useSession as any).mockReturnValue({ data: { user: {} }, status: 'authenticated' } as any);
|
||||||
vi.mocked(useTenant).mockReturnValue({ activeClubId: null, clubs: [], setActiveClub: vi.fn(), userRole: null });
|
(useTenant as any).mockReturnValue({ activeClubId: null, clubs: [], setActiveClub: vi.fn(), userRole: null });
|
||||||
|
|
||||||
render(<AuthGuard><div>Protected</div></AuthGuard>);
|
render(<AuthGuard><div>Protected</div></AuthGuard>);
|
||||||
expect(screen.getByText('Contact admin to get access to a club')).toBeInTheDocument();
|
expect(screen.getByText('Contact admin to get access to a club')).toBeInTheDocument();
|
||||||
@@ -51,8 +51,8 @@ describe('AuthGuard', () => {
|
|||||||
|
|
||||||
it('auto-selects when 1 club and no active club', () => {
|
it('auto-selects when 1 club and no active club', () => {
|
||||||
const mockSetActiveClub = vi.fn();
|
const mockSetActiveClub = vi.fn();
|
||||||
vi.mocked(useSession).mockReturnValue({ data: { user: {} }, status: 'authenticated' } as any);
|
(useSession as any).mockReturnValue({ data: { user: {} }, status: 'authenticated' } as any);
|
||||||
vi.mocked(useTenant).mockReturnValue({
|
(useTenant as any).mockReturnValue({
|
||||||
activeClubId: null,
|
activeClubId: null,
|
||||||
clubs: [{ id: 'club-1', name: 'Club 1' }],
|
clubs: [{ id: 'club-1', name: 'Club 1' }],
|
||||||
setActiveClub: mockSetActiveClub,
|
setActiveClub: mockSetActiveClub,
|
||||||
@@ -64,8 +64,8 @@ describe('AuthGuard', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('redirects to /select-club when multiple clubs and no active club', () => {
|
it('redirects to /select-club when multiple clubs and no active club', () => {
|
||||||
vi.mocked(useSession).mockReturnValue({ data: { user: {} }, status: 'authenticated' } as any);
|
(useSession as any).mockReturnValue({ data: { user: {} }, status: 'authenticated' } as any);
|
||||||
vi.mocked(useTenant).mockReturnValue({
|
(useTenant as any).mockReturnValue({
|
||||||
activeClubId: null,
|
activeClubId: null,
|
||||||
clubs: [{ id: 'club-1', name: 'Club 1' }, { id: 'club-2', name: 'Club 2' }],
|
clubs: [{ id: 'club-1', name: 'Club 1' }, { id: 'club-2', name: 'Club 2' }],
|
||||||
setActiveClub: vi.fn(),
|
setActiveClub: vi.fn(),
|
||||||
@@ -77,8 +77,8 @@ describe('AuthGuard', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders children when authenticated and active club is set', () => {
|
it('renders children when authenticated and active club is set', () => {
|
||||||
vi.mocked(useSession).mockReturnValue({ data: { user: {} }, status: 'authenticated' } as any);
|
(useSession as any).mockReturnValue({ data: { user: {} }, status: 'authenticated' } as any);
|
||||||
vi.mocked(useTenant).mockReturnValue({
|
(useTenant as any).mockReturnValue({
|
||||||
activeClubId: 'club-1',
|
activeClubId: 'club-1',
|
||||||
clubs: [{ id: 'club-1', name: 'Club 1' }],
|
clubs: [{ id: 'club-1', name: 'Club 1' }],
|
||||||
setActiveClub: vi.fn(),
|
setActiveClub: vi.fn(),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('ClubSwitcher', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders loading state when clubs is empty', () => {
|
it('renders loading state when clubs is empty', () => {
|
||||||
vi.mocked(useTenant).mockReturnValue({
|
(useTenant as any).mockReturnValue({
|
||||||
activeClubId: null,
|
activeClubId: null,
|
||||||
clubs: [],
|
clubs: [],
|
||||||
setActiveClub: vi.fn(),
|
setActiveClub: vi.fn(),
|
||||||
@@ -34,7 +34,7 @@ describe('ClubSwitcher', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders current club name and sport type badge', () => {
|
it('renders current club name and sport type badge', () => {
|
||||||
vi.mocked(useTenant).mockReturnValue({
|
(useTenant as any).mockReturnValue({
|
||||||
activeClubId: 'club-1',
|
activeClubId: 'club-1',
|
||||||
clubs: [
|
clubs: [
|
||||||
{ id: 'club-1', name: 'Tennis Club', sportType: 'Tennis' },
|
{ id: 'club-1', name: 'Tennis Club', sportType: 'Tennis' },
|
||||||
@@ -50,7 +50,7 @@ describe('ClubSwitcher', () => {
|
|||||||
|
|
||||||
it('calls setActiveClub when club is selected', () => {
|
it('calls setActiveClub when club is selected', () => {
|
||||||
const mockSetActiveClub = vi.fn();
|
const mockSetActiveClub = vi.fn();
|
||||||
vi.mocked(useTenant).mockReturnValue({
|
(useTenant as any).mockReturnValue({
|
||||||
activeClubId: 'club-1',
|
activeClubId: 'club-1',
|
||||||
clubs: [
|
clubs: [
|
||||||
{ id: 'club-1', name: 'Tennis Club', sportType: 'Tennis' },
|
{ id: 'club-1', name: 'Tennis Club', sportType: 'Tennis' },
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ describe('TaskDetailPage', () => {
|
|||||||
const mockMutate = vi.fn();
|
const mockMutate = vi.fn();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.mocked(useUpdateTask).mockReturnValue({
|
(useUpdateTask as any).mockReturnValue({
|
||||||
mutate: mockMutate,
|
mutate: mockMutate,
|
||||||
isPending: false,
|
isPending: false,
|
||||||
} as any);
|
} as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows valid transitions for Open status', async () => {
|
it('shows valid transitions for Open status', async () => {
|
||||||
vi.mocked(useTask).mockReturnValue({
|
(useTask as any).mockReturnValue({
|
||||||
data: { id: '1', title: 'Task 1', status: 'Open', description: 'Desc', createdAt: '2024-01-01', updatedAt: '2024-01-01' },
|
data: { id: '1', title: 'Task 1', status: 'Open', description: 'Desc', createdAt: '2024-01-01', updatedAt: '2024-01-01' },
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -44,7 +44,7 @@ describe('TaskDetailPage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('shows valid transitions for InProgress status', async () => {
|
it('shows valid transitions for InProgress status', async () => {
|
||||||
vi.mocked(useTask).mockReturnValue({
|
(useTask as any).mockReturnValue({
|
||||||
data: { id: '1', title: 'Task 1', status: 'InProgress', description: 'Desc', createdAt: '2024-01-01', updatedAt: '2024-01-01' },
|
data: { id: '1', title: 'Task 1', status: 'InProgress', description: 'Desc', createdAt: '2024-01-01', updatedAt: '2024-01-01' },
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -60,7 +60,7 @@ describe('TaskDetailPage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('shows valid transitions for Review status (including back transition)', async () => {
|
it('shows valid transitions for Review status (including back transition)', async () => {
|
||||||
vi.mocked(useTask).mockReturnValue({
|
(useTask as any).mockReturnValue({
|
||||||
data: { id: '1', title: 'Task 1', status: 'Review', description: 'Desc', createdAt: '2024-01-01', updatedAt: '2024-01-01' },
|
data: { id: '1', title: 'Task 1', status: 'Review', description: 'Desc', createdAt: '2024-01-01', updatedAt: '2024-01-01' },
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ vi.mock('@/hooks/useTasks', () => ({
|
|||||||
|
|
||||||
describe('TaskListPage', () => {
|
describe('TaskListPage', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.mocked(useTasks).mockReturnValue({
|
(useTasks as any).mockReturnValue({
|
||||||
data: {
|
data: {
|
||||||
items: [
|
items: [
|
||||||
{ id: '1', title: 'Test Task 1', status: 'Open', assigneeId: null, createdAt: '2024-01-01' },
|
{ id: '1', title: 'Test Task 1', status: 'Open', assigneeId: null, createdAt: '2024-01-01' },
|
||||||
|
|||||||
@@ -33,15 +33,15 @@ describe('useActiveClub', () => {
|
|||||||
status: 'authenticated',
|
status: 'authenticated',
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mocked(localStorage.getItem).mockImplementation((key: string) => {
|
(localStorage.getItem as any).mockImplementation((key: string) => {
|
||||||
return localStorageData[key] || null;
|
return localStorageData[key] || null;
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mocked(localStorage.setItem).mockImplementation((key: string, value: string) => {
|
(localStorage.setItem as any).mockImplementation((key: string, value: string) => {
|
||||||
localStorageData[key] = value;
|
localStorageData[key] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mocked(localStorage.clear).mockImplementation(() => {
|
(localStorage.clear as any).mockImplementation(() => {
|
||||||
localStorageData = {};
|
localStorageData = {};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import path from 'path';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
test: {
|
test: {
|
||||||
environment: 'happy-dom',
|
environment: 'jsdom',
|
||||||
globals: true,
|
globals: true,
|
||||||
setupFiles: ['./src/test/setup.ts'],
|
setupFiles: ['./src/test/setup.ts'],
|
||||||
|
exclude: ['node_modules', 'dist', '.idea', '.git', '.cache', 'e2e'],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user