Adds Playwright E2E and Vitest test infrastructure
Some checks failed
Lint / Lint and Check (push) Failing after 48s

Integrates Playwright for end-to-end browser testing with automated web server setup, example smoke tests, and CI-compatible configuration. Introduces Vitest, Testing Library, and related utilities for fast component and unit testing.

Updates scripts, development dependencies, and lockfile to support both test suites. Establishes unified testing commands for local and CI workflows, laying groundwork for comprehensive automated UI and integration coverage.
This commit is contained in:
2025-11-24 22:43:46 +01:00
parent 65f1fcb7bb
commit f64fc274a7
10 changed files with 1309 additions and 7 deletions

120
app/dashboard/page.test.tsx Normal file
View File

@@ -0,0 +1,120 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import Dashboard from './page';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// Mock next/navigation
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: vi.fn(),
}),
}));
// Mock ResizeObserver
global.ResizeObserver = class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
};
// Mock fetch
global.fetch = vi.fn();
const createTestQueryClient = () => new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
describe('Dashboard', () => {
beforeEach(() => {
vi.clearAllMocks();
// Mock Auth Response
(global.fetch as any).mockImplementation((url: string) => {
if (url === '/api/auth') {
return Promise.resolve({
json: () => Promise.resolve({ authenticated: true, token: 'test-token' }),
});
}
if (url === '/api/habits') {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
habits: [
{
id: 1,
name: 'Test Habit',
type: 'neutral',
totalLogs: 5,
logsLastWeek: 2,
logsLastMonth: 5,
createdAt: new Date().toISOString(),
lastLoggedAt: new Date().toISOString(),
}
]
}),
});
}
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
});
});
it('renders habits correctly', async () => {
render(
<QueryClientProvider client={createTestQueryClient()}>
<Dashboard />
</QueryClientProvider>
);
await waitFor(() => {
expect(screen.getByText('Test Habit')).toBeInTheDocument();
});
});
it('opens edit dialog when edit button is clicked', async () => {
render(
<QueryClientProvider client={createTestQueryClient()}>
<Dashboard />
</QueryClientProvider>
);
await waitFor(() => {
expect(screen.getByText('Test Habit')).toBeInTheDocument();
});
const editBtn = screen.getByTestId('edit-habit-1');
fireEvent.click(editBtn);
await waitFor(() => {
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(screen.getByText('Edit Habit')).toBeInTheDocument();
expect(screen.getByDisplayValue('Test Habit')).toBeInTheDocument();
});
});
it('opens archive confirmation when archive button is clicked', async () => {
render(
<QueryClientProvider client={createTestQueryClient()}>
<Dashboard />
</QueryClientProvider>
);
await waitFor(() => {
expect(screen.getByText('Test Habit')).toBeInTheDocument();
});
// Open Edit Dialog
fireEvent.click(screen.getByTestId('edit-habit-1'));
await waitFor(() => screen.getByRole('dialog'));
// Click Archive
fireEvent.click(screen.getByText('Archive Habit'));
await waitFor(() => {
expect(screen.getByText('Confirm Delete')).toBeInTheDocument();
});
});
});

View File

@@ -451,6 +451,7 @@ export default function Dashboard() {
size="icon"
className="h-8 w-8 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-black/20"
onClick={(e) => openEditDialog(e, habit)}
data-testid={`edit-habit-${habit.id}`}
>
<MoreVertical className="h-4 w-4" />
</Button>