2026-07-10 01:08:34 -04:00
|
|
|
import { test } from 'node:test';
|
|
|
|
|
import assert from 'node:assert/strict';
|
|
|
|
|
import {
|
|
|
|
|
filterThreads,
|
|
|
|
|
sortThreads,
|
|
|
|
|
isThreadFilter,
|
|
|
|
|
isThreadSort,
|
|
|
|
|
ThreadSnapshot,
|
|
|
|
|
} from './threadList';
|
|
|
|
|
|
|
|
|
|
const t = (
|
|
|
|
|
id: string,
|
|
|
|
|
latestTs: number,
|
|
|
|
|
unread = 0,
|
|
|
|
|
participated = false,
|
|
|
|
|
): ThreadSnapshot => ({ id, latestTs, unread, participated });
|
|
|
|
|
|
|
|
|
|
test('filterThreads all returns every thread', () => {
|
|
|
|
|
const input = [t('a', 1, 0, false), t('b', 2, 3, true)];
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
filterThreads(input, 'all').map((x) => x.id),
|
|
|
|
|
['a', 'b'],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('filterThreads unread keeps only threads with unread > 0', () => {
|
|
|
|
|
const input = [t('a', 1, 0), t('b', 2, 2), t('c', 3, 0)];
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
filterThreads(input, 'unread').map((x) => x.id),
|
|
|
|
|
['b'],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('filterThreads participating keeps only participated threads', () => {
|
|
|
|
|
const input = [t('a', 1, 0, true), t('b', 2, 0, false), t('c', 3, 0, true)];
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
filterThreads(input, 'participating').map((x) => x.id),
|
|
|
|
|
['a', 'c'],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('sortThreads recent orders by latestTs descending', () => {
|
|
|
|
|
const input = [t('a', 100), t('b', 300), t('c', 200)];
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
sortThreads(input, 'recent').map((x) => x.id),
|
|
|
|
|
['b', 'c', 'a'],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('sortThreads oldest orders by latestTs ascending', () => {
|
|
|
|
|
const input = [t('a', 100), t('b', 300), t('c', 200)];
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
sortThreads(input, 'oldest').map((x) => x.id),
|
|
|
|
|
['a', 'c', 'b'],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('sortThreads breaks ties deterministically by id', () => {
|
|
|
|
|
const input = [t('z', 100), t('a', 100), t('m', 100)];
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
sortThreads(input, 'recent').map((x) => x.id),
|
|
|
|
|
['a', 'm', 'z'],
|
|
|
|
|
);
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
sortThreads(input, 'oldest').map((x) => x.id),
|
|
|
|
|
['a', 'm', 'z'],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-10 01:21:01 -04:00
|
|
|
test('filterThreads / sortThreads do not mutate input (all/participating branches)', () => {
|
|
|
|
|
const input = [t('a', 100, 1, true), t('b', 300, 0, false)];
|
2026-07-10 01:08:34 -04:00
|
|
|
const before = input.map((x) => x.id);
|
2026-07-10 01:21:01 -04:00
|
|
|
filterThreads(input, 'all');
|
|
|
|
|
filterThreads(input, 'participating');
|
2026-07-10 01:08:34 -04:00
|
|
|
filterThreads(input, 'unread');
|
2026-07-10 01:21:01 -04:00
|
|
|
sortThreads(input, 'recent');
|
2026-07-10 01:08:34 -04:00
|
|
|
sortThreads(input, 'oldest');
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
input.map((x) => x.id),
|
|
|
|
|
before,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-10 01:21:01 -04:00
|
|
|
test('filter → sort pipeline composes (unread + recent)', () => {
|
|
|
|
|
const input = [t('a', 100, 2), t('b', 300, 0), t('c', 200, 1)];
|
|
|
|
|
const out = sortThreads(filterThreads(input, 'unread'), 'recent');
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
out.map((x) => x.id),
|
|
|
|
|
['c', 'a'],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-10 01:08:34 -04:00
|
|
|
test('isThreadFilter / isThreadSort accept valid and reject junk', () => {
|
|
|
|
|
assert.equal(isThreadFilter('all'), true);
|
|
|
|
|
assert.equal(isThreadFilter('unread'), true);
|
|
|
|
|
assert.equal(isThreadFilter('participating'), true);
|
|
|
|
|
assert.equal(isThreadFilter('bogus'), false);
|
|
|
|
|
assert.equal(isThreadFilter(undefined), false);
|
|
|
|
|
assert.equal(isThreadSort('recent'), true);
|
|
|
|
|
assert.equal(isThreadSort('oldest'), true);
|
|
|
|
|
assert.equal(isThreadSort('nope'), false);
|
|
|
|
|
assert.equal(isThreadSort(3), false);
|
|
|
|
|
});
|