Spaces:
Sleeping
Sleeping
File size: 6,814 Bytes
cd99321 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, waitFor } from '../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { server } from '../../tests/helpers/msw/server';
import { resetAllStores, seedStore } from '../../tests/helpers/store';
import { buildUser } from '../../tests/helpers/factories';
import { useAuthStore } from '../store/authStore';
import { useInAppNotificationStore } from '../store/inAppNotificationStore';
import InAppNotificationsPage from './InAppNotificationsPage';
// Mock InAppNotificationItem to simplify rendering
vi.mock('../components/Notifications/InAppNotificationItem', () => ({
default: ({ notification }: { notification: { id: number; is_read: number } }) => (
<div
data-testid={`notification-${notification.id}`}
data-read={notification.is_read}
>
Notification {notification.id}
</div>
),
}));
beforeEach(() => {
resetAllStores();
seedStore(useAuthStore, { isAuthenticated: true, user: buildUser() });
});
describe('InAppNotificationsPage', () => {
describe('FE-PAGE-NOTIFPAGE-001: Notification list loads on mount', () => {
it('fetches and displays notifications on mount', async () => {
render(<InAppNotificationsPage />);
// Default handler returns 20 notifications (offset 0..19 from 25 total)
await waitFor(() => {
expect(screen.getByTestId('notification-1')).toBeInTheDocument();
});
});
});
describe('FE-PAGE-NOTIFPAGE-002: Unread notifications shown with indicator', () => {
it('shows unread count badge when there are unread notifications', async () => {
render(<InAppNotificationsPage />);
// Default handler returns unread_count: 5
// The badge shows the count as a span inside the heading
await waitFor(() => {
// The "5" badge appears next to the Notifications heading
const badges = screen.getAllByText('5');
expect(badges.length).toBeGreaterThan(0);
});
});
});
describe('FE-PAGE-NOTIFPAGE-003: Mark all read button', () => {
it('shows "Mark all read" button when there are unread notifications', async () => {
render(<InAppNotificationsPage />);
await waitFor(() => {
// Button has "Mark all read" text (possibly hidden on mobile via CSS class)
// In jsdom, CSS "hidden" class doesn't actually hide elements
expect(screen.getByRole('button', { name: /mark all read/i })).toBeInTheDocument();
});
});
});
describe('FE-PAGE-NOTIFPAGE-004: Delete all button', () => {
it('shows "Delete all" button when there are notifications', async () => {
render(<InAppNotificationsPage />);
await waitFor(() => {
expect(screen.getByRole('button', { name: /delete all/i })).toBeInTheDocument();
});
});
});
describe('FE-PAGE-NOTIFPAGE-005: Empty state when no notifications', () => {
it('shows empty state when API returns no notifications', async () => {
server.use(
http.get('/api/notifications/in-app', () => {
return HttpResponse.json({
notifications: [],
total: 0,
unread_count: 0,
});
}),
);
render(<InAppNotificationsPage />);
await waitFor(() => {
expect(screen.getByText(/no notifications/i)).toBeInTheDocument();
});
});
});
describe('FE-PAGE-NOTIFPAGE-006: Filter toggle', () => {
it('renders "All" and "Unread" filter buttons', async () => {
render(<InAppNotificationsPage />);
await waitFor(() => {
expect(screen.getByRole('button', { name: /^all$/i })).toBeInTheDocument();
});
// The unread filter button uses t('notifications.unreadOnly') = 'Unread'
expect(screen.getByRole('button', { name: /^unread$/i })).toBeInTheDocument();
});
});
describe('FE-PAGE-NOTIFPAGE-007: Unread only filter hides read notifications', () => {
it('clicking Unread filter shows only unread notifications', async () => {
const user = userEvent.setup();
// Seed store with known mix of read/unread
const unreadNotif = {
id: 100, is_read: 0, type: 'simple',
scope: 'trip', target: 1, sender_id: 2,
sender_username: 'alice', sender_avatar: null,
recipient_id: 1, title_key: 'n', title_params: '{}',
text_key: 'n', text_params: '{}',
positive_text_key: null, negative_text_key: null,
response: null, navigate_text_key: null, navigate_target: null,
created_at: '2025-01-01T00:00:00Z',
};
const readNotif = {
id: 101, is_read: 1, type: 'simple',
scope: 'trip', target: 1, sender_id: 2,
sender_username: 'alice', sender_avatar: null,
recipient_id: 1, title_key: 'n', title_params: '{}',
text_key: 'n', text_params: '{}',
positive_text_key: null, negative_text_key: null,
response: null, navigate_text_key: null, navigate_target: null,
created_at: '2025-01-01T00:00:00Z',
};
seedStore(useInAppNotificationStore, {
notifications: [unreadNotif, readNotif],
unreadCount: 1,
total: 2,
isLoading: false,
hasMore: false,
fetchNotifications: vi.fn(),
markAllRead: vi.fn(),
deleteAll: vi.fn(),
} as any);
render(<InAppNotificationsPage />);
// Both notifications start visible
await waitFor(() => {
expect(screen.getByTestId('notification-100')).toBeInTheDocument();
expect(screen.getByTestId('notification-101')).toBeInTheDocument();
});
// Click "Unread" filter
await user.click(screen.getByRole('button', { name: /^unread$/i }));
// Only unread notification should be visible
await waitFor(() => {
expect(screen.getByTestId('notification-100')).toBeInTheDocument();
expect(screen.queryByTestId('notification-101')).not.toBeInTheDocument();
});
});
});
describe('FE-PAGE-NOTIFPAGE-008: Page title', () => {
it('shows "Notifications" heading', async () => {
render(<InAppNotificationsPage />);
await waitFor(() => {
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument();
});
expect(screen.getByRole('heading', { level: 1 }).textContent).toMatch(/notifications/i);
});
});
describe('FE-PAGE-NOTIFPAGE-009: Notification total count', () => {
it('shows total notification count in the subtitle', async () => {
render(<InAppNotificationsPage />);
await waitFor(() => {
// "25 notifications" (total from default handler)
expect(screen.getByText(/25 notifications/i)).toBeInTheDocument();
});
});
});
});
|