UNPKG

@towns-protocol/sdk

Version:

For more details, visit the following resources:

343 lines 14.3 kB
/** * @group core */ import { TimelinesView } from '../../views/streams/timelines'; import { ConversationBuilder } from './helpers/ConversationBuilder'; function describeEvent(event) { return `${event.eventId} ${event.fallbackContent}`; } function describeEvents(events) { return events.map((e) => describeEvent(e)); } function describeThreads(threads) { if (!threads) { return undefined; } return Object.entries(threads).reduce((acc, [threadParentId, thread]) => { acc[threadParentId] = thread.map((e) => describeEvent(e)); return acc; }, {}); } function describeTips(tipsMap) { if (!tipsMap) { return undefined; } const returnValue = {}; for (const [eventId, tips] of Object.entries(tipsMap)) { const described = tips.map((t) => `${t.eventId} amount: ${t.content.tip.event?.amount.toString() ?? '??'} from: ${t.content.fromUserId} to: ${t.content.toUserId}`); returnValue[eventId] = described; } return returnValue; } function execute(timelinesView, userId, events, expected) { const setState = timelinesView.setState; const channelId = 'channel1'; // { // test appending events (normal flow when syncing channels) setState.appendEvents(events, userId, channelId); // get the timeline store interface const { timelines: timelinesAppended, threads: threadsAppended, threadsStats: threadStatsAppended, tips: tipsAppended, } = timelinesView.value; // assert the timeline events are in the correct order expect(describeEvents(timelinesAppended[channelId])).toEqual(expected.timeline); // check threads if (expected.threads) { expect(describeThreads(threadsAppended[channelId])).toEqual(expected.threads); } // thread stats if (expected.threadStats) { expect(threadStatsAppended[channelId]).toEqual(expected.threadStats); } // check tips if (expected.tips) { expect(describeTips(tipsAppended[channelId])).toEqual(expected.tips); } } // clear the timeline setState.reset([channelId]); // { // test prepending events (normal flow when paginating) setState.prependEvents(events, userId, channelId); // get the timeline store interface const { timelines: timelinesPrepended, threads: threadsPrepended, threadsStats: threadStatsPrepended, tips: tipsPrepended, } = timelinesView.value; // assert the timeline events are in the correct order expect(describeEvents(timelinesPrepended[channelId])).toEqual(expected.timeline); // check threads if (expected.threads) { expect(describeThreads(threadsPrepended[channelId])).toEqual(expected.threads); } // thread stats if (expected.threadStats) { expect(threadStatsPrepended[channelId]).toEqual(expected.threadStats); } // check tips if (expected.tips) { expect(describeTips(tipsPrepended[channelId])).toEqual(expected.tips); } } } describe('UseTimelinesView', () => { const timelinesView = new TimelinesView('', undefined); beforeAll(() => { const { timelines } = timelinesView.value; const setState = timelinesView.setState; const roomIds = Object.keys(timelines); setState.reset(roomIds); }); afterEach(() => { const { timelines } = timelinesView.value; const setState = timelinesView.setState; const roomIds = Object.keys(timelines); setState.reset(roomIds); }); test('test send', () => { // events const events = new ConversationBuilder() .sendMessage({ from: 'alice', body: 'hi bob!' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: ['event0 alice: hi bob!'] }); }); test('test send and edit', () => { // events (use a custom id for the fist message so we can edit it) const events = new ConversationBuilder() .sendMessage({ id: 'MSG_0', from: 'alice', body: 'hi bob!' }) .editMessage({ edits: 'MSG_0', newBody: 'hi bob! (edited)' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: ['MSG_0 alice: hi bob! (edited)'] }); }); test('test send and edit with different sender', () => { // events (use a custom id for the fist message so we can edit it) const events = new ConversationBuilder() .sendMessage({ id: 'MSG_0', from: 'alice', body: 'hi bob!' }) .editMessage({ edits: 'MSG_0', newBody: 'alice sucks! (edited)', senderId: 'bob' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: ['MSG_0 alice: hi bob!'] }); }); test('test send and multiple edits', () => { // events (use a custom id for the fist message so we can edit it) const events = new ConversationBuilder() .sendMessage({ id: 'MSG_0', from: 'alice', body: 'hi bob!' }) .editMessage({ edits: 'MSG_0', newBody: 'hi bob! (edited)' }) .editMessage({ edits: 'MSG_0', newBody: 'hi bob! (edited2)' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: ['MSG_0 alice: hi bob! (edited2)'] }); }); test('test send and multiple edits out of order', () => { // events (use a custom id for the fist message so we can edit it) const events = new ConversationBuilder() .sendMessage({ id: 'MSG_0', from: 'alice', body: 'hi bob!' }) .editMessage({ edits: 'MSG_0', newBody: 'hi bob! (edited)' }) .editMessage({ edits: 'MSG_0', newBody: 'hi bob! (edited2)' }) .getEvents(); // results const ex = events[events.length - 1]; events[events.length - 1] = events[events.length - 2]; events[events.length - 2] = ex; execute(timelinesView, 'alice', events, { timeline: ['MSG_0 alice: hi bob! (edited2)'] }); }); test('test threads and thread stats', () => { // events const events = new ConversationBuilder() .sendMessage({ id: 'MSG_0', from: 'alice', body: 'hi bob!' }) .sendMessage({ threadId: 'MSG_0', from: 'bob', body: 'hi alice!' }) .sendMessage({ threadId: 'MSG_0', from: 'bob', body: 'Hows it going?' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: [ 'MSG_0 alice: hi bob!', 'event1 bob: hi alice!', 'event2 bob: Hows it going?', ], threads: { MSG_0: ['event1 bob: hi alice!', 'event2 bob: Hows it going?'], }, threadStats: { MSG_0: { replyEventIds: new Set(['event1', 'event2']), userIds: new Set(['bob']), latestTs: events[2].createdAtEpochMs, parentId: events[0].eventId, parentEvent: events[0], parentMessageContent: events[0].content, isParticipating: true, }, }, }); }); test('test tip', () => { // ids must be hex const msgId_0 = '0x1234'; const msgId_1 = '0x1235'; const tipId_a = '0x1236'; const tipId_b = '0x1237'; const tipId_c = '0x1238'; // events const events = new ConversationBuilder() .sendMessage({ id: msgId_0, from: 'alice', body: 'hi bob!' }) .sendMessage({ id: msgId_1, from: 'bob', body: 'hi alice!' }) .sendTip({ tip: 10, ref: msgId_1, id: tipId_a, from: 'bob', to: 'alice' }) .sendTip({ tip: 10, ref: msgId_0, id: tipId_b, from: 'alice', to: 'bob' }) .sendTip({ tip: 10, ref: msgId_0, id: tipId_c, from: 'alice', to: 'bob' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: [ `${msgId_0} alice: hi bob!`, `${msgId_1} bob: hi alice!`, `${tipId_a} tip from: bob to: alice refEventId: ${msgId_1} amount: 10`, `${tipId_b} tip from: alice to: bob refEventId: ${msgId_0} amount: 10`, `${tipId_c} tip from: alice to: bob refEventId: ${msgId_0} amount: 10`, ], tips: { [msgId_1]: [`${tipId_a} amount: 10 from: bob to: alice`], [msgId_0]: [ `${tipId_b} amount: 10 from: alice to: bob`, `${tipId_c} amount: 10 from: alice to: bob`, ], }, }); }); test('test edit thread item', () => { // events const events = new ConversationBuilder() .sendMessage({ id: 'MSG_0', from: 'alice', body: 'hi bob!' }) .sendMessage({ id: 'THREAD_0', threadId: 'MSG_0', from: 'bob', body: 'hi alice!' }) .editMessage({ edits: 'THREAD_0', newBody: 'hi alice! (edited)' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: ['MSG_0 alice: hi bob!', 'THREAD_0 bob: hi alice! (edited)'], threads: { MSG_0: ['THREAD_0 bob: hi alice! (edited)'], }, threadStats: { MSG_0: { replyEventIds: new Set(['THREAD_0']), userIds: new Set(['bob']), latestTs: events[1].createdAtEpochMs, parentId: events[0].eventId, parentEvent: events[0], parentMessageContent: events[0].content, isParticipating: true, }, }, }); }); test('test redact thread item', () => { // events const events = new ConversationBuilder() .sendMessage({ id: 'MSG_0', from: 'alice', body: 'hi bob!' }) .sendMessage({ id: 'THREAD_0', threadId: 'MSG_0', from: 'bob', body: 'hi alice!' }) .redactMessage({ redacts: 'THREAD_0' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: [ 'MSG_0 alice: hi bob!', 'THREAD_0 ~Redacted~', 'event2 Redacts THREAD_0 adminRedaction: false', ], threads: { MSG_0: ['THREAD_0 ~Redacted~'], }, threadStats: { MSG_0: { replyEventIds: new Set([]), userIds: new Set([]), latestTs: events[1].createdAtEpochMs, parentId: events[0].eventId, parentEvent: events[0], parentMessageContent: events[0].content, isParticipating: false, }, }, }); }); test('test send, edit and redact', () => { // events const events = new ConversationBuilder() .sendMessage({ from: 'alice', body: 'hi bob!' }) .sendMessage({ id: 'MSG_1', from: 'bob', body: 'hi alice!', }) .sendMessage({ id: 'MSG_2', from: 'bob', body: 'this is banannas', }) .editMessage({ edits: 'MSG_1', newBody: 'hi alice! (edited)', }) .sendMessage({ from: 'alice', body: 'banannas? lol' }) .redactMessage({ redacts: 'MSG_2' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: [ 'event0 alice: hi bob!', 'MSG_1 bob: hi alice! (edited)', 'MSG_2 ~Redacted~', 'event4 alice: banannas? lol', 'event5 Redacts MSG_2 adminRedaction: false', ], }); }); test('test send and redact', () => { // events (use a custom id for the fist message so we can edit it) const events = new ConversationBuilder() .sendMessage({ id: 'MSG_0', from: 'alice', body: 'hi bob' }) .sendMessage({ id: 'MSG_1', from: 'alice', body: 'hi bob!' }) .redactMessage({ redacts: 'MSG_0' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: [ 'MSG_0 ~Redacted~', 'MSG_1 alice: hi bob!', 'event2 Redacts MSG_0 adminRedaction: false', ], }); }); test('test send and redact with different sender, redaction should be ignored', () => { // events (use a custom id for the fist message so we can edit it) const events = new ConversationBuilder() .sendMessage({ id: 'MSG_0', from: 'alice', body: 'hi bob' }) .sendMessage({ id: 'MSG_1', from: 'alice', body: 'hi bob!' }) .redactMessage({ redacts: 'MSG_0', senderId: 'bob' }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: [ 'MSG_0 alice: hi bob', 'MSG_1 alice: hi bob!', 'event2 Redacts MSG_0 adminRedaction: false', // the redaction action show up, but the message is not redacted ], }); }); test('test send and admin redact', () => { // events (use a custom id for the fist message so we can edit it) const events = new ConversationBuilder() .sendMessage({ id: 'MSG_0', from: 'alice', body: 'hi bob' }) .sendMessage({ id: 'MSG_1', from: 'alice', body: 'hi bob!' }) .redactMessage({ redacts: 'MSG_0', senderId: 'bob', isAdmin: true }) .getEvents(); // results execute(timelinesView, 'alice', events, { timeline: [ 'MSG_0 ~Redacted~', 'MSG_1 alice: hi bob!', 'event2 Redacts MSG_0 adminRedaction: true', ], }); }); }); //# sourceMappingURL=timelineStoreInterface.test.js.map