mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
test(front): add test suite frontend (WIP)
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildDay, buildAssignment, buildPlace } from '../../helpers/factories';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > assignments', () => {
|
||||
const seedData = () => {
|
||||
useTripStore.setState({
|
||||
days: [buildDay({ id: 10 }), buildDay({ id: 20 })],
|
||||
assignments: {
|
||||
'10': [buildAssignment({ id: 100, day_id: 10 })],
|
||||
'20': [],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it('FE-WSEVT-ASSIGN-001: assignment:created adds assignment to correct day', () => {
|
||||
seedData();
|
||||
const newAssignment = buildAssignment({ id: 200, day_id: 20 });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'assignment:created', assignment: newAssignment });
|
||||
const { assignments } = useTripStore.getState();
|
||||
expect(assignments['20']).toHaveLength(1);
|
||||
expect(assignments['20'][0].id).toBe(200);
|
||||
expect(assignments['10']).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-ASSIGN-002: assignment:created is idempotent — no duplicate if same ID', () => {
|
||||
seedData();
|
||||
const duplicate = buildAssignment({ id: 100, day_id: 10 });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'assignment:created', assignment: duplicate });
|
||||
const { assignments } = useTripStore.getState();
|
||||
expect(assignments['10']).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-ASSIGN-003: assignment:created replaces temp (negative) ID assignment with same place_id', () => {
|
||||
const place = buildPlace({ id: 55 });
|
||||
const tempAssignment = buildAssignment({ id: -1, day_id: 10, place, place_id: place.id });
|
||||
useTripStore.setState({
|
||||
days: [buildDay({ id: 10 })],
|
||||
assignments: { '10': [tempAssignment] },
|
||||
});
|
||||
const realAssignment = buildAssignment({ id: 500, day_id: 10, place, place_id: place.id });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'assignment:created', assignment: realAssignment });
|
||||
const { assignments } = useTripStore.getState();
|
||||
expect(assignments['10']).toHaveLength(1);
|
||||
expect(assignments['10'][0].id).toBe(500);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-ASSIGN-004: assignment:updated merges updated data into correct day', () => {
|
||||
seedData();
|
||||
const updated = buildAssignment({ id: 100, day_id: 10, notes: 'Updated notes' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'assignment:updated', assignment: updated });
|
||||
const { assignments } = useTripStore.getState();
|
||||
expect(assignments['10'][0].notes).toBe('Updated notes');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-ASSIGN-005: assignment:deleted removes assignment from day', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'assignment:deleted', assignmentId: 100, dayId: 10 });
|
||||
const { assignments } = useTripStore.getState();
|
||||
expect(assignments['10']).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-ASSIGN-006: assignment:moved removes from old day and adds to new day', () => {
|
||||
const movedAssignment = buildAssignment({ id: 100, day_id: 20 });
|
||||
useTripStore.setState({
|
||||
days: [buildDay({ id: 10 }), buildDay({ id: 20 })],
|
||||
assignments: {
|
||||
'10': [movedAssignment],
|
||||
'20': [],
|
||||
},
|
||||
});
|
||||
useTripStore.getState().handleRemoteEvent({
|
||||
type: 'assignment:moved',
|
||||
assignment: movedAssignment,
|
||||
oldDayId: 10,
|
||||
newDayId: 20,
|
||||
});
|
||||
const { assignments } = useTripStore.getState();
|
||||
expect(assignments['10']).toHaveLength(0);
|
||||
expect(assignments['20']).toHaveLength(1);
|
||||
expect(assignments['20'][0].id).toBe(100);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-ASSIGN-007: assignment:reordered updates order_index values', () => {
|
||||
const a1 = buildAssignment({ id: 1, day_id: 10, order_index: 0 });
|
||||
const a2 = buildAssignment({ id: 2, day_id: 10, order_index: 1 });
|
||||
const a3 = buildAssignment({ id: 3, day_id: 10, order_index: 2 });
|
||||
useTripStore.setState({
|
||||
assignments: { '10': [a1, a2, a3] },
|
||||
});
|
||||
useTripStore.getState().handleRemoteEvent({
|
||||
type: 'assignment:reordered',
|
||||
dayId: 10,
|
||||
orderedIds: [3, 1, 2],
|
||||
});
|
||||
const { assignments } = useTripStore.getState();
|
||||
const reordered = assignments['10'];
|
||||
const item3 = reordered.find(a => a.id === 3);
|
||||
const item1 = reordered.find(a => a.id === 1);
|
||||
const item2 = reordered.find(a => a.id === 2);
|
||||
expect(item3?.order_index).toBe(0);
|
||||
expect(item1?.order_index).toBe(1);
|
||||
expect(item2?.order_index).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildBudgetItem } from '../../helpers/factories';
|
||||
import type { BudgetMember } from '../../../src/types';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > budget', () => {
|
||||
const member1: BudgetMember = { user_id: 5, paid: false };
|
||||
const member2: BudgetMember = { user_id: 6, paid: true };
|
||||
|
||||
const seedData = () => {
|
||||
useTripStore.setState({
|
||||
budgetItems: [
|
||||
buildBudgetItem({ id: 1, persons: 1, members: [{ ...member1 }] }),
|
||||
buildBudgetItem({ id: 2, persons: 2, members: [{ ...member2 }] }),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
it('FE-WSEVT-BUDGET-001: budget:created adds item to budgetItems', () => {
|
||||
seedData();
|
||||
const newItem = buildBudgetItem({ id: 99, name: 'Hotel' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'budget:created', item: newItem });
|
||||
const { budgetItems } = useTripStore.getState();
|
||||
expect(budgetItems).toHaveLength(3);
|
||||
expect(budgetItems.find(i => i.id === 99)).toBeDefined();
|
||||
});
|
||||
|
||||
it('FE-WSEVT-BUDGET-002: budget:created is idempotent — no duplicate if same ID', () => {
|
||||
seedData();
|
||||
const duplicate = buildBudgetItem({ id: 1, name: 'Duplicate' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'budget:created', item: duplicate });
|
||||
const { budgetItems } = useTripStore.getState();
|
||||
expect(budgetItems).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-BUDGET-003: budget:updated replaces item in array', () => {
|
||||
seedData();
|
||||
const updated = buildBudgetItem({ id: 1, name: 'Updated Hotel', amount: 500 });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'budget:updated', item: updated });
|
||||
const { budgetItems } = useTripStore.getState();
|
||||
const item = budgetItems.find(i => i.id === 1);
|
||||
expect(item?.name).toBe('Updated Hotel');
|
||||
expect(item?.amount).toBe(500);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-BUDGET-004: budget:deleted removes item by ID', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'budget:deleted', itemId: 1 });
|
||||
const { budgetItems } = useTripStore.getState();
|
||||
expect(budgetItems).toHaveLength(1);
|
||||
expect(budgetItems.find(i => i.id === 1)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('FE-WSEVT-BUDGET-005: budget:members-updated replaces entire members array and persons count', () => {
|
||||
seedData();
|
||||
const newMembers: BudgetMember[] = [{ user_id: 7, paid: true }, { user_id: 8, paid: false }];
|
||||
useTripStore.getState().handleRemoteEvent({
|
||||
type: 'budget:members-updated',
|
||||
itemId: 1,
|
||||
members: newMembers,
|
||||
persons: 3,
|
||||
});
|
||||
const { budgetItems } = useTripStore.getState();
|
||||
const item = budgetItems.find(i => i.id === 1);
|
||||
expect(item?.members).toEqual(newMembers);
|
||||
expect(item?.persons).toBe(3);
|
||||
// Other item should be unchanged
|
||||
const item2 = budgetItems.find(i => i.id === 2);
|
||||
expect(item2?.members).toEqual([{ ...member2 }]);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-BUDGET-006: budget:member-paid-updated toggles specific member paid status', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({
|
||||
type: 'budget:member-paid-updated',
|
||||
itemId: 1,
|
||||
userId: 5,
|
||||
paid: true,
|
||||
});
|
||||
const { budgetItems } = useTripStore.getState();
|
||||
const item = budgetItems.find(i => i.id === 1);
|
||||
const m = item?.members?.find(m => m.user_id === 5);
|
||||
expect(m?.paid).toBe(true);
|
||||
// Other item members unchanged
|
||||
const item2 = budgetItems.find(i => i.id === 2);
|
||||
expect(item2?.members?.[0].paid).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildDayNote } from '../../helpers/factories';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > dayNotes', () => {
|
||||
const seedData = () => {
|
||||
useTripStore.setState({
|
||||
dayNotes: {
|
||||
'10': [buildDayNote({ id: 1, day_id: 10, text: 'Original' })],
|
||||
'20': [],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it('FE-WSEVT-DAYNOTE-001: dayNote:created adds note to correct day', () => {
|
||||
seedData();
|
||||
const newNote = buildDayNote({ id: 99, day_id: 10, text: 'New note' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'dayNote:created', dayId: 10, note: newNote });
|
||||
const { dayNotes } = useTripStore.getState();
|
||||
expect(dayNotes['10']).toHaveLength(2);
|
||||
expect(dayNotes['10'].find(n => n.id === 99)).toBeDefined();
|
||||
});
|
||||
|
||||
it('FE-WSEVT-DAYNOTE-002: dayNote:created is idempotent — no duplicate if same ID', () => {
|
||||
seedData();
|
||||
const duplicate = buildDayNote({ id: 1, day_id: 10, text: 'Duplicate' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'dayNote:created', dayId: 10, note: duplicate });
|
||||
const { dayNotes } = useTripStore.getState();
|
||||
expect(dayNotes['10']).toHaveLength(1);
|
||||
expect(dayNotes['10'][0].text).toBe('Original');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-DAYNOTE-003: dayNote:updated replaces note in correct day', () => {
|
||||
seedData();
|
||||
const updated = buildDayNote({ id: 1, day_id: 10, text: 'Updated text' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'dayNote:updated', dayId: 10, note: updated });
|
||||
const { dayNotes } = useTripStore.getState();
|
||||
expect(dayNotes['10'][0].text).toBe('Updated text');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-DAYNOTE-004: dayNote:deleted removes note from correct day', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'dayNote:deleted', dayId: 10, noteId: 1 });
|
||||
const { dayNotes } = useTripStore.getState();
|
||||
expect(dayNotes['10']).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-DAYNOTE-005: operations on day 10 do not affect day 20', () => {
|
||||
seedData();
|
||||
const newNote = buildDayNote({ id: 50, day_id: 10, text: 'Day 10 note' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'dayNote:created', dayId: 10, note: newNote });
|
||||
const { dayNotes } = useTripStore.getState();
|
||||
expect(dayNotes['20']).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildDay, buildAssignment, buildDayNote } from '../../helpers/factories';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > days', () => {
|
||||
const seedData = () => {
|
||||
useTripStore.setState({
|
||||
days: [buildDay({ id: 10 }), buildDay({ id: 20 })],
|
||||
assignments: {
|
||||
'10': [buildAssignment({ id: 100, day_id: 10 })],
|
||||
'20': [],
|
||||
},
|
||||
dayNotes: {
|
||||
'10': [buildDayNote({ id: 1, day_id: 10 })],
|
||||
'20': [],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it('FE-WSEVT-DAY-001: day:created adds day to days array', () => {
|
||||
seedData();
|
||||
const newDay = buildDay({ id: 30 });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'day:created', day: newDay });
|
||||
const { days } = useTripStore.getState();
|
||||
expect(days).toHaveLength(3);
|
||||
expect(days.find(d => d.id === 30)).toBeDefined();
|
||||
});
|
||||
|
||||
it('FE-WSEVT-DAY-002: day:created is idempotent — no duplicate if same ID', () => {
|
||||
seedData();
|
||||
const duplicate = buildDay({ id: 10 });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'day:created', day: duplicate });
|
||||
const { days } = useTripStore.getState();
|
||||
expect(days).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-DAY-003: day:updated replaces day in days array', () => {
|
||||
seedData();
|
||||
const updated = buildDay({ id: 10, title: 'New Title' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'day:updated', day: updated });
|
||||
const { days } = useTripStore.getState();
|
||||
const day10 = days.find(d => d.id === 10);
|
||||
expect(day10?.title).toBe('New Title');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-DAY-004: day:deleted removes day from days array', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'day:deleted', dayId: 10 });
|
||||
const { days } = useTripStore.getState();
|
||||
expect(days).toHaveLength(1);
|
||||
expect(days.find(d => d.id === 10)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('FE-WSEVT-DAY-005: day:deleted removes the assignments key for deleted day', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'day:deleted', dayId: 10 });
|
||||
const { assignments } = useTripStore.getState();
|
||||
expect('10' in assignments).toBe(false);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-DAY-006: day:deleted removes the dayNotes key for deleted day', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'day:deleted', dayId: 10 });
|
||||
const { dayNotes } = useTripStore.getState();
|
||||
expect('10' in dayNotes).toBe(false);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-DAY-007: day:deleted does not remove other days assignments/dayNotes', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'day:deleted', dayId: 10 });
|
||||
const { assignments, dayNotes } = useTripStore.getState();
|
||||
expect('20' in assignments).toBe(true);
|
||||
expect('20' in dayNotes).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildTripFile } from '../../helpers/factories';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > files', () => {
|
||||
const seedData = () => {
|
||||
useTripStore.setState({
|
||||
files: [buildTripFile({ id: 1, original_name: 'document.pdf' })],
|
||||
});
|
||||
};
|
||||
|
||||
it('FE-WSEVT-FILE-001: file:created prepends new file to array', () => {
|
||||
seedData();
|
||||
const newFile = buildTripFile({ id: 99, original_name: 'photo.jpg' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'file:created', file: newFile });
|
||||
const { files } = useTripStore.getState();
|
||||
expect(files).toHaveLength(2);
|
||||
expect(files[0].id).toBe(99); // prepended
|
||||
});
|
||||
|
||||
it('FE-WSEVT-FILE-002: file:created is idempotent — no duplicate if same ID', () => {
|
||||
seedData();
|
||||
const duplicate = buildTripFile({ id: 1, original_name: 'document_dup.pdf' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'file:created', file: duplicate });
|
||||
const { files } = useTripStore.getState();
|
||||
expect(files).toHaveLength(1);
|
||||
expect(files[0].original_name).toBe('document.pdf');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-FILE-003: file:updated replaces file in array', () => {
|
||||
seedData();
|
||||
const updated = buildTripFile({ id: 1, original_name: 'renamed.pdf' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'file:updated', file: updated });
|
||||
const { files } = useTripStore.getState();
|
||||
expect(files[0].original_name).toBe('renamed.pdf');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-FILE-004: file:deleted removes file by ID', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'file:deleted', fileId: 1 });
|
||||
const { files } = useTripStore.getState();
|
||||
expect(files).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-FILE-005: file:created ordering — newest is first', () => {
|
||||
seedData();
|
||||
const f2 = buildTripFile({ id: 2, original_name: 'second.pdf' });
|
||||
const f3 = buildTripFile({ id: 3, original_name: 'third.pdf' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'file:created', file: f2 });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'file:created', file: f3 });
|
||||
const { files } = useTripStore.getState();
|
||||
expect(files[0].id).toBe(3);
|
||||
expect(files[1].id).toBe(2);
|
||||
expect(files[2].id).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildPlace } from '../../helpers/factories';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > memories', () => {
|
||||
it('FE-WSEVT-MEM-001: memories:updated dispatches CustomEvent on window', () => {
|
||||
const received: Event[] = [];
|
||||
const handler = (e: Event) => received.push(e);
|
||||
window.addEventListener('memories:updated', handler);
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'memories:updated', photos: [] });
|
||||
window.removeEventListener('memories:updated', handler);
|
||||
expect(received).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-MEM-002: memories:updated event type is correct', () => {
|
||||
const received: Event[] = [];
|
||||
const handler = (e: Event) => received.push(e);
|
||||
window.addEventListener('memories:updated', handler);
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'memories:updated', photos: [] });
|
||||
window.removeEventListener('memories:updated', handler);
|
||||
expect(received[0].type).toBe('memories:updated');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-MEM-003: memories:updated event detail contains the payload', () => {
|
||||
const received: CustomEvent[] = [];
|
||||
const handler = (e: Event) => received.push(e as CustomEvent);
|
||||
window.addEventListener('memories:updated', handler);
|
||||
const payload = { photos: [{ id: 1, url: '/photo.jpg' }] };
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'memories:updated', ...payload });
|
||||
window.removeEventListener('memories:updated', handler);
|
||||
expect(received[0].detail).toMatchObject(payload);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-MEM-004: memories:updated does not modify store state', () => {
|
||||
const places = [buildPlace({ id: 42, name: 'Eiffel Tower' })];
|
||||
useTripStore.setState({ places });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'memories:updated', photos: [] });
|
||||
const { places: afterPlaces } = useTripStore.getState();
|
||||
expect(afterPlaces).toHaveLength(1);
|
||||
expect(afterPlaces[0].id).toBe(42);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-MEM-005: memories:updated fires exactly once per event', () => {
|
||||
const received: Event[] = [];
|
||||
const handler = (e: Event) => received.push(e);
|
||||
window.addEventListener('memories:updated', handler);
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'memories:updated', photos: [] });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'memories:updated', photos: [] });
|
||||
window.removeEventListener('memories:updated', handler);
|
||||
expect(received).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildPackingItem } from '../../helpers/factories';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > packing', () => {
|
||||
const seedData = () => {
|
||||
useTripStore.setState({
|
||||
packingItems: [buildPackingItem({ id: 1, name: 'Sunscreen' })],
|
||||
});
|
||||
};
|
||||
|
||||
it('FE-WSEVT-PACK-001: packing:created adds item to packingItems', () => {
|
||||
seedData();
|
||||
const newItem = buildPackingItem({ id: 99, name: 'Hat' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'packing:created', item: newItem });
|
||||
const { packingItems } = useTripStore.getState();
|
||||
expect(packingItems).toHaveLength(2);
|
||||
expect(packingItems.find(i => i.id === 99)).toBeDefined();
|
||||
});
|
||||
|
||||
it('FE-WSEVT-PACK-002: packing:created is idempotent — no duplicate if same ID', () => {
|
||||
seedData();
|
||||
const duplicate = buildPackingItem({ id: 1, name: 'Sunscreen Duplicate' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'packing:created', item: duplicate });
|
||||
const { packingItems } = useTripStore.getState();
|
||||
expect(packingItems).toHaveLength(1);
|
||||
expect(packingItems[0].name).toBe('Sunscreen');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-PACK-003: packing:updated replaces item in array', () => {
|
||||
seedData();
|
||||
const updated = buildPackingItem({ id: 1, name: 'SPF 50 Sunscreen' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'packing:updated', item: updated });
|
||||
const { packingItems } = useTripStore.getState();
|
||||
expect(packingItems[0].name).toBe('SPF 50 Sunscreen');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-PACK-004: packing:deleted removes item by ID', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'packing:deleted', itemId: 1 });
|
||||
const { packingItems } = useTripStore.getState();
|
||||
expect(packingItems).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildPlace, buildAssignment } from '../../helpers/factories';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > places', () => {
|
||||
const seedData = () => {
|
||||
const place = buildPlace({ id: 1, name: 'Original' });
|
||||
const assignment = buildAssignment({ id: 100, place, day_id: 10 });
|
||||
useTripStore.setState({
|
||||
places: [place],
|
||||
assignments: { '10': [assignment] },
|
||||
});
|
||||
};
|
||||
|
||||
it('FE-WSEVT-PLACE-001: place:created prepends new place to places array', () => {
|
||||
seedData();
|
||||
const newPlace = buildPlace({ id: 99, name: 'New Place' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'place:created', place: newPlace });
|
||||
const { places } = useTripStore.getState();
|
||||
expect(places[0].id).toBe(99);
|
||||
expect(places).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-PLACE-002: place:created is idempotent — no duplicate if same ID', () => {
|
||||
seedData();
|
||||
const duplicate = buildPlace({ id: 1, name: 'Duplicate' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'place:created', place: duplicate });
|
||||
const { places } = useTripStore.getState();
|
||||
expect(places).toHaveLength(1);
|
||||
expect(places[0].name).toBe('Original');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-PLACE-003: place:updated updates place in places array', () => {
|
||||
seedData();
|
||||
const updated = buildPlace({ id: 1, name: 'Updated Name' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'place:updated', place: updated });
|
||||
const { places } = useTripStore.getState();
|
||||
expect(places[0].name).toBe('Updated Name');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-PLACE-004: place:updated cascades into assignments nested place', () => {
|
||||
seedData();
|
||||
const updated = buildPlace({ id: 1, name: 'Cascaded Update' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'place:updated', place: updated });
|
||||
const { assignments } = useTripStore.getState();
|
||||
expect(assignments['10'][0].place?.name).toBe('Cascaded Update');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-PLACE-005: place:deleted removes place from places array', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'place:deleted', placeId: 1 });
|
||||
const { places } = useTripStore.getState();
|
||||
expect(places).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-PLACE-006: place:deleted cascades — assignments referencing that place are removed', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'place:deleted', placeId: 1 });
|
||||
const { assignments } = useTripStore.getState();
|
||||
expect(assignments['10']).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildReservation } from '../../helpers/factories';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > reservations', () => {
|
||||
const seedData = () => {
|
||||
useTripStore.setState({
|
||||
reservations: [buildReservation({ id: 1, name: 'Hotel Paris' })],
|
||||
});
|
||||
};
|
||||
|
||||
it('FE-WSEVT-RESERV-001: reservation:created prepends new reservation to array', () => {
|
||||
seedData();
|
||||
const newRes = buildReservation({ id: 99, name: 'Flight' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'reservation:created', reservation: newRes });
|
||||
const { reservations } = useTripStore.getState();
|
||||
expect(reservations).toHaveLength(2);
|
||||
expect(reservations[0].id).toBe(99); // prepended, so first
|
||||
});
|
||||
|
||||
it('FE-WSEVT-RESERV-002: reservation:created is idempotent — no duplicate if same ID', () => {
|
||||
seedData();
|
||||
const duplicate = buildReservation({ id: 1, name: 'Hotel Paris Dup' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'reservation:created', reservation: duplicate });
|
||||
const { reservations } = useTripStore.getState();
|
||||
expect(reservations).toHaveLength(1);
|
||||
expect(reservations[0].name).toBe('Hotel Paris');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-RESERV-003: reservation:updated replaces reservation in array', () => {
|
||||
seedData();
|
||||
const updated = buildReservation({ id: 1, name: 'Hotel Lyon' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'reservation:updated', reservation: updated });
|
||||
const { reservations } = useTripStore.getState();
|
||||
expect(reservations[0].name).toBe('Hotel Lyon');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-RESERV-004: reservation:deleted removes reservation by ID', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'reservation:deleted', reservationId: 1 });
|
||||
const { reservations } = useTripStore.getState();
|
||||
expect(reservations).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('FE-WSEVT-RESERV-005: reservation:created ordering — newest is first', () => {
|
||||
seedData();
|
||||
const r2 = buildReservation({ id: 2, name: 'Second' });
|
||||
const r3 = buildReservation({ id: 3, name: 'Third' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'reservation:created', reservation: r2 });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'reservation:created', reservation: r3 });
|
||||
const { reservations } = useTripStore.getState();
|
||||
expect(reservations[0].id).toBe(3);
|
||||
expect(reservations[1].id).toBe(2);
|
||||
expect(reservations[2].id).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildTodoItem } from '../../helpers/factories';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > todo', () => {
|
||||
const seedData = () => {
|
||||
useTripStore.setState({
|
||||
todoItems: [buildTodoItem({ id: 1, name: 'Book flights' })],
|
||||
});
|
||||
};
|
||||
|
||||
it('FE-WSEVT-TODO-001: todo:created adds item to todoItems', () => {
|
||||
seedData();
|
||||
const newItem = buildTodoItem({ id: 99, name: 'Pack bags' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'todo:created', item: newItem });
|
||||
const { todoItems } = useTripStore.getState();
|
||||
expect(todoItems).toHaveLength(2);
|
||||
expect(todoItems.find(i => i.id === 99)).toBeDefined();
|
||||
});
|
||||
|
||||
it('FE-WSEVT-TODO-002: todo:created is idempotent — no duplicate if same ID', () => {
|
||||
seedData();
|
||||
const duplicate = buildTodoItem({ id: 1, name: 'Book flights duplicate' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'todo:created', item: duplicate });
|
||||
const { todoItems } = useTripStore.getState();
|
||||
expect(todoItems).toHaveLength(1);
|
||||
expect(todoItems[0].name).toBe('Book flights');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-TODO-003: todo:updated replaces item in array', () => {
|
||||
seedData();
|
||||
const updated = buildTodoItem({ id: 1, name: 'Book round-trip flights' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'todo:updated', item: updated });
|
||||
const { todoItems } = useTripStore.getState();
|
||||
expect(todoItems[0].name).toBe('Book round-trip flights');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-TODO-004: todo:deleted removes item by ID', () => {
|
||||
seedData();
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'todo:deleted', itemId: 1 });
|
||||
const { todoItems } = useTripStore.getState();
|
||||
expect(todoItems).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTripStore } from '../../../src/store/tripStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { buildTrip, buildPlace } from '../../helpers/factories';
|
||||
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
});
|
||||
|
||||
describe('remoteEventHandler > trip', () => {
|
||||
it('FE-WSEVT-TRIP-001: trip:updated replaces trip in state', () => {
|
||||
const originalTrip = buildTrip({ id: 1, name: 'Paris Trip' });
|
||||
useTripStore.setState({ trip: originalTrip });
|
||||
const updatedTrip = buildTrip({ id: 1, name: 'Paris & Lyon Trip' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'trip:updated', trip: updatedTrip });
|
||||
const { trip } = useTripStore.getState();
|
||||
expect(trip?.name).toBe('Paris & Lyon Trip');
|
||||
});
|
||||
|
||||
it('FE-WSEVT-TRIP-002: trip:updated does not affect other state fields', () => {
|
||||
const existingPlace = buildPlace({ id: 55, name: 'Eiffel Tower' });
|
||||
useTripStore.setState({
|
||||
trip: buildTrip({ id: 1, name: 'Original' }),
|
||||
places: [existingPlace],
|
||||
});
|
||||
const updatedTrip = buildTrip({ id: 1, name: 'Updated' });
|
||||
useTripStore.getState().handleRemoteEvent({ type: 'trip:updated', trip: updatedTrip });
|
||||
const { places } = useTripStore.getState();
|
||||
expect(places).toHaveLength(1);
|
||||
expect(places[0].id).toBe(55);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user