Feat/frontend tests (#62)
* test(frontend): add vitest test infrastructure - Add vitest, testing-library, jsdom dependencies - Configure vitest with jsdom environment - Add test setup with mocks for fetch, localStorage, matchMedia - Set 75% coverage threshold * Add frontend tests (#61) * Initial plan * Add frontend tests - utilities, hooks, and components (21% coverage) Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Add more component tests (Auth, AboutModal, ExportModal) and useRefill hook tests - 30% coverage Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Add useMedicationForm utility function tests - 30% coverage Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Add AppHeader tests and more schedule.ts tests - 32% coverage Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Changes before error encountered Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Fix page tests and add more tests - 326 tests passing, 34% coverage Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Add tests for ProfileModal, UserFilterModal, MedDetailModal - 361 tests, 36% coverage Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Add SharedSchedule tests - 366 tests, 39% coverage Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Expand page tests - 383 tests, 39% coverage Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Add MobileEditModal tests - 409 tests, 40% coverage Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Expand Dashboard and Schedule page tests - 427 tests, 40% coverage Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Fix code review issues - remove invalid remindEnabled property Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { AppHeader } from '../../components/AppHeader';
|
||||
import { AuthProvider } from '../../components/Auth';
|
||||
|
||||
// Mock useNavigate
|
||||
const mockNavigate = vi.fn();
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual = await vi.importActual('react-router-dom');
|
||||
return {
|
||||
...actual,
|
||||
useNavigate: () => mockNavigate,
|
||||
};
|
||||
});
|
||||
|
||||
describe('AppHeader', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockNavigate.mockClear();
|
||||
// Set up default auth mock - auth disabled
|
||||
(global.fetch as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
authEnabled: false,
|
||||
localAuthEnabled: true,
|
||||
hasUsers: false,
|
||||
needsSetup: false
|
||||
})
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
status: 401,
|
||||
ok: false
|
||||
});
|
||||
});
|
||||
|
||||
it('renders header with logo', async () => {
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockOnOpenAbout = vi.fn();
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/dashboard']}>
|
||||
<AuthProvider>
|
||||
<AppHeader onOpenProfile={mockOnOpenProfile} onOpenAbout={mockOnOpenAbout} />
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const logo = screen.getByAltText('MedAssist-ng');
|
||||
expect(logo).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders navigation tabs', async () => {
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockOnOpenAbout = vi.fn();
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/dashboard']}>
|
||||
<AuthProvider>
|
||||
<AppHeader onOpenProfile={mockOnOpenProfile} onOpenAbout={mockOnOpenAbout} />
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
// Use getAllBy since there are multiple elements with same text
|
||||
const dashboardElements = screen.getAllByText(/nav\.dashboard/i);
|
||||
expect(dashboardElements.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders theme toggle button', async () => {
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockOnOpenAbout = vi.fn();
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/dashboard']}>
|
||||
<AuthProvider>
|
||||
<AppHeader onOpenProfile={mockOnOpenProfile} onOpenAbout={mockOnOpenAbout} />
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const buttons = screen.getAllByRole('button');
|
||||
const themeBtn = buttons.find(btn => btn.textContent?.includes('🌙') || btn.textContent?.includes('☀️'));
|
||||
expect(themeBtn).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders settings button when auth is disabled', async () => {
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockOnOpenAbout = vi.fn();
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/dashboard']}>
|
||||
<AuthProvider>
|
||||
<AppHeader onOpenProfile={mockOnOpenProfile} onOpenAbout={mockOnOpenAbout} />
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const settingsBtn = screen.queryByTitle(/nav\.settings/i);
|
||||
expect(settingsBtn).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows page eyebrow and title', async () => {
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockOnOpenAbout = vi.fn();
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/dashboard']}>
|
||||
<AuthProvider>
|
||||
<AppHeader onOpenProfile={mockOnOpenProfile} onOpenAbout={mockOnOpenAbout} />
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/header\.eyebrow\.overview/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows medications page title on medications route', async () => {
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockOnOpenAbout = vi.fn();
|
||||
|
||||
// Reset mock for this test
|
||||
vi.clearAllMocks();
|
||||
(global.fetch as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
authEnabled: false,
|
||||
localAuthEnabled: true,
|
||||
hasUsers: false,
|
||||
needsSetup: false
|
||||
})
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
status: 401,
|
||||
ok: false
|
||||
});
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/medications']}>
|
||||
<AuthProvider>
|
||||
<AppHeader onOpenProfile={mockOnOpenProfile} onOpenAbout={mockOnOpenAbout} />
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/header\.eyebrow\.inventory/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows planner page title on planner route', async () => {
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockOnOpenAbout = vi.fn();
|
||||
|
||||
vi.clearAllMocks();
|
||||
(global.fetch as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
authEnabled: false,
|
||||
localAuthEnabled: true,
|
||||
hasUsers: false,
|
||||
needsSetup: false
|
||||
})
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
status: 401,
|
||||
ok: false
|
||||
});
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/planner']}>
|
||||
<AuthProvider>
|
||||
<AppHeader onOpenProfile={mockOnOpenProfile} onOpenAbout={mockOnOpenAbout} />
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/header\.eyebrow\.planner/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows settings page title on settings route', async () => {
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockOnOpenAbout = vi.fn();
|
||||
|
||||
vi.clearAllMocks();
|
||||
(global.fetch as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
authEnabled: false,
|
||||
localAuthEnabled: true,
|
||||
hasUsers: false,
|
||||
needsSetup: false
|
||||
})
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
status: 401,
|
||||
ok: false
|
||||
});
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/settings']}>
|
||||
<AuthProvider>
|
||||
<AppHeader onOpenProfile={mockOnOpenProfile} onOpenAbout={mockOnOpenAbout} />
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/header\.eyebrow\.settings/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('navigates when tab clicked', async () => {
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockOnOpenAbout = vi.fn();
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/dashboard']}>
|
||||
<AuthProvider>
|
||||
<AppHeader onOpenProfile={mockOnOpenProfile} onOpenAbout={mockOnOpenAbout} />
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const buttons = screen.getAllByRole('button');
|
||||
const medsBtn = buttons.find(btn => btn.textContent?.includes('nav.medications'));
|
||||
if (medsBtn) {
|
||||
fireEvent.click(medsBtn);
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/medications');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user