import { describe, it, expect } from 'vitest'; import { BlockConflictResolvedError, mergeBlockOnConflict } from './blockConflictMerge'; import type { PersonMention, TranscriptionBlockData } from '$lib/types'; const baseBlock: TranscriptionBlockData = { id: 'b1', annotationId: 'a1', documentId: 'd1', text: 'old text from server', label: null, sortOrder: 0, version: 7, source: 'MANUAL', reviewed: false, mentionedPersons: [] }; describe('mergeBlockOnConflict', () => { it('keeps the local unsaved text — never overwritten by server text (B12b)', () => { const merged = mergeBlockOnConflict({ serverBlock: { ...baseBlock, text: 'server-side text' }, localText: 'transcriber unsaved input', localMentions: [] }); expect(merged.text).toBe('transcriber unsaved input'); }); it('takes server-side displayName for personIds present on both sides (rename win)', () => { const localMentions: PersonMention[] = [ { personId: 'p-aug', displayName: 'Auguste Raddatz' } // stale: server renamed her ]; const serverMentions: PersonMention[] = [ { personId: 'p-aug', displayName: 'Augusta Raddatz' } // post-rename ]; const merged = mergeBlockOnConflict({ serverBlock: { ...baseBlock, mentionedPersons: serverMentions }, localText: '@Augusta Raddatz', localMentions }); expect(merged.mentionedPersons).toEqual([ { personId: 'p-aug', displayName: 'Augusta Raddatz' } ]); }); it('keeps local-only mentions added since last save', () => { const localMentions: PersonMention[] = [ { personId: 'p-anna', displayName: 'Anna Schmidt' } // typed since last save ]; const merged = mergeBlockOnConflict({ serverBlock: { ...baseBlock, mentionedPersons: [] }, localText: '@Anna Schmidt', localMentions }); expect(merged.mentionedPersons).toContainEqual({ personId: 'p-anna', displayName: 'Anna Schmidt' }); }); it('returns a union of personIds when local and server diverge', () => { const localMentions: PersonMention[] = [{ personId: 'p-anna', displayName: 'Anna Schmidt' }]; const serverMentions: PersonMention[] = [{ personId: 'p-aug', displayName: 'Augusta Raddatz' }]; const merged = mergeBlockOnConflict({ serverBlock: { ...baseBlock, mentionedPersons: serverMentions }, localText: '@Augusta Raddatz und @Anna Schmidt', localMentions }); expect(merged.mentionedPersons).toHaveLength(2); expect(merged.mentionedPersons).toContainEqual({ personId: 'p-aug', displayName: 'Augusta Raddatz' }); expect(merged.mentionedPersons).toContainEqual({ personId: 'p-anna', displayName: 'Anna Schmidt' }); }); it('carries server version forward so the next save sends the latest revision', () => { const merged = mergeBlockOnConflict({ serverBlock: { ...baseBlock, version: 42 }, localText: 'x', localMentions: [] }); expect(merged.version).toBe(42); }); it('carries server-only mention array through when local has none', () => { const merged = mergeBlockOnConflict({ serverBlock: { ...baseBlock, mentionedPersons: [ { personId: 'p-aug', displayName: 'Augusta Raddatz' }, { personId: 'p-anna', displayName: 'Anna Schmidt' } ] }, localText: 'x', localMentions: [] }); expect(merged.mentionedPersons).toHaveLength(2); }); it('carries other server fields (sortOrder, reviewed, updatedAt) forward', () => { const merged = mergeBlockOnConflict({ serverBlock: { ...baseBlock, sortOrder: 9, reviewed: true, updatedAt: '2026-04-29T10:00:00Z' }, localText: 'x', localMentions: [] }); expect(merged.sortOrder).toBe(9); expect(merged.reviewed).toBe(true); expect(merged.updatedAt).toBe('2026-04-29T10:00:00Z'); }); }); describe('BlockConflictResolvedError', () => { it('is an Error with code = CONFLICT_RESOLVED', () => { const err = new BlockConflictResolvedError('block-1'); expect(err).toBeInstanceOf(Error); expect(err.code).toBe('CONFLICT_RESOLVED'); expect(err.name).toBe('BlockConflictResolvedError'); expect(err.message).toContain('block-1'); }); });