import { describe, it, expect, beforeEach } from 'vitest' import { useSoloStore } from '@/stores/solo-store' import type { RealtimeEvent } from '@/stores/solo-store' import type { SoloTask } from '@/components/solo/solo-board' const baseTask = (id: string, overrides: Partial = {}): SoloTask => ({ id, title: `Task ${id}`, description: null, implementation_plan: null, priority: 1, sort_order: 1, status: 'TO_DO', verify_only: false, story_id: 'story-1', story_code: 'ST-100', story_title: 'Original Story', task_code: 'ST-100.1', ...overrides, }) const taskEvent = (overrides: Partial): RealtimeEvent => ({ op: 'U', entity: 'task', id: 'task-1', story_id: 'story-1', product_id: 'prod-1', sprint_id: 'sprint-1', assignee_id: 'user-1', ...overrides, }) const storyEvent = (overrides: Partial): RealtimeEvent => ({ op: 'U', entity: 'story', id: 'story-1', product_id: 'prod-1', sprint_id: 'sprint-1', assignee_id: 'user-1', ...overrides, }) describe('solo-store realtime', () => { beforeEach(() => { useSoloStore.setState({ tasks: {}, pendingOps: new Set() }) }) it('applies a task status update', () => { useSoloStore.getState().initTasks([baseTask('task-1', { status: 'TO_DO' })]) useSoloStore.getState().handleRealtimeEvent(taskEvent({ task_status: 'IN_PROGRESS' })) expect(useSoloStore.getState().tasks['task-1'].status).toBe('IN_PROGRESS') }) it('skips a task update when pendingOps has the id', () => { useSoloStore.getState().initTasks([baseTask('task-1', { status: 'TO_DO' })]) useSoloStore.getState().markPending('task-1') useSoloStore.getState().handleRealtimeEvent(taskEvent({ task_status: 'DONE' })) expect(useSoloStore.getState().tasks['task-1'].status).toBe('TO_DO') }) it('applies the update again once pendingOps is cleared', () => { useSoloStore.getState().initTasks([baseTask('task-1', { status: 'TO_DO' })]) useSoloStore.getState().markPending('task-1') useSoloStore.getState().handleRealtimeEvent(taskEvent({ task_status: 'DONE' })) expect(useSoloStore.getState().tasks['task-1'].status).toBe('TO_DO') useSoloStore.getState().clearPending('task-1') useSoloStore.getState().handleRealtimeEvent(taskEvent({ task_status: 'DONE' })) expect(useSoloStore.getState().tasks['task-1'].status).toBe('DONE') }) it('removes a task on D op', () => { useSoloStore.getState().initTasks([baseTask('task-1'), baseTask('task-2')]) useSoloStore.getState().handleRealtimeEvent(taskEvent({ id: 'task-1', op: 'D' })) expect(useSoloStore.getState().tasks['task-1']).toBeUndefined() expect(useSoloStore.getState().tasks['task-2']).toBeDefined() }) it('ignores task INSERT/UPDATE for tasks not in the store', () => { useSoloStore.getState().initTasks([baseTask('task-1')]) useSoloStore.getState().handleRealtimeEvent(taskEvent({ id: 'task-other', task_status: 'DONE' })) expect(Object.keys(useSoloStore.getState().tasks)).toEqual(['task-1']) }) it('updates story_title/code on all child tasks via story UPDATE', () => { useSoloStore.getState().initTasks([ baseTask('task-1', { story_id: 'story-1' }), baseTask('task-2', { story_id: 'story-1' }), baseTask('task-3', { story_id: 'story-other', story_title: 'Other' }), ]) useSoloStore.getState().handleRealtimeEvent( storyEvent({ story_title: 'Renamed Story', story_code: 'ST-100B' }), ) expect(useSoloStore.getState().tasks['task-1'].story_title).toBe('Renamed Story') expect(useSoloStore.getState().tasks['task-1'].story_code).toBe('ST-100B') expect(useSoloStore.getState().tasks['task-2'].story_title).toBe('Renamed Story') expect(useSoloStore.getState().tasks['task-3'].story_title).toBe('Other') }) it('removes all tasks of a story on story DELETE', () => { useSoloStore.getState().initTasks([ baseTask('task-1', { story_id: 'story-1' }), baseTask('task-2', { story_id: 'story-1' }), baseTask('task-3', { story_id: 'story-other' }), ]) useSoloStore.getState().handleRealtimeEvent(storyEvent({ op: 'D' })) expect(useSoloStore.getState().tasks['task-1']).toBeUndefined() expect(useSoloStore.getState().tasks['task-2']).toBeUndefined() expect(useSoloStore.getState().tasks['task-3']).toBeDefined() }) })