mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
173 lines
5.8 KiB
TypeScript
173 lines
5.8 KiB
TypeScript
import React from 'react';
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { render, screen, fireEvent, waitFor } from '../../../helpers/render';
|
|
import { resetAllStores } from '../../../helpers/store';
|
|
import MNotificationRow from '../../../../src/mobile/screens/notifications/MNotificationRow';
|
|
import {
|
|
useInAppNotificationStore, type InAppNotification,
|
|
} from '../../../../src/store/inAppNotificationStore';
|
|
|
|
// FE-MOB-NROW-001 onwards
|
|
|
|
const markRead = vi.fn(async (_id: number) => {});
|
|
const deleteNotification = vi.fn(async (_id: number) => {});
|
|
const respondToBoolean = vi.fn(async (_id: number, _r: 'positive' | 'negative') => {});
|
|
|
|
function buildNotification(over: Partial<InAppNotification> = {}): InAppNotification {
|
|
return {
|
|
id: 3,
|
|
type: 'simple',
|
|
scope: 'trip',
|
|
target: 1,
|
|
sender_id: 2,
|
|
sender_username: 'maurice',
|
|
sender_avatar: null,
|
|
recipient_id: 1,
|
|
title_key: 'notif.title',
|
|
title_params: {},
|
|
text_key: 'notif.text',
|
|
text_params: {},
|
|
positive_text_key: null,
|
|
negative_text_key: null,
|
|
response: null,
|
|
navigate_text_key: null,
|
|
navigate_target: null,
|
|
is_read: false,
|
|
created_at: new Date().toISOString(),
|
|
...over,
|
|
};
|
|
}
|
|
|
|
const agesAgo = (minutes: number) => new Date(Date.now() - minutes * 60000).toISOString();
|
|
|
|
beforeEach(() => {
|
|
resetAllStores();
|
|
markRead.mockClear();
|
|
deleteNotification.mockClear();
|
|
respondToBoolean.mockClear();
|
|
useInAppNotificationStore.setState({ markRead, deleteNotification, respondToBoolean });
|
|
});
|
|
|
|
describe('MNotificationRow', () => {
|
|
it('FE-MOB-NROW-001: shows title, body, sender initial and the unread dot', () => {
|
|
const { container } = render(<MNotificationRow notification={buildNotification()} />);
|
|
|
|
expect(screen.getByText('notif.title')).toBeInTheDocument();
|
|
expect(screen.getByText('notif.text')).toBeInTheDocument();
|
|
expect(screen.getByText('M')).toBeInTheDocument();
|
|
expect(container.querySelectorAll('span[aria-hidden]')).toHaveLength(1);
|
|
});
|
|
|
|
it('FE-MOB-NROW-002: a read notification drops the dot', () => {
|
|
const { container } = render(<MNotificationRow notification={buildNotification({ is_read: true })} />);
|
|
|
|
expect(container.querySelectorAll('span[aria-hidden]')).toHaveLength(0);
|
|
});
|
|
|
|
it('FE-MOB-NROW-003: renders the sender avatar when there is one', () => {
|
|
const { container } = render(
|
|
<MNotificationRow notification={buildNotification({ sender_avatar: '/uploads/avatars/m.jpg' })} />,
|
|
);
|
|
|
|
expect(container.querySelector('img')).toHaveAttribute('src', '/uploads/avatars/m.jpg');
|
|
expect(screen.queryByText('M')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('FE-MOB-NROW-004: a system notification falls back to the bell tile', () => {
|
|
const { container } = render(
|
|
<MNotificationRow notification={buildNotification({ sender_username: null })} />,
|
|
);
|
|
|
|
expect(container.querySelector('.lucide-bell')).toBeInTheDocument();
|
|
});
|
|
|
|
it.each([
|
|
[0, 'just now'],
|
|
[7, '7m'],
|
|
[180, '3h'],
|
|
[60 * 24 * 2, '2d'],
|
|
])('FE-MOB-NROW-005: %s minutes old renders as %s', (minutes, label) => {
|
|
render(<MNotificationRow notification={buildNotification({ created_at: agesAgo(minutes) })} />);
|
|
|
|
expect(screen.getByText(label)).toBeInTheDocument();
|
|
});
|
|
|
|
it('FE-MOB-NROW-006: tapping an unread row marks it read', () => {
|
|
render(<MNotificationRow notification={buildNotification()} />);
|
|
|
|
fireEvent.click(screen.getByText('notif.title'));
|
|
|
|
expect(markRead).toHaveBeenCalledWith(3);
|
|
});
|
|
|
|
it('FE-MOB-NROW-007: tapping an already-read row does nothing', () => {
|
|
render(<MNotificationRow notification={buildNotification({ is_read: true })} />);
|
|
|
|
fireEvent.click(screen.getByText('notif.title'));
|
|
|
|
expect(markRead).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('FE-MOB-NROW-008: the trailing button deletes without marking read', () => {
|
|
render(<MNotificationRow notification={buildNotification()} />);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Delete' }));
|
|
|
|
expect(deleteNotification).toHaveBeenCalledWith(3);
|
|
expect(markRead).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('FE-MOB-NROW-009: a boolean notification answers through the chips', async () => {
|
|
render(
|
|
<MNotificationRow
|
|
notification={buildNotification({
|
|
type: 'boolean', positive_text_key: 'notif.yes', negative_text_key: 'notif.no',
|
|
})}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(screen.getByText('notif.yes'));
|
|
|
|
await waitFor(() => expect(respondToBoolean).toHaveBeenCalledWith(3, 'positive'));
|
|
// The chips sit in their own stop-propagation area, so the row is not marked read.
|
|
expect(markRead).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('FE-MOB-NROW-010: an answered boolean notification ignores further taps', () => {
|
|
render(
|
|
<MNotificationRow
|
|
notification={buildNotification({
|
|
type: 'boolean', positive_text_key: 'notif.yes', negative_text_key: 'notif.no', response: 'negative',
|
|
})}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(screen.getByText('notif.no'));
|
|
fireEvent.click(screen.getByText('notif.yes'));
|
|
|
|
expect(respondToBoolean).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('FE-MOB-NROW-011: a navigate notification marks read and follows its target', async () => {
|
|
render(
|
|
<MNotificationRow
|
|
notification={buildNotification({
|
|
type: 'navigate', navigate_text_key: 'notif.open', navigate_target: '/trips/9',
|
|
})}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(screen.getByText('notif.open'));
|
|
|
|
await waitFor(() => expect(markRead).toHaveBeenCalledWith(3));
|
|
});
|
|
|
|
it('FE-MOB-NROW-012: a boolean notification without labels renders no chips', () => {
|
|
render(
|
|
<MNotificationRow notification={buildNotification({ type: 'boolean', positive_text_key: 'notif.yes' })} />,
|
|
);
|
|
|
|
expect(screen.queryByText('notif.yes')).not.toBeInTheDocument();
|
|
});
|
|
});
|