Files
TREK/client/src/store/tripJournalLinkStore.test.ts
T
Maurice fe05afec19 feat(trips): trip settings with a linked journal tab
The Share button becomes the trip's Settings, a round button beside the back
pill. The dialog is rebuilt around three areas (People, Sharing, Journal) with
a side navigation, a fixed height and 960 px width on wide screens.

Journal links one of your journals from the Journey addon to the trip. Every
trip member is ticked as a reader by default, each can be raised to editor or
left out, and the people a save would let in for the first time are named
before it saves. The link can be changed or removed. With a link the trip gets
a Journal tab on desktop and phone that shows the Journey addon's own view,
live through its sync and trip:journal:changed. Nothing shows with the addon
off.

members.shareTrip and nav.share are gone with the button that used them.
2026-10-18 17:41:25 +02:00

82 lines
3.5 KiB
TypeScript

/**
* FE-TJL-STORE-001 to FE-TJL-STORE-005
*
* The store keeps the server's answer per trip so the Journal tab and the
* Settings dialog read the same one: a write replaces it with what the server
* answered, a 404/403 means there is no journal for this person, and any other
* failure leaves the last answer standing rather than flashing the tab away.
*/
import type { TripJournalLinkResponse } from '@trek/shared';
import { http, HttpResponse } from 'msw';
import { beforeEach, describe, expect, it } from 'vitest';
import { server } from '../../tests/helpers/msw/server';
import { useTripJournalLinkStore } from './tripJournalLinkStore';
const LINKED: TripJournalLinkResponse = {
link: {
journey_id: 9,
title: 'Japan',
subtitle: null,
cover_image: null,
owner_id: 1,
linked_by: 1,
created_at: 1,
my_role: 'owner',
members: [],
},
can_link: true,
can_unlink: true,
can_edit_members: true,
};
const EMPTY: TripJournalLinkResponse = { link: null, can_link: true, can_unlink: false, can_edit_members: false };
beforeEach(() => useTripJournalLinkStore.setState({ byTrip: {} }));
describe('tripJournalLinkStore', () => {
it('FE-TJL-STORE-001: refresh keeps the answer under the trip', async () => {
server.use(http.get('/api/trips/4/journal', () => HttpResponse.json(LINKED)));
await useTripJournalLinkStore.getState().refresh(4);
expect(useTripJournalLinkStore.getState().byTrip['4']).toEqual(LINKED);
});
it('FE-TJL-STORE-002: a 404 (addon off, trip out of reach) is no journal at all', async () => {
server.use(http.get('/api/trips/4/journal', () => HttpResponse.json({ error: 'gone' }, { status: 404 })));
await useTripJournalLinkStore.getState().refresh(4);
expect(useTripJournalLinkStore.getState().byTrip['4']).toEqual({
link: null,
can_link: false,
can_unlink: false,
can_edit_members: false,
});
});
it('FE-TJL-STORE-003: any other failure leaves the last answer standing', async () => {
useTripJournalLinkStore.setState({ byTrip: { 4: LINKED } });
server.use(http.get('/api/trips/4/journal', () => HttpResponse.json({ error: 'boom' }, { status: 500 })));
await useTripJournalLinkStore.getState().refresh(4);
expect(useTripJournalLinkStore.getState().byTrip['4']).toEqual(LINKED);
});
it('FE-TJL-STORE-004: link, setMembers and unlink each keep what the server answered', async () => {
server.use(
http.put('/api/trips/4/journal', () => HttpResponse.json(LINKED)),
http.patch('/api/trips/4/journal/members', () => HttpResponse.json({ ...LINKED, can_unlink: false })),
http.delete('/api/trips/4/journal', () => HttpResponse.json(EMPTY))
);
const s = useTripJournalLinkStore.getState();
await s.link(4, 9, []);
expect(useTripJournalLinkStore.getState().byTrip['4']).toEqual(LINKED);
await s.setMembers(4, []);
expect(useTripJournalLinkStore.getState().byTrip['4']?.can_unlink).toBe(false);
await s.unlink(4);
expect(useTripJournalLinkStore.getState().byTrip['4']).toEqual(EMPTY);
});
it('FE-TJL-STORE-005: a failed write rejects for its caller and keeps the last answer', async () => {
useTripJournalLinkStore.setState({ byTrip: { 4: EMPTY } });
server.use(http.put('/api/trips/4/journal', () => HttpResponse.json({ error: 'no' }, { status: 403 })));
await expect(useTripJournalLinkStore.getState().link(4, 9, [])).rejects.toBeTruthy();
expect(useTripJournalLinkStore.getState().byTrip['4']).toEqual(EMPTY);
});
});