Spaces:
Sleeping
Sleeping
| 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'; | |
| import type { Assignment } from '../../../src/types'; | |
| 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-003b: a second assignment of an already-present place is NOT suppressed (H11)', () => { | |
| const place = buildPlace({ id: 55 }); | |
| useTripStore.setState({ | |
| days: [buildDay({ id: 10 })], | |
| // A committed (positive-id) assignment of place 55 already on the day. | |
| assignments: { '10': [buildAssignment({ id: 100, day_id: 10, place, place_id: place.id })] }, | |
| }); | |
| // A legitimately new, distinct assignment of the same place arrives. | |
| const second = buildAssignment({ id: 300, day_id: 10, place, place_id: place.id }); | |
| useTripStore.getState().handleRemoteEvent({ type: 'assignment:created', assignment: second }); | |
| const { assignments } = useTripStore.getState(); | |
| expect(assignments['10']).toHaveLength(2); | |
| expect(assignments['10'].map(a => a.id).sort((x, y) => x - y)).toEqual([100, 300]); | |
| }); | |
| it('FE-WSEVT-ASSIGN-003c: temp reconciliation replaces only the matching place, not a sibling temp (H11)', () => { | |
| const place55 = buildPlace({ id: 55 }); | |
| const place66 = buildPlace({ id: 66 }); | |
| useTripStore.setState({ | |
| days: [buildDay({ id: 10 })], | |
| assignments: { | |
| '10': [ | |
| buildAssignment({ id: -1, day_id: 10, place: place55, place_id: 55 }), | |
| buildAssignment({ id: -2, day_id: 10, place: place66, place_id: 66 }), | |
| ], | |
| }, | |
| }); | |
| const real = buildAssignment({ id: 500, day_id: 10, place: place55, place_id: 55 }); | |
| useTripStore.getState().handleRemoteEvent({ type: 'assignment:created', assignment: real }); | |
| const { assignments } = useTripStore.getState(); | |
| const ids = assignments['10'].map(a => a.id); | |
| expect(assignments['10']).toHaveLength(2); | |
| expect(ids).toContain(500); // temp 55 reconciled to real | |
| expect(ids).toContain(-2); // sibling temp 66 untouched | |
| expect(ids).not.toContain(-1); | |
| }); | |
| it('FE-WSEVT-ASSIGN-003d: place-less assignments do not collapse onto each other (H11)', () => { | |
| // Defensive: a malformed event lacking place data must not let the | |
| // `place?.id === placeId` reconciliation match undefined === undefined. | |
| const placeless = (id: number): Assignment => | |
| ({ ...buildAssignment({ id, day_id: 10 }), place: undefined, place_id: undefined } as unknown as Assignment); | |
| useTripStore.setState({ | |
| days: [buildDay({ id: 10 })], | |
| assignments: { '10': [placeless(-1)] }, | |
| }); | |
| useTripStore.getState().handleRemoteEvent({ type: 'assignment:created', assignment: placeless(700) }); | |
| const { assignments } = useTripStore.getState(); | |
| // No placeId → no reconcile; both survive as distinct rows (no collapse). | |
| expect(assignments['10']).toHaveLength(2); | |
| }); | |
| 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); | |
| }); | |
| }); | |