import { describe, it, expect } from 'vitest'; import type { Message } from '../../../../shared-kernel'; import { buildMessageRevision, materializeMessageFromRevision, revisionBeatsMessage } from './message-revision.builder.rules'; function createMessage(overrides: Partial = {}): Message { return { id: 'message-1', roomId: 'room-1', senderId: 'user-1', senderName: 'User 1', content: 'hello', timestamp: 1_000, reactions: [], isDeleted: false, ...overrides }; } describe('message-revision.builder.rules', () => { it('builds create revisions at revision zero', async () => { const message = createMessage(); const revision = await buildMessageRevision({ message, type: 'create', actorId: 'user-1', editedAt: 1_000 }); expect(revision.revision).toBe(0); expect(revision.headHash).toMatch(/^[a-f0-9]{64}$/); expect(revision.type).toBe('create'); }); it('increments revision for author edits', async () => { const message = createMessage({ revision: 0, headHash: 'abc' }); const revision = await buildMessageRevision({ message, type: 'author-edit', actorId: 'user-1', content: 'edited', editedAt: 2_000, prevRevisionHash: 'abc' }); expect(revision.revision).toBe(1); expect(revision.prevRevisionHash).toBe('abc'); expect(revision.content).toBe('edited'); }); it('materializes create revisions without an editedAt label timestamp', async () => { const revision = await buildMessageRevision({ message: createMessage(), type: 'create', actorId: 'user-1', editedAt: 1_000 }); const materialized = materializeMessageFromRevision(null, revision); expect(materialized.timestamp).toBe(1_000); expect(materialized.editedAt).toBeUndefined(); expect(materialized.revision).toBe(0); }); it('materializes message state from an edit revision', async () => { const revision = await buildMessageRevision({ message: createMessage(), type: 'author-edit', actorId: 'user-1', content: 'edited', editedAt: 2_000 }); const materialized = materializeMessageFromRevision(createMessage(), revision); expect(materialized.revision).toBe(1); expect(materialized.content).toBe('edited'); expect(materialized.editedAt).toBe(2_000); expect(materialized.headHash).toBe(revision.headHash); }); it('detects when a revision should replace local state', async () => { const local = materializeMessageFromRevision( createMessage({ revision: 0, headHash: 'old' }), await buildMessageRevision({ message: createMessage(), type: 'create', actorId: 'user-1', editedAt: 1_000 }) ); const incoming = await buildMessageRevision({ message: createMessage({ revision: 0, headHash: local.headHash }), type: 'author-edit', actorId: 'user-1', content: 'edited', editedAt: 2_000, prevRevisionHash: local.headHash ?? '' }); expect(revisionBeatsMessage(incoming, local)).toBe(true); }); });