mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 07:40:59 +00:00
67f20829f7
Both panels loaded the same trips and users, built the same twelve test payloads and sent them the same way. That now lives in useDevNotifications; each panel keeps its own markup and the phone keeps its own wording of the failure toast.
145 lines
5.9 KiB
TypeScript
145 lines
5.9 KiB
TypeScript
// FE-COMP-DEVNOTIF-HOOK-001 to -008: the shared dev notification tester behind both admin shells.
|
|
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
|
|
import { buildUser } from '../../../tests/helpers/factories';
|
|
import { resetAllStores, seedStore } from '../../../tests/helpers/store';
|
|
import { adminApi, tripsApi } from '../../api/client';
|
|
import { useAuthStore } from '../../store/authStore';
|
|
import { useDevNotifications } from './useDevNotifications';
|
|
|
|
const toast = vi.hoisted(() => ({ success: vi.fn(), error: vi.fn() }));
|
|
vi.mock('../shared/Toast', () => ({ useToast: () => toast }));
|
|
|
|
const ME = buildUser({ id: 7, username: 'tester' });
|
|
const TRIPS = [
|
|
{ id: 11, title: 'Paris' },
|
|
{ id: 12, title: 'Tokyo' },
|
|
];
|
|
const USERS = [
|
|
{ id: 21, username: 'alice', email: 'alice@example.com' },
|
|
{ id: 22, username: 'bob', email: 'bob@example.com' },
|
|
];
|
|
|
|
beforeEach(() => {
|
|
resetAllStores();
|
|
seedStore(useAuthStore, { user: ME, isAuthenticated: true });
|
|
vi.spyOn(tripsApi, 'list').mockResolvedValue({ trips: TRIPS });
|
|
vi.spyOn(adminApi, 'users').mockResolvedValue({ users: USERS });
|
|
vi.spyOn(adminApi, 'sendTestNotification').mockResolvedValue({});
|
|
toast.success.mockReset();
|
|
toast.error.mockReset();
|
|
});
|
|
afterEach(() => vi.restoreAllMocks());
|
|
|
|
async function loaded(options?: Parameters<typeof useDevNotifications>[0]) {
|
|
const hook = renderHook(() => useDevNotifications(options));
|
|
await waitFor(() => expect(hook.result.current.selectedUserId).toBe(21));
|
|
await waitFor(() => expect(hook.result.current.selectedTripId).toBe(11));
|
|
return hook;
|
|
}
|
|
|
|
describe('useDevNotifications', () => {
|
|
it('FE-COMP-DEVNOTIF-HOOK-001: loads trips and users and preselects the first of each', async () => {
|
|
const { result } = await loaded();
|
|
expect(result.current.trips).toEqual(TRIPS);
|
|
expect(result.current.users).toEqual(USERS);
|
|
expect(result.current.tripInviteId).toBe('trip_invite-21');
|
|
expect(result.current.vacayInviteId).toBe('vacay_invite-21');
|
|
});
|
|
|
|
it('FE-COMP-DEVNOTIF-HOOK-002: a failed load leaves the lists empty and nothing selected', async () => {
|
|
vi.mocked(tripsApi.list).mockRejectedValue(new Error('down'));
|
|
vi.mocked(adminApi.users).mockRejectedValue(new Error('down'));
|
|
const { result } = renderHook(() => useDevNotifications());
|
|
await waitFor(() => expect(adminApi.users).toHaveBeenCalled());
|
|
expect(result.current.trips).toEqual([]);
|
|
expect(result.current.selectedTripId).toBeNull();
|
|
expect(result.current.selectedUserId).toBeNull();
|
|
});
|
|
|
|
it('FE-COMP-DEVNOTIF-HOOK-003: a self-addressed send targets the signed-in user and toasts the label', async () => {
|
|
const { result } = await loaded();
|
|
await act(async () => {
|
|
await result.current.send.booleanMe();
|
|
});
|
|
expect(adminApi.sendTestNotification).toHaveBeenCalledWith({
|
|
event: 'test_boolean',
|
|
scope: 'user',
|
|
targetId: 7,
|
|
params: {},
|
|
inApp: {
|
|
type: 'boolean',
|
|
positiveCallback: { action: 'test_approve', payload: {} },
|
|
negativeCallback: { action: 'test_deny', payload: {} },
|
|
},
|
|
});
|
|
expect(toast.success).toHaveBeenCalledWith('Sent: boolean-me');
|
|
expect(result.current.sending).toBeNull();
|
|
});
|
|
|
|
it('FE-COMP-DEVNOTIF-HOOK-004: trip events carry the selected trip, its title and the actor', async () => {
|
|
const { result } = await loaded();
|
|
act(() => result.current.setSelectedTripId(12));
|
|
await act(async () => {
|
|
await result.current.send.photosShared();
|
|
});
|
|
expect(adminApi.sendTestNotification).toHaveBeenCalledWith({
|
|
event: 'photos_shared',
|
|
scope: 'trip',
|
|
targetId: 12,
|
|
params: { actor: 'tester', trip: 'Tokyo', count: '5', tripId: '12' },
|
|
});
|
|
});
|
|
|
|
it('FE-COMP-DEVNOTIF-HOOK-005: user events go to the selected recipient under an id naming them', async () => {
|
|
const { result } = await loaded();
|
|
act(() => result.current.setSelectedUserId(22));
|
|
await act(async () => {
|
|
await result.current.send.tripInvite();
|
|
});
|
|
expect(adminApi.sendTestNotification).toHaveBeenCalledWith({
|
|
event: 'trip_invite',
|
|
scope: 'user',
|
|
targetId: 22,
|
|
params: { actor: 'tester', trip: 'Paris', invitee: 'bob@example.com', tripId: '11' },
|
|
});
|
|
expect(toast.success).toHaveBeenCalledWith('Sent: trip_invite-22');
|
|
});
|
|
|
|
it('FE-COMP-DEVNOTIF-HOOK-006: trip and user events do nothing while nothing is selected', async () => {
|
|
vi.mocked(tripsApi.list).mockResolvedValue({ trips: [] });
|
|
vi.mocked(adminApi.users).mockResolvedValue({ users: [] });
|
|
const { result } = renderHook(() => useDevNotifications());
|
|
await waitFor(() => expect(adminApi.users).toHaveBeenCalled());
|
|
await act(async () => {
|
|
await result.current.send.bookingChange();
|
|
await result.current.send.vacayInvite();
|
|
});
|
|
expect(adminApi.sendTestNotification).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('FE-COMP-DEVNOTIF-HOOK-007: by default a failure toasts the server error field, else Failed', async () => {
|
|
const { result } = await loaded();
|
|
vi.mocked(adminApi.sendTestNotification).mockRejectedValueOnce({ response: { data: { error: 'No channel' } } });
|
|
await act(async () => {
|
|
await result.current.send.versionAvailable();
|
|
});
|
|
expect(toast.error).toHaveBeenLastCalledWith('No channel');
|
|
vi.mocked(adminApi.sendTestNotification).mockRejectedValueOnce(new Error('boom'));
|
|
await act(async () => {
|
|
await result.current.send.versionAvailable();
|
|
});
|
|
expect(toast.error).toHaveBeenLastCalledWith('Failed');
|
|
expect(result.current.sending).toBeNull();
|
|
});
|
|
|
|
it('FE-COMP-DEVNOTIF-HOOK-008: a shell can word the failure toast itself', async () => {
|
|
const { result } = await loaded({ errorMessage: (err) => (err instanceof Error ? err.message : 'Failed') });
|
|
vi.mocked(adminApi.sendTestNotification).mockRejectedValueOnce(new Error('boom'));
|
|
await act(async () => {
|
|
await result.current.send.simpleAdmins();
|
|
});
|
|
expect(toast.error).toHaveBeenCalledWith('boom');
|
|
});
|
|
});
|