feat(260929-if2): Erinnerungen anlegen und zur Faelligkeit benachrichtigen (Tracer)
- Reminder-Tabelle mit Zeilenschutz (Mandant+Benutzer, Systemlesen fuer den E-Mail-Planer), API reminders (Liste, Anlegen) - Kachel "Erinnerungen", globaler Melder im Portalrahmen (Browser und Desktop, je Faelligkeit einmal) - Desktop: Laufzeit-Berechtigung fuer Benachrichtigungen nur fuer die gespeicherte Server-Adresse - Zugriffsklassifikation nachgemessen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('next-intl', () => ({
|
||||
useTranslations: () => (key: string) => key,
|
||||
useLocale: () => 'de',
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/reminders-api', async () => {
|
||||
const actual = await vi.importActual<typeof import('@/lib/reminders-api')>('@/lib/reminders-api');
|
||||
return {
|
||||
REMINDERS_CHANGED_EVENT: actual.REMINDERS_CHANGED_EVENT,
|
||||
ReminderRequestError: actual.ReminderRequestError,
|
||||
listReminders: vi.fn(),
|
||||
createReminder: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import { createReminder, listReminders } from '@/lib/reminders-api';
|
||||
import { ReminderWidget } from './reminder-widget';
|
||||
|
||||
const mockList = listReminders as ReturnType<typeof vi.fn>;
|
||||
const mockCreate = createReminder as ReturnType<typeof vi.fn>;
|
||||
|
||||
function reminder(id: string, title: string, dueAt: string, description = '') {
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
dueAt,
|
||||
emailEnabled: false,
|
||||
createdAt: dueAt,
|
||||
updatedAt: dueAt,
|
||||
};
|
||||
}
|
||||
|
||||
const props = { instanceId: 'w1', config: {}, isEditMode: false };
|
||||
|
||||
let requestPermission: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(() => {
|
||||
window.localStorage.clear();
|
||||
mockList.mockReset();
|
||||
mockCreate.mockReset();
|
||||
requestPermission = vi.fn(async () => 'granted');
|
||||
const ctor = vi.fn();
|
||||
Object.assign(ctor, { permission: 'default', requestPermission });
|
||||
vi.stubGlobal('Notification', ctor);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
async function openForm() {
|
||||
fireEvent.click(await screen.findByText('reminder.add'));
|
||||
}
|
||||
|
||||
function fillAndSubmit(title: string) {
|
||||
fireEvent.change(screen.getByLabelText('reminder.titleLabel'), { target: { value: title } });
|
||||
fireEvent.change(screen.getByLabelText('reminder.dateLabel'), { target: { value: '2099-03-04' } });
|
||||
fireEvent.change(screen.getByLabelText('reminder.timeLabel'), { target: { value: '14:30' } });
|
||||
fireEvent.click(screen.getByText('reminder.save'));
|
||||
}
|
||||
|
||||
describe('ReminderWidget', () => {
|
||||
it('listet die Erinnerungen nach Faelligkeit sortiert', async () => {
|
||||
mockList.mockResolvedValue([
|
||||
reminder('b', 'Spaeter', '2099-01-02T10:00:00.000Z'),
|
||||
reminder('a', 'Frueh', '2099-01-01T10:00:00.000Z', 'Beschreibung'),
|
||||
]);
|
||||
render(<ReminderWidget {...props} />);
|
||||
const rows = await screen.findAllByTestId('reminder-row');
|
||||
expect(rows[0].textContent).toContain('Frueh');
|
||||
expect(rows[0].textContent).toContain('Beschreibung');
|
||||
expect(rows[1].textContent).toContain('Spaeter');
|
||||
});
|
||||
|
||||
it('zeigt den Leerzustand', async () => {
|
||||
mockList.mockResolvedValue([]);
|
||||
render(<ReminderWidget {...props} />);
|
||||
expect(await screen.findByText('reminder.empty')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('das Rendern fragt keine Benachrichtigungs-Erlaubnis (D-04)', async () => {
|
||||
mockList.mockResolvedValue([]);
|
||||
render(<ReminderWidget {...props} />);
|
||||
await screen.findByText('reminder.empty');
|
||||
expect(requestPermission).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Anlegen sendet die aus den Ortszeit-Eingaben gebildete ISO-Zeit', async () => {
|
||||
mockList.mockResolvedValue([]);
|
||||
mockCreate.mockResolvedValue(reminder('n', 'Zahnarzt', '2099-03-04T13:30:00.000Z'));
|
||||
render(<ReminderWidget {...props} />);
|
||||
await openForm();
|
||||
fillAndSubmit('Zahnarzt');
|
||||
await waitFor(() => expect(mockCreate).toHaveBeenCalledTimes(1));
|
||||
const arg = mockCreate.mock.calls[0][0];
|
||||
expect(arg.title).toBe('Zahnarzt');
|
||||
expect(arg.dueAt).toBe(new Date('2099-03-04T14:30').toISOString());
|
||||
});
|
||||
|
||||
it('die erste Anlage fragt die Erlaubnis einmal, die zweite nicht mehr', async () => {
|
||||
mockList.mockResolvedValue([]);
|
||||
mockCreate.mockResolvedValue(reminder('n', 'x', '2099-03-04T13:30:00.000Z'));
|
||||
render(<ReminderWidget {...props} />);
|
||||
|
||||
await openForm();
|
||||
fillAndSubmit('Erste');
|
||||
await waitFor(() => expect(mockCreate).toHaveBeenCalledTimes(1));
|
||||
expect(requestPermission).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Dialog ist nach dem Speichern zu; neu oeffnen
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
|
||||
(Notification as unknown as { permission: string }).permission = 'default';
|
||||
await openForm();
|
||||
fillAndSubmit('Zweite');
|
||||
await waitFor(() => expect(mockCreate).toHaveBeenCalledTimes(2));
|
||||
expect(requestPermission).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('lehnt eine Vergangenheit im Formular ab, ohne zu senden', async () => {
|
||||
mockList.mockResolvedValue([]);
|
||||
render(<ReminderWidget {...props} />);
|
||||
await openForm();
|
||||
fireEvent.change(screen.getByLabelText('reminder.titleLabel'), { target: { value: 'x' } });
|
||||
fireEvent.change(screen.getByLabelText('reminder.dateLabel'), { target: { value: '2001-01-01' } });
|
||||
fireEvent.click(screen.getByText('reminder.save'));
|
||||
expect(await screen.findByText('reminder.pastError')).toBeTruthy();
|
||||
expect(mockCreate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('meldet REMINDERS_CHANGED nach dem Anlegen', async () => {
|
||||
mockList.mockResolvedValue([]);
|
||||
mockCreate.mockResolvedValue(reminder('n', 'x', '2099-03-04T13:30:00.000Z'));
|
||||
const handler = vi.fn();
|
||||
window.addEventListener('tessera:reminders-changed', handler);
|
||||
render(<ReminderWidget {...props} />);
|
||||
await openForm();
|
||||
await act(async () => {
|
||||
fillAndSubmit('Meldung');
|
||||
});
|
||||
await waitFor(() => expect(handler).toHaveBeenCalled());
|
||||
window.removeEventListener('tessera:reminders-changed', handler);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user