// Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; import { times } from 'lodash'; import { v4 as uuid } from 'uuid'; import { fromItemIndexToRow, fromRowToItemIndex, getEphemeralRows, getHeroRow, getLastSeenIndicatorRow, getRowCount, getTypingBubbleRow, } from '../../util/timelineUtil'; describe(' utilities', () => { const getItems = (count: number): Array => times(count, () => uuid()); describe('fromItemIndexToRow', () => { it('returns the same number under normal conditions', () => { times(5, index => { assert.strictEqual( fromItemIndexToRow(index, { haveOldest: false }), index ); }); }); it('adds 1 (for the hero row) if you have the oldest messages', () => { times(5, index => { assert.strictEqual( fromItemIndexToRow(index, { haveOldest: true }), index + 1 ); }); }); it('adds 1 (for the unread indicator) once crossing the unread indicator index', () => { const props = { haveOldest: false, oldestUnreadIndex: 2 }; [0, 1].forEach(index => { assert.strictEqual(fromItemIndexToRow(index, props), index); }); [2, 3, 4].forEach(index => { assert.strictEqual(fromItemIndexToRow(index, props), index + 1); }); }); it('can include the hero row and the unread indicator', () => { const props = { haveOldest: true, oldestUnreadIndex: 2 }; [0, 1].forEach(index => { assert.strictEqual(fromItemIndexToRow(index, props), index + 1); }); [2, 3, 4].forEach(index => { assert.strictEqual(fromItemIndexToRow(index, props), index + 2); }); }); }); describe('fromRowToItemIndex', () => { it('returns the item index under normal conditions', () => { const props = { haveOldest: false, items: getItems(5) }; times(5, row => { assert.strictEqual(fromRowToItemIndex(row, props), row); }); assert.isUndefined(fromRowToItemIndex(5, props)); }); it('handles the unread indicator', () => { const props = { haveOldest: false, items: getItems(4), oldestUnreadIndex: 2, }; [0, 1].forEach(row => { assert.strictEqual(fromRowToItemIndex(row, props), row); }); assert.isUndefined(fromRowToItemIndex(2, props)); [3, 4].forEach(row => { assert.strictEqual(fromRowToItemIndex(row, props), row - 1); }); assert.isUndefined(fromRowToItemIndex(5, props)); }); it('handles the hero row', () => { const props = { haveOldest: true, items: getItems(3) }; assert.isUndefined(fromRowToItemIndex(0, props)); [1, 2, 3].forEach(row => { assert.strictEqual(fromRowToItemIndex(row, props), row - 1); }); assert.isUndefined(fromRowToItemIndex(4, props)); }); it('handles the whole enchilada', () => { const props = { haveOldest: true, items: getItems(4), oldestUnreadIndex: 2, }; assert.isUndefined(fromRowToItemIndex(0, props)); [1, 2].forEach(row => { assert.strictEqual(fromRowToItemIndex(row, props), row - 1); }); assert.isUndefined(fromRowToItemIndex(3, props)); [4, 5].forEach(row => { assert.strictEqual(fromRowToItemIndex(row, props), row - 2); }); assert.isUndefined(fromRowToItemIndex(6, props)); }); }); describe('getRowCount', () => { it('returns 1 (for the hero row) if the conversation is empty', () => { assert.strictEqual(getRowCount({ haveOldest: true, items: [] }), 1); }); it('returns the number of items under normal conditions', () => { assert.strictEqual( getRowCount({ haveOldest: false, items: getItems(4) }), 4 ); }); it('adds 1 (for the hero row) if you have the oldest messages', () => { assert.strictEqual( getRowCount({ haveOldest: true, items: getItems(4) }), 5 ); }); it('adds 1 (for the unread indicator) if you have unread messages', () => { assert.strictEqual( getRowCount({ haveOldest: false, items: getItems(4), oldestUnreadIndex: 2, }), 5 ); }); it('adds 1 (for the typing contact) if you have unread messages', () => { assert.strictEqual( getRowCount({ haveOldest: false, items: getItems(4), typingContactId: uuid(), }), 5 ); }); it('can have the whole enchilada', () => { assert.strictEqual( getRowCount({ haveOldest: true, items: getItems(4), oldestUnreadIndex: 2, typingContactId: uuid(), }), 7 ); }); }); describe('getHeroRow', () => { it("returns undefined if there's no hero row", () => { assert.isUndefined(getHeroRow({ haveOldest: false })); }); it("returns 0 if there's a hero row", () => { assert.strictEqual(getHeroRow({ haveOldest: true }), 0); }); }); describe('getLastSeenIndicatorRow', () => { it('returns undefined with no unread messages', () => { assert.isUndefined(getLastSeenIndicatorRow({ haveOldest: false })); assert.isUndefined(getLastSeenIndicatorRow({ haveOldest: true })); }); it('returns the same number if the oldest messages are loaded', () => { [0, 1, 2].forEach(oldestUnreadIndex => { assert.strictEqual( getLastSeenIndicatorRow({ haveOldest: false, oldestUnreadIndex }), oldestUnreadIndex ); }); }); it("increases the number by 1 if there's a hero row", () => { [0, 1, 2].forEach(oldestUnreadIndex => { assert.strictEqual( getLastSeenIndicatorRow({ haveOldest: true, oldestUnreadIndex }), oldestUnreadIndex + 1 ); }); }); }); describe('getTypingBubbleRow', () => { it('returns undefined if nobody is typing', () => { assert.isUndefined( getTypingBubbleRow({ haveOldest: false, items: getItems(3) }) ); }); it('returns the last row if people are typing', () => { [ { haveOldest: true, items: [], typingContactId: uuid() }, { haveOldest: false, items: getItems(3), typingContactId: uuid() }, { haveOldest: true, items: getItems(3), typingContactId: uuid() }, { haveOldest: false, items: getItems(3), oldestUnreadIndex: 2, typingContactId: uuid(), }, { haveOldest: true, items: getItems(3), oldestUnreadIndex: 2, typingContactId: uuid(), }, ].forEach(props => { assert.strictEqual(getTypingBubbleRow(props), getRowCount(props) - 1); }); }); }); describe('getEphemeralRows', () => { function iterate(iterator: Iterator): Array { const result: Array = []; let iteration = iterator.next(); while (!iteration.done) { result.push(iteration.value); iteration = iterator.next(); } return result; } it('yields each row under normal conditions', () => { const result = getEphemeralRows({ haveOldest: false, items: ['a', 'b', 'c'], }); assert.deepStrictEqual(iterate(result), ['item:a', 'item:b', 'item:c']); }); it('yields a hero row if there is one', () => { const result = getEphemeralRows({ haveOldest: true, items: getItems(3) }); const iterated = iterate(result); assert.lengthOf(iterated, 4); assert.strictEqual(iterated[0], 'hero'); }); it('yields an unread indicator if there is one', () => { const result = getEphemeralRows({ haveOldest: false, items: getItems(3), oldestUnreadIndex: 2, }); const iterated = iterate(result); assert.lengthOf(iterated, 4); assert.strictEqual(iterated[2], 'oldest-unread'); }); it('yields a typing row if there is one', () => { const result = getEphemeralRows({ haveOldest: false, items: getItems(3), typingContactId: uuid(), }); const iterated = iterate(result); assert.lengthOf(iterated, 4); assert.strictEqual(iterated[3], 'typing-contact'); }); it('handles the whole enchilada', () => { const result = getEphemeralRows({ haveOldest: true, items: ['a', 'b', 'c'], oldestUnreadIndex: 2, typingContactId: uuid(), }); assert.deepStrictEqual(iterate(result), [ 'hero', 'item:a', 'item:b', 'oldest-unread', 'item:c', 'typing-contact', ]); }); }); });