/* eslint-disable no-restricted-globals */ import { expect } from '@jest/globals'; import { scenario } from '@testduet/given-when-then'; import { parse } from 'valibot'; import type { WebChatActivity } from '../../../types/WebChatActivity'; import { LocalIdSchema, type LocalId } from './property/LocalId'; import { type Activity, type ActivityMapEntry, type LivestreamSessionId, type LivestreamSessionMapEntry, type LivestreamSessionMapEntryActivityEntry, type SortedChatHistory, type SortedChatHistoryEntry } from './types'; import upsert, { INITIAL_STATE } from './upsert'; function activityToExpectation(activity: Activity, expectedPosition: number = expect.any(Number) as any): Activity { return { ...activity, channelData: { ...activity.channelData, 'webchat:internal:position': expectedPosition } as any }; } function buildActivity( activity: | { channelData: | { streamId: string; streamSequence?: never; streamType: 'final'; } | undefined; id: string; text: string; timestamp: string; type: 'message'; } | { channelData: | { streamId: string; streamSequence: number; streamType: 'informative' | 'streaming'; } | { streamId?: never; streamSequence: 1; streamType: 'informative' | 'streaming'; } | undefined; id: string; text: string; timestamp: string; type: 'typing'; } ): WebChatActivity { const { id } = activity; return { from: { id: 'bot', role: 'bot' }, ...activity, channelData: { 'webchat:internal:local-id': parse(LocalIdSchema, `_:${id}`), 'webchat:internal:position': 0, 'webchat:send-status': undefined, ...activity.channelData } } as any; } scenario('upserting a livestream session', bdd => { const activity1 = buildActivity({ channelData: { streamSequence: 1, streamType: 'streaming' }, id: 'a-00001', text: 'A quick', timestamp: new Date(1_000).toISOString(), type: 'typing' }); const activity2 = buildActivity({ channelData: { streamId: 'a-00001', streamSequence: 3, // In reversed order. streamType: 'streaming' }, id: 'a-00002', text: 'A quick brown fox jumped over', timestamp: new Date(3_000).toISOString(), type: 'typing' }); const activity3 = buildActivity({ channelData: { streamId: 'a-00001', streamSequence: 2, streamType: 'streaming' }, id: 'a-00003', text: 'A quick brown fox', timestamp: new Date(2_000).toISOString(), type: 'typing' }); const activity4 = buildActivity({ channelData: { streamId: 'a-00001', streamType: 'final' }, id: 'a-00004', text: 'A quick brown fox jumped over the lazy dogs.', timestamp: new Date(4_000).toISOString(), type: 'message' }); bdd .given('an initial state', () => INITIAL_STATE) .when('upserted', state => upsert({ Date }, state, activity1)) .then('should have added to `activityMap`', (_, state) => { expect(state.activityMap).toEqual( new Map([ [ '_:a-00001' as LocalId, { activity: activityToExpectation(activity1), activityLocalId: '_:a-00001' as LocalId, logicalTimestamp: 1_000, type: 'activity' } ] ]) ); }) .and('should have added to `livestreamSessions`', (_, state) => { expect(state.livestreamSessionMap).toEqual( new Map([ [ 'a-00001' as LivestreamSessionId, { activities: [ { activityLocalId: '_:a-00001' as LocalId, logicalTimestamp: 1_000, sequenceNumber: 1, type: 'activity' } satisfies LivestreamSessionMapEntryActivityEntry ], finalized: false, logicalTimestamp: 1_000 } ] ]) ); }) .and('should have added to `sortedChatHistoryList`', (_, state) => { expect(state.sortedChatHistoryList).toEqual([ { livestreamSessionId: 'a-00001' as LivestreamSessionId, logicalTimestamp: 1_000, type: 'livestream session' } ] satisfies SortedChatHistory); }) .and('`sortedActivities` should match snapshot', (_, state) => { expect(state.sortedActivities).toEqual([activityToExpectation(activity1, 1_000)]); }) .when('the second activity is upserted', (_, state) => upsert({ Date }, state, activity2)) .then('should have added to `activityMap`', (_, state) => { expect(state.activityMap).toEqual( new Map([ [ '_:a-00001' as LocalId, { activity: activityToExpectation(activity1), activityLocalId: '_:a-00001' as LocalId, logicalTimestamp: 1_000, type: 'activity' } satisfies ActivityMapEntry ], [ '_:a-00002' as LocalId, { activity: activityToExpectation(activity2), activityLocalId: '_:a-00002' as LocalId, logicalTimestamp: 3_000, type: 'activity' } satisfies ActivityMapEntry ] ]) ); }) .and('should have added to `livestreamSessions`', (_, state) => { expect(state.livestreamSessionMap).toEqual( new Map([ [ 'a-00001' as LivestreamSessionId, { activities: [ { activityLocalId: '_:a-00001' as LocalId, logicalTimestamp: 1_000, sequenceNumber: 1, type: 'activity' } satisfies LivestreamSessionMapEntryActivityEntry, { activityLocalId: '_:a-00002' as LocalId, logicalTimestamp: 3_000, sequenceNumber: 3, type: 'activity' } satisfies LivestreamSessionMapEntryActivityEntry ], finalized: false, logicalTimestamp: 1_000 } ] ]) ); }) .and('should not modify `sortedChatHistoryList`', (_, state) => { expect(state.sortedChatHistoryList).toEqual([ { livestreamSessionId: 'a-00001' as LivestreamSessionId, logicalTimestamp: 1_000, type: 'livestream session' } satisfies SortedChatHistoryEntry ]); }) .and('`sortedActivities` should match snapshot', (_, state) => { expect(state.sortedActivities).toEqual([ activityToExpectation(activity1, 1_000), activityToExpectation(activity2, 2_000) ]); }) .when('the third activity is upserted', (_, state) => upsert({ Date }, state, activity3)) .then('should have added to `activityMap`', (_, state) => { expect(state.activityMap).toEqual( new Map([ [ '_:a-00001' as LocalId, { activity: activityToExpectation(activity1), activityLocalId: '_:a-00001' as LocalId, logicalTimestamp: 1_000, type: 'activity' } ], [ '_:a-00003' as LocalId, { activity: activityToExpectation(activity3), activityLocalId: '_:a-00003' as LocalId, logicalTimestamp: 2_000, type: 'activity' } ], [ '_:a-00002' as LocalId, { activity: activityToExpectation(activity2), activityLocalId: '_:a-00002' as LocalId, logicalTimestamp: 3_000, type: 'activity' } ] ]) ); }) .and('should have added to `livestreamSessions`', (_, state) => { expect(state.livestreamSessionMap).toEqual( new Map([ [ 'a-00001' as LivestreamSessionId, { activities: [ { activityLocalId: '_:a-00001' as LocalId, logicalTimestamp: 1_000, sequenceNumber: 1, type: 'activity' } satisfies LivestreamSessionMapEntryActivityEntry, { activityLocalId: '_:a-00003' as LocalId, logicalTimestamp: 2_000, sequenceNumber: 2, type: 'activity' } satisfies LivestreamSessionMapEntryActivityEntry, { activityLocalId: '_:a-00002' as LocalId, logicalTimestamp: 3_000, sequenceNumber: 3, type: 'activity' } satisfies LivestreamSessionMapEntryActivityEntry ], finalized: false, logicalTimestamp: 1_000 } ] ]) ); }) .and('should update `sortedChatHistoryList` with updated timestamp', (_, state) => { expect(state.sortedChatHistoryList).toEqual([ { livestreamSessionId: 'a-00001' as LivestreamSessionId, logicalTimestamp: 1_000, type: 'livestream session' } satisfies SortedChatHistoryEntry ]); }) .and('`sortedActivities` should match snapshot', (_, state) => { expect(state.sortedActivities).toEqual([ activityToExpectation(activity1, 1_000), activityToExpectation(activity3, 1_001), activityToExpectation(activity2, 2_000) ]); }) .when('the fourth and final activity is upserted', (_, state) => upsert({ Date }, state, activity4)) .then('should have added to `activityMap`', (_, state) => { expect(state.activityMap).toEqual( new Map([ [ '_:a-00001' as LocalId, { activity: activityToExpectation(activity1), activityLocalId: '_:a-00001' as LocalId, logicalTimestamp: 1_000, type: 'activity' } ], [ '_:a-00003' as LocalId, { activity: activityToExpectation(activity3), activityLocalId: '_:a-00003' as LocalId, logicalTimestamp: 2_000, type: 'activity' } ], [ '_:a-00002' as LocalId, { activity: activityToExpectation(activity2), activityLocalId: '_:a-00002' as LocalId, logicalTimestamp: 3_000, type: 'activity' } ], [ '_:a-00004' as LocalId, { activity: activityToExpectation(activity4), activityLocalId: '_:a-00004' as LocalId, logicalTimestamp: 4_000, type: 'activity' } ] ]) ); }) .and('should have added to `livestreamSessions`', (_, state) => { expect(state.livestreamSessionMap).toEqual( new Map([ [ 'a-00001' as LivestreamSessionId, { activities: [ { activityLocalId: '_:a-00001' as LocalId, logicalTimestamp: 1_000, sequenceNumber: 1, type: 'activity' } satisfies LivestreamSessionMapEntryActivityEntry, { activityLocalId: '_:a-00003' as LocalId, logicalTimestamp: 2_000, sequenceNumber: 2, type: 'activity' } satisfies LivestreamSessionMapEntryActivityEntry, { activityLocalId: '_:a-00002' as LocalId, logicalTimestamp: 3_000, sequenceNumber: 3, type: 'activity' } satisfies LivestreamSessionMapEntryActivityEntry, { activityLocalId: '_:a-00004' as LocalId, logicalTimestamp: 4_000, sequenceNumber: Infinity, type: 'activity' } satisfies LivestreamSessionMapEntryActivityEntry ], finalized: true, logicalTimestamp: 4_000 } ] ]) ); }) .and('should update `sortedChatHistoryList` with updated timestamp', (_, state) => { expect(state.sortedChatHistoryList).toEqual([ { livestreamSessionId: 'a-00001' as LivestreamSessionId, logicalTimestamp: 4_000, type: 'livestream session' } satisfies SortedChatHistoryEntry ]); }) .and('`sortedActivities` should match snapshot', (_, state) => { expect(state.sortedActivities).toEqual([activityToExpectation(activity4, 1_000)]); }); });