91 lines
5.9 KiB
TypeScript
91 lines
5.9 KiB
TypeScript
import React from 'react';
|
|
import '@testing-library/jest-dom';
|
|
import { act, fireEvent, render, screen, within } from '@testing-library/react';
|
|
import { MemoryRouter } from 'react-router-dom';
|
|
import { api } from './api';
|
|
import CalendarAgenda from './components/CalendarAgenda';
|
|
import { I18nProvider } from './i18n/I18nProvider';
|
|
import { translations } from './i18n/translations';
|
|
import { setAuthUserKey } from './auth';
|
|
|
|
jest.mock('./api', () => ({ api: { get: jest.fn() }, getApiErrorMessage: (_: unknown, fallback: string) => fallback }));
|
|
const get = api.get as jest.Mock;
|
|
const now = new Date();
|
|
const month = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
const row = { jobApplicationId: 42, jobTitle: 'Recorded teacher role', companyName: 'School', kind: 'deadline', date: `${month}-15`, recordedAt: `${month}-15T23:30:00` };
|
|
const mount = () => render(<I18nProvider><MemoryRouter><CalendarAgenda /></MemoryRouter></I18nProvider>);
|
|
beforeEach(() => { localStorage.clear(); document.documentElement.lang = 'en'; get.mockReset(); get.mockResolvedValue({ data: [row] }); });
|
|
afterEach(() => jest.restoreAllMocks());
|
|
|
|
test('agenda prioritises upcoming recorded dates while retaining earlier dates in the month', async () => {
|
|
jest.spyOn(Date, 'now').mockReturnValue(new Date(now.getFullYear(), now.getMonth(), 16, 12).getTime());
|
|
get.mockResolvedValue({ data: [row, { ...row, jobApplicationId: 43, jobTitle: 'Upcoming nursing role', date: `${month}-20`, recordedAt: `${month}-20T00:00:00` }] });
|
|
mount(); await screen.findByRole('link', { name: /Upcoming nursing role/ });
|
|
expect(screen.getAllByRole('link').map(link => link.getAttribute('href'))).toEqual(['/jobs/43', '/jobs/42']);
|
|
});
|
|
|
|
test('agenda defaults to recorded dates and links exact application without a fabricated appointment time', async () => {
|
|
mount();
|
|
const link = await screen.findByRole('link', { name: /Recorded teacher role/ });
|
|
expect(link).toHaveAttribute('href', '/jobs/42');
|
|
expect(screen.getByText(translations.en.calendarDateScope)).toBeVisible();
|
|
expect(document.querySelector('time')).toHaveAttribute('datetime', row.recordedAt);
|
|
expect(screen.queryByText('23:30')).not.toBeInTheDocument();
|
|
expect(get).toHaveBeenCalledWith('/calendar/agenda', { params: { from: `${month}-01`, to: `${month}-${new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate()}` } });
|
|
});
|
|
|
|
test('month toggle persists only view preference and selecting a day keeps linked events available', async () => {
|
|
const first = mount(); await screen.findByRole('link', { name: /Recorded teacher role/ });
|
|
fireEvent.click(screen.getByRole('button', { name: translations.en.calendarMonth }));
|
|
const table = screen.getByRole('table', { name: translations.en.calendarMonth });
|
|
fireEvent.click(within(table).getByRole('button', { name: /15.*1/ }));
|
|
expect(screen.getByRole('link', { name: /Recorded teacher role/ })).toBeVisible();
|
|
expect(get).toHaveBeenCalledTimes(1);
|
|
first.unmount(); mount();
|
|
expect(await screen.findByRole('table', { name: translations.en.calendarMonth })).toBeVisible();
|
|
});
|
|
|
|
test('locale changes translate controls without repeating the read or translating source text', async () => {
|
|
mount(); await screen.findByRole('link', { name: /Recorded teacher role/ });
|
|
act(() => window.dispatchEvent(new StorageEvent('storage', { key: 'uiLanguage', newValue: 'nb' })));
|
|
expect(screen.getByRole('button', { name: translations.nb.calendarMonth })).toBeVisible();
|
|
expect(screen.getByRole('combobox', { name: translations.nb.calendarSelectedMonth })).toHaveTextContent(new Intl.DateTimeFormat('nb-NO', { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(new Date(`${month}-01T12:00:00Z`)));
|
|
expect(screen.getByRole('link', { name: /Recorded teacher role/ })).toBeVisible();
|
|
expect(get).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('failed read offers bounded retry and never claims an empty successful calendar', async () => {
|
|
get.mockRejectedValueOnce(new Error('offline'));
|
|
mount(); await screen.findByRole('alert');
|
|
expect(screen.queryByText(translations.en.calendarAgendaEmpty)).not.toBeInTheDocument();
|
|
fireEvent.click(screen.getByRole('button', { name: translations.en.retry }));
|
|
expect(await screen.findByRole('link', { name: /Recorded teacher role/ })).toBeVisible();
|
|
expect(get).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
test('invalid and unknown-kind fixture data is not turned into guessed events', async () => {
|
|
get.mockResolvedValue({ data: [{ ...row, date: `${month}-99` }, { ...row, kind: 'interview' }] });
|
|
mount(); expect(await screen.findByRole('alert')).toBeVisible();
|
|
expect(screen.queryByText(translations.en.calendarAgendaEmpty)).not.toBeInTheDocument();
|
|
expect(screen.queryByRole('link', { name: /Recorded teacher role/ })).not.toBeInTheDocument();
|
|
});
|
|
|
|
test('month navigation requests only the selected calendar range', async () => {
|
|
mount(); await screen.findByRole('link', { name: /Recorded teacher role/ });
|
|
get.mockResolvedValue({ data: [] });
|
|
fireEvent.click(screen.getByRole('button', { name: translations.en.calendarNextMonth }));
|
|
const next = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
const prefix = `${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, '0')}`;
|
|
expect(get).toHaveBeenLastCalledWith('/calendar/agenda', { params: { from: `${prefix}-01`, to: `${prefix}-${new Date(next.getFullYear(), next.getMonth() + 1, 0).getDate()}` } });
|
|
expect(await screen.findByText(translations.en.calendarAgendaEmpty)).toBeVisible();
|
|
});
|
|
|
|
test('view preference is scoped to the signed-in owner', async () => {
|
|
setAuthUserKey('calendar-owner-one', false);
|
|
const first = mount(); await screen.findByRole('link', { name: /Recorded teacher role/ });
|
|
fireEvent.click(screen.getByRole('button', { name: translations.en.calendarMonth })); first.unmount();
|
|
setAuthUserKey('calendar-owner-two', false); mount();
|
|
await screen.findByRole('link', { name: /Recorded teacher role/ });
|
|
expect(screen.queryByRole('table', { name: translations.en.calendarMonth })).not.toBeInTheDocument();
|
|
});
|