mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
15f22058e9
Three things Sonar was right about. The content panel had grown a filter, a menu and an upload into one function of thirty-two branches: the filtering is four pure rules in photoFilter.ts, the row that drives it is its own component, and what is left in the panel is the grid and the drop zone. The gallery rows behind an entry upload move out of the store's setter into a named function, since five levels of nesting is a place bugs hide. And the ids for placed elements come from getRandomValues rather than Math.random, in one module instead of four copies of the same line: two editors minting the same id for two elements is one of them losing their work. The new paths carry tests now, which is what the gate was really asking for: the filter's rules, the ids, the upload helper, the store's derived gallery rows, the canvas file drop, and the shell's upload, drop and stop switch.
503 lines
21 KiB
TypeScript
503 lines
21 KiB
TypeScript
// FE-STORE-JOURNEY-001 to FE-STORE-JOURNEY-015
|
|
import { http, HttpResponse } from 'msw';
|
|
import { server } from '../../tests/helpers/msw/server';
|
|
import { journeyApi } from '../api/client';
|
|
import { useJourneyStore } from './journeyStore';
|
|
import type { JourneyDetail, JourneyEntry, JourneyPhoto } from './journeyStore';
|
|
|
|
const initialState = useJourneyStore.getState();
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
let _seq = 100;
|
|
function nextId(): number {
|
|
return ++_seq;
|
|
}
|
|
|
|
function buildJourney(overrides: Record<string, unknown> = {}) {
|
|
const id = (overrides.id as number) ?? nextId();
|
|
return {
|
|
id,
|
|
user_id: 1,
|
|
title: `Journey ${id}`,
|
|
subtitle: null,
|
|
cover_gradient: null,
|
|
cover_image: null,
|
|
status: 'draft' as const,
|
|
created_at: Date.now(),
|
|
updated_at: Date.now(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildJourneyDetail(overrides: Record<string, unknown> = {}): JourneyDetail {
|
|
const base = buildJourney(overrides);
|
|
return {
|
|
...base,
|
|
entries: [],
|
|
trips: [],
|
|
contributors: [],
|
|
stats: { entries: 0, photos: 0, cities: 0 },
|
|
...(overrides as any),
|
|
};
|
|
}
|
|
|
|
function buildEntry(overrides: Record<string, unknown> = {}): JourneyEntry {
|
|
const id = (overrides.id as number) ?? nextId();
|
|
return {
|
|
id,
|
|
journey_id: 1,
|
|
source_trip_id: null,
|
|
source_place_id: null,
|
|
source_trip_name: null,
|
|
author_id: 1,
|
|
type: 'entry',
|
|
title: `Entry ${id}`,
|
|
story: null,
|
|
entry_date: '2026-04-01',
|
|
entry_time: null,
|
|
location_name: null,
|
|
location_lat: null,
|
|
location_lng: null,
|
|
mood: null,
|
|
weather: null,
|
|
tags: [],
|
|
pros_cons: null,
|
|
visibility: 'private',
|
|
sort_order: 0,
|
|
photos: [],
|
|
created_at: Date.now(),
|
|
updated_at: Date.now(),
|
|
...overrides,
|
|
} as JourneyEntry;
|
|
}
|
|
|
|
function buildPhoto(overrides: Record<string, unknown> = {}): JourneyPhoto {
|
|
const id = (overrides.id as number) ?? nextId();
|
|
return {
|
|
id,
|
|
entry_id: 1,
|
|
provider: 'local',
|
|
asset_id: null,
|
|
owner_id: null,
|
|
file_path: `/uploads/photo_${id}.jpg`,
|
|
thumbnail_path: null,
|
|
caption: null,
|
|
sort_order: 0,
|
|
width: null,
|
|
height: null,
|
|
shared: 0,
|
|
created_at: Date.now(),
|
|
...overrides,
|
|
} as JourneyPhoto;
|
|
}
|
|
|
|
// ── Setup ────────────────────────────────────────────────────────────────────
|
|
|
|
beforeEach(() => {
|
|
useJourneyStore.setState(initialState, true);
|
|
server.resetHandlers();
|
|
});
|
|
|
|
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
|
|
describe('journeyStore', () => {
|
|
// ── loadJourneys ─────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-001: loadJourneys populates store', async () => {
|
|
const j1 = buildJourney({ id: 1 });
|
|
const j2 = buildJourney({ id: 2 });
|
|
server.use(
|
|
http.get('/api/journeys', () =>
|
|
HttpResponse.json({ journeys: [j1, j2] })
|
|
)
|
|
);
|
|
await useJourneyStore.getState().loadJourneys();
|
|
expect(useJourneyStore.getState().journeys).toHaveLength(2);
|
|
expect(useJourneyStore.getState().journeys[0].id).toBe(1);
|
|
});
|
|
|
|
it('FE-STORE-JOURNEY-002: loadJourneys sets loading false on error', async () => {
|
|
server.use(
|
|
http.get('/api/journeys', () =>
|
|
HttpResponse.json({ error: 'server error' }, { status: 500 })
|
|
)
|
|
);
|
|
await expect(useJourneyStore.getState().loadJourneys()).rejects.toThrow();
|
|
expect(useJourneyStore.getState().loading).toBe(false);
|
|
});
|
|
|
|
// ── loadJourney ──────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-003: loadJourney sets current journey', async () => {
|
|
const detail = buildJourneyDetail({ id: 5 });
|
|
server.use(
|
|
http.get('/api/journeys/5', () =>
|
|
HttpResponse.json(detail)
|
|
)
|
|
);
|
|
await useJourneyStore.getState().loadJourney(5);
|
|
expect(useJourneyStore.getState().current?.id).toBe(5);
|
|
expect(useJourneyStore.getState().loading).toBe(false);
|
|
});
|
|
|
|
it('FE-STORE-JOURNEY-004: loadJourney sets loading false on error', async () => {
|
|
server.use(
|
|
http.get('/api/journeys/999', () =>
|
|
HttpResponse.json({ error: 'not found' }, { status: 404 })
|
|
)
|
|
);
|
|
await expect(useJourneyStore.getState().loadJourney(999)).rejects.toThrow();
|
|
expect(useJourneyStore.getState().loading).toBe(false);
|
|
expect(useJourneyStore.getState().notFound).toBe(true);
|
|
});
|
|
|
|
// ── createJourney ────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-005: createJourney adds to store and returns journey', async () => {
|
|
const created = buildJourney({ id: 10, title: 'My Trip' });
|
|
server.use(
|
|
http.post('/api/journeys', () =>
|
|
HttpResponse.json(created)
|
|
)
|
|
);
|
|
const result = await useJourneyStore.getState().createJourney({ title: 'My Trip' });
|
|
expect(result.id).toBe(10);
|
|
expect(useJourneyStore.getState().journeys).toContainEqual(created);
|
|
});
|
|
|
|
it('FE-STORE-JOURNEY-006: createJourney throws on API error', async () => {
|
|
server.use(
|
|
http.post('/api/journeys', () =>
|
|
HttpResponse.json({ error: 'Validation failed' }, { status: 422 })
|
|
)
|
|
);
|
|
await expect(useJourneyStore.getState().createJourney({ title: '' })).rejects.toThrow();
|
|
});
|
|
|
|
// ── updateJourney ────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-007: updateJourney updates in list and current', async () => {
|
|
const existing = buildJourney({ id: 20, title: 'Old' });
|
|
const detail = buildJourneyDetail({ id: 20, title: 'Old' });
|
|
useJourneyStore.setState({ journeys: [existing], current: detail });
|
|
|
|
server.use(
|
|
http.patch('/api/journeys/20', () =>
|
|
HttpResponse.json({ title: 'New' })
|
|
)
|
|
);
|
|
await useJourneyStore.getState().updateJourney(20, { title: 'New' });
|
|
expect(useJourneyStore.getState().journeys[0].title).toBe('New');
|
|
expect(useJourneyStore.getState().current?.title).toBe('New');
|
|
});
|
|
|
|
// ── deleteJourney ────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-008: deleteJourney removes from list', async () => {
|
|
const j1 = buildJourney({ id: 30 });
|
|
const j2 = buildJourney({ id: 31 });
|
|
useJourneyStore.setState({ journeys: [j1, j2] });
|
|
|
|
server.use(
|
|
http.delete('/api/journeys/30', () =>
|
|
HttpResponse.json({})
|
|
)
|
|
);
|
|
await useJourneyStore.getState().deleteJourney(30);
|
|
expect(useJourneyStore.getState().journeys).toHaveLength(1);
|
|
expect(useJourneyStore.getState().journeys[0].id).toBe(31);
|
|
});
|
|
|
|
it('FE-STORE-JOURNEY-009: deleteJourney clears current if matching', async () => {
|
|
const detail = buildJourneyDetail({ id: 40 });
|
|
useJourneyStore.setState({ journeys: [buildJourney({ id: 40 })], current: detail });
|
|
|
|
server.use(
|
|
http.delete('/api/journeys/40', () =>
|
|
HttpResponse.json({})
|
|
)
|
|
);
|
|
await useJourneyStore.getState().deleteJourney(40);
|
|
expect(useJourneyStore.getState().current).toBeNull();
|
|
});
|
|
|
|
// ── createEntry ──────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-010: createEntry adds entry to current', async () => {
|
|
const detail = buildJourneyDetail({ id: 50 });
|
|
useJourneyStore.setState({ current: detail });
|
|
|
|
const newEntry = buildEntry({ id: 60, journey_id: 50 });
|
|
server.use(
|
|
http.post('/api/journeys/50/entries', () =>
|
|
HttpResponse.json(newEntry)
|
|
)
|
|
);
|
|
const result = await useJourneyStore.getState().createEntry(50, { title: 'Day 1' });
|
|
expect(result.id).toBe(60);
|
|
expect(useJourneyStore.getState().current?.entries).toHaveLength(1);
|
|
expect(useJourneyStore.getState().current?.entries[0].id).toBe(60);
|
|
});
|
|
|
|
// ── updateEntry ──────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-011: updateEntry updates entry in current', async () => {
|
|
const entry = buildEntry({ id: 70, title: 'Old Title' });
|
|
const detail = buildJourneyDetail({ id: 50, entries: [entry] });
|
|
useJourneyStore.setState({ current: detail });
|
|
|
|
server.use(
|
|
http.patch('/api/journeys/entries/70', () =>
|
|
HttpResponse.json({ title: 'New Title' })
|
|
)
|
|
);
|
|
await useJourneyStore.getState().updateEntry(70, { title: 'New Title' });
|
|
expect(useJourneyStore.getState().current?.entries[0].title).toBe('New Title');
|
|
});
|
|
|
|
// ── deleteEntry ──────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-012: deleteEntry removes entry from current', async () => {
|
|
const entry1 = buildEntry({ id: 80 });
|
|
const entry2 = buildEntry({ id: 81 });
|
|
const detail = buildJourneyDetail({ id: 50, entries: [entry1, entry2] });
|
|
useJourneyStore.setState({ current: detail });
|
|
|
|
server.use(
|
|
http.delete('/api/journeys/entries/80', () =>
|
|
HttpResponse.json({})
|
|
)
|
|
);
|
|
await useJourneyStore.getState().deleteEntry(80);
|
|
expect(useJourneyStore.getState().current?.entries).toHaveLength(1);
|
|
expect(useJourneyStore.getState().current?.entries[0].id).toBe(81);
|
|
});
|
|
|
|
// ── uploadPhotos ─────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-013: uploadPhotos appends photos to entry', async () => {
|
|
const existingPhoto = buildPhoto({ id: 90, entry_id: 100 });
|
|
const entry = buildEntry({ id: 100, photos: [existingPhoto] });
|
|
const detail = buildJourneyDetail({ id: 50, entries: [entry] });
|
|
useJourneyStore.setState({ current: detail });
|
|
|
|
const newPhoto = buildPhoto({ id: 91, entry_id: 100 });
|
|
// MSW's XHR interceptor calls request.arrayBuffer() on FormData bodies to
|
|
// emit upload progress events, which hangs in jsdom+Node. Spy on the API
|
|
// layer directly so this test exercises store state management only.
|
|
const spy = vi.spyOn(journeyApi, 'uploadPhotos').mockResolvedValue({ photos: [newPhoto] } as any);
|
|
const file = new File(['x'], 'photo.jpg', { type: 'image/jpeg' });
|
|
const result = await useJourneyStore.getState().uploadPhotos(100, [file]);
|
|
expect(result.succeeded).toHaveLength(1);
|
|
expect(result.succeeded[0].id).toBe(91);
|
|
expect(result.failed).toHaveLength(0);
|
|
const storedEntry = useJourneyStore.getState().current?.entries.find(e => e.id === 100);
|
|
expect(storedEntry?.photos).toHaveLength(2);
|
|
spy.mockRestore();
|
|
});
|
|
|
|
/*
|
|
* A picture uploaded onto an entry is a gallery row as well: the entry only
|
|
* holds a link to it. Studio's content browser reads the gallery, so without
|
|
* this a picture added from there vanished until the journey was fetched
|
|
* again (#2064).
|
|
*/
|
|
it('FE-STORE-JOURNEY-013b: uploadPhotos puts the picture in the gallery the entry links to', async () => {
|
|
const entry = buildEntry({ id: 100, photos: [] });
|
|
useJourneyStore.setState({ current: buildJourneyDetail({ id: 50, entries: [entry], gallery: [] }) });
|
|
const uploaded = buildPhoto({ id: 91, entry_id: 100, photo_id: 700, caption: 'a caption' });
|
|
const spy = vi.spyOn(journeyApi, 'uploadPhotos').mockResolvedValue({ photos: [uploaded] } as any);
|
|
|
|
await useJourneyStore.getState().uploadPhotos(100, [new File(['x'], 'p.jpg', { type: 'image/jpeg' })]);
|
|
|
|
const gallery = useJourneyStore.getState().current?.gallery ?? [];
|
|
expect(gallery).toHaveLength(1);
|
|
// The gallery's own shape: the journey it belongs to, and no entry on it.
|
|
expect(gallery[0]).toMatchObject({ id: 91, photo_id: 700, caption: 'a caption', journey_id: 50 });
|
|
expect('entry_id' in gallery[0]).toBe(false);
|
|
spy.mockRestore();
|
|
});
|
|
|
|
it('FE-STORE-JOURNEY-013c: the gallery row the route answers with wins over a derived one', async () => {
|
|
useJourneyStore.setState({
|
|
current: buildJourneyDetail({ id: 50, entries: [buildEntry({ id: 100, photos: [] })], gallery: [] }),
|
|
});
|
|
const uploaded = buildPhoto({ id: 91, entry_id: 100, photo_id: 700 });
|
|
const answered = { id: 91, journey_id: 50, photo_id: 700, caption: null, shared: 0, sort_order: 4, created_at: 7 };
|
|
const spy = vi.spyOn(journeyApi, 'uploadPhotos')
|
|
.mockResolvedValue({ photos: [uploaded], gallery: [answered] } as any);
|
|
|
|
await useJourneyStore.getState().uploadPhotos(100, [new File(['x'], 'p.jpg', { type: 'image/jpeg' })]);
|
|
|
|
expect(useJourneyStore.getState().current?.gallery).toEqual([answered]);
|
|
spy.mockRestore();
|
|
});
|
|
|
|
it('FE-STORE-JOURNEY-013d: a picture already in the gallery is not added to it twice', async () => {
|
|
const known = { id: 91, journey_id: 50, photo_id: 700, caption: null, shared: 0, sort_order: 0, created_at: 0 };
|
|
useJourneyStore.setState({
|
|
current: buildJourneyDetail({ id: 50, entries: [buildEntry({ id: 100, photos: [] })], gallery: [known] }),
|
|
});
|
|
const spy = vi.spyOn(journeyApi, 'uploadPhotos')
|
|
.mockResolvedValue({ photos: [buildPhoto({ id: 91, entry_id: 100, photo_id: 700 })] } as any);
|
|
|
|
await useJourneyStore.getState().uploadPhotos(100, [new File(['x'], 'p.jpg', { type: 'image/jpeg' })]);
|
|
|
|
expect(useJourneyStore.getState().current?.gallery).toEqual([known]);
|
|
spy.mockRestore();
|
|
});
|
|
|
|
it('FE-STORE-JOURNEY-017: uploadPhotos returns failed files and merges only succeeded on network error', async () => {
|
|
const entry = buildEntry({ id: 100, photos: [] });
|
|
const detail = buildJourneyDetail({ id: 50, entries: [entry] });
|
|
useJourneyStore.setState({ current: detail });
|
|
|
|
server.use(
|
|
http.post('/api/journeys/entries/100/photos', () =>
|
|
HttpResponse.error()
|
|
)
|
|
);
|
|
const file = new File(['x'], 'fail.jpg', { type: 'image/jpeg' });
|
|
const result = await useJourneyStore.getState().uploadPhotos(100, [file]);
|
|
expect(result.succeeded).toHaveLength(0);
|
|
expect(result.failed).toHaveLength(1);
|
|
expect(result.failed[0]).toBe(file);
|
|
const storedEntry = useJourneyStore.getState().current?.entries.find(e => e.id === 100);
|
|
expect(storedEntry?.photos).toHaveLength(0);
|
|
});
|
|
|
|
it('FE-STORE-JOURNEY-018: uploadPhotos merges each file result incrementally on partial success', async () => {
|
|
const entry = buildEntry({ id: 100, photos: [] });
|
|
const detail = buildJourneyDetail({ id: 50, entries: [entry] });
|
|
useJourneyStore.setState({ current: detail });
|
|
|
|
const photo1 = buildPhoto({ id: 91, entry_id: 100 });
|
|
const photo2 = buildPhoto({ id: 92, entry_id: 100 });
|
|
let callCount = 0;
|
|
// Spy on the API layer to avoid MSW's FormData body hang (see FE-STORE-JOURNEY-013).
|
|
// Use a 4xx-shaped error for file2 so isRetryable returns false and the test runs instantly.
|
|
const spy = vi.spyOn(journeyApi, 'uploadPhotos').mockImplementation(async () => {
|
|
callCount++;
|
|
if (callCount === 1) return { photos: [photo1] } as any;
|
|
throw Object.assign(new Error('Bad Request'), { response: { status: 400 } });
|
|
});
|
|
const file1 = new File(['a'], 'ok.jpg', { type: 'image/jpeg' });
|
|
const file2 = new File(['b'], 'fail.jpg', { type: 'image/jpeg' });
|
|
const result = await useJourneyStore.getState().uploadPhotos(100, [file1, file2], undefined);
|
|
expect(result.succeeded).toHaveLength(1);
|
|
expect(result.succeeded[0].id).toBe(photo1.id);
|
|
expect(result.failed).toHaveLength(1);
|
|
const storedEntry = useJourneyStore.getState().current?.entries.find(e => e.id === 100);
|
|
expect(storedEntry?.photos).toHaveLength(1);
|
|
void photo2; // referenced to avoid lint warning
|
|
spy.mockRestore();
|
|
});
|
|
|
|
// ── deletePhoto ──────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-014: deletePhoto removes photo from entry', async () => {
|
|
const photo1 = buildPhoto({ id: 200, entry_id: 100 });
|
|
const photo2 = buildPhoto({ id: 201, entry_id: 100 });
|
|
const entry = buildEntry({ id: 100, photos: [photo1, photo2] });
|
|
const detail = buildJourneyDetail({ id: 50, entries: [entry] });
|
|
useJourneyStore.setState({ current: detail });
|
|
|
|
server.use(
|
|
http.delete('/api/journeys/photos/200', () =>
|
|
HttpResponse.json({})
|
|
)
|
|
);
|
|
await useJourneyStore.getState().deletePhoto(200);
|
|
const storedEntry = useJourneyStore.getState().current?.entries.find(e => e.id === 100);
|
|
expect(storedEntry?.photos).toHaveLength(1);
|
|
expect(storedEntry?.photos[0].id).toBe(201);
|
|
});
|
|
|
|
// ── loadJourney silent refresh ───────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-016: loadJourney does not set loading when refreshing same journey', async () => {
|
|
const existing = buildJourneyDetail({ id: 5, title: 'Old' });
|
|
useJourneyStore.setState({ current: existing, loading: false });
|
|
|
|
const loadingValues: boolean[] = [];
|
|
const unsub = useJourneyStore.subscribe(s => loadingValues.push(s.loading));
|
|
|
|
const refreshed = buildJourneyDetail({ id: 5, title: 'Refreshed' });
|
|
server.use(
|
|
http.get('/api/journeys/5', () => HttpResponse.json(refreshed))
|
|
);
|
|
|
|
await useJourneyStore.getState().loadJourney(5);
|
|
unsub();
|
|
|
|
expect(loadingValues.every(v => v === false)).toBe(true);
|
|
expect(useJourneyStore.getState().current?.title).toBe('Refreshed');
|
|
});
|
|
|
|
it('FE-STORE-JOURNEY-017: loadJourney sets loading on cold load (different journey)', async () => {
|
|
const existing = buildJourneyDetail({ id: 5 });
|
|
useJourneyStore.setState({ current: existing, loading: false });
|
|
|
|
const loadingValues: boolean[] = [];
|
|
const unsub = useJourneyStore.subscribe(s => loadingValues.push(s.loading));
|
|
|
|
const other = buildJourneyDetail({ id: 99 });
|
|
server.use(
|
|
http.get('/api/journeys/99', () => HttpResponse.json(other))
|
|
);
|
|
|
|
await useJourneyStore.getState().loadJourney(99);
|
|
unsub();
|
|
|
|
expect(loadingValues).toContain(true);
|
|
expect(useJourneyStore.getState().current?.id).toBe(99);
|
|
expect(useJourneyStore.getState().loading).toBe(false);
|
|
});
|
|
|
|
// ── reorderEntries ───────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-018: reorderEntries reorders by sort_order not entry_time', async () => {
|
|
const a = buildEntry({ id: 201, entry_date: '2026-04-01', entry_time: '09:00', sort_order: 0 });
|
|
const b = buildEntry({ id: 202, entry_date: '2026-04-01', entry_time: '11:00', sort_order: 1 });
|
|
const c = buildEntry({ id: 203, entry_date: '2026-04-01', entry_time: '14:00', sort_order: 2 });
|
|
const detail = buildJourneyDetail({ id: 55, entries: [a, b, c] });
|
|
useJourneyStore.setState({ current: detail });
|
|
|
|
server.use(
|
|
http.put('/api/journeys/55/entries/reorder', () => HttpResponse.json({ success: true }))
|
|
);
|
|
await useJourneyStore.getState().reorderEntries(55, [202, 201, 203]);
|
|
const ids = useJourneyStore.getState().current?.entries.map(e => e.id);
|
|
expect(ids).toEqual([202, 201, 203]);
|
|
});
|
|
|
|
it('FE-STORE-JOURNEY-019: reorderEntries rolls back on API failure', async () => {
|
|
const a = buildEntry({ id: 211, entry_date: '2026-04-01', sort_order: 0 });
|
|
const b = buildEntry({ id: 212, entry_date: '2026-04-01', sort_order: 1 });
|
|
const detail = buildJourneyDetail({ id: 56, entries: [a, b] });
|
|
useJourneyStore.setState({ current: detail });
|
|
|
|
server.use(
|
|
http.put('/api/journeys/56/entries/reorder', () => HttpResponse.json({}, { status: 403 }))
|
|
);
|
|
await expect(useJourneyStore.getState().reorderEntries(56, [212, 211])).rejects.toBeTruthy();
|
|
const ids = useJourneyStore.getState().current?.entries.map(e => e.id);
|
|
expect(ids).toEqual([211, 212]);
|
|
});
|
|
|
|
// ── clear ────────────────────────────────────────────────────────────────
|
|
|
|
it('FE-STORE-JOURNEY-015: clear resets state', () => {
|
|
useJourneyStore.setState({
|
|
journeys: [buildJourney()],
|
|
current: buildJourneyDetail(),
|
|
loading: true,
|
|
});
|
|
useJourneyStore.getState().clear();
|
|
expect(useJourneyStore.getState().journeys).toEqual([]);
|
|
expect(useJourneyStore.getState().current).toBeNull();
|
|
expect(useJourneyStore.getState().loading).toBe(false);
|
|
});
|
|
});
|