CI / Build & Quality Checks (pull_request) Successful in 2m57s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 14s
CI / Playwright smoke (e2e) (pull_request) Successful in 14m21s
Server search also returns the m.replace events, so one message edited twice came back as three rows. Fold edits into their message: one row at the best-ranked position, showing the latest m.new_content and pointing at the original event so Open jumps to the real message. Fixes #288 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
157 lines
5.2 KiB
TypeScript
157 lines
5.2 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
foldEdits,
|
|
filterGroupsByDateRange,
|
|
filterGroupsByMsgType,
|
|
filterGroupsByPinned,
|
|
ResultGroup,
|
|
} from './useMessageSearch';
|
|
|
|
// Minimal ResultGroup/ResultItem fixtures — only the fields the filters read
|
|
// (event.content.msgtype, event.event_id, group.roomId).
|
|
const item = (msgtype: string | undefined, eventId: string) => ({
|
|
rank: 1,
|
|
event: { event_id: eventId, content: msgtype === undefined ? {} : { msgtype } },
|
|
context: {},
|
|
});
|
|
const tsItem = (eventId: string, ts: number) => ({
|
|
rank: 1,
|
|
event: { event_id: eventId, origin_server_ts: ts, content: {} },
|
|
context: {},
|
|
});
|
|
const mkGroups = (
|
|
...groups: { roomId: string; items: ReturnType<typeof item>[] }[]
|
|
): ResultGroup[] => groups as unknown as ResultGroup[];
|
|
|
|
test('filterGroupsByMsgType: empty filter returns groups unchanged', () => {
|
|
const groups = mkGroups({ roomId: '!r1', items: [item('m.text', '$1'), item('m.image', '$2')] });
|
|
assert.equal(filterGroupsByMsgType(groups, []), groups);
|
|
});
|
|
|
|
test('filterGroupsByMsgType: keeps only matching msgtypes (union)', () => {
|
|
const groups = mkGroups({
|
|
roomId: '!r1',
|
|
items: [item('m.text', '$1'), item('m.image', '$2'), item('m.file', '$3')],
|
|
});
|
|
const out = filterGroupsByMsgType(groups, ['m.image', 'm.file']);
|
|
assert.equal(out.length, 1);
|
|
assert.deepEqual(
|
|
out[0].items.map((i) => i.event.event_id),
|
|
['$2', '$3'],
|
|
);
|
|
});
|
|
|
|
test('filterGroupsByMsgType: drops groups left empty', () => {
|
|
const groups = mkGroups(
|
|
{ roomId: '!r1', items: [item('m.text', '$1')] },
|
|
{ roomId: '!r2', items: [item('m.image', '$2')] },
|
|
);
|
|
const out = filterGroupsByMsgType(groups, ['m.image']);
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].roomId, '!r2');
|
|
});
|
|
|
|
test('filterGroupsByMsgType: ignores items with a non-string msgtype', () => {
|
|
const groups = mkGroups({ roomId: '!r1', items: [item(undefined, '$1'), item('m.video', '$2')] });
|
|
const out = filterGroupsByMsgType(groups, ['m.video']);
|
|
assert.equal(out[0].items.length, 1);
|
|
assert.equal(out[0].items[0].event.event_id, '$2');
|
|
});
|
|
|
|
test('filterGroupsByDateRange: no bounds returns groups unchanged', () => {
|
|
const groups = mkGroups({ roomId: '!r1', items: [tsItem('$1', 100)] });
|
|
assert.equal(filterGroupsByDateRange(groups, undefined, undefined), groups);
|
|
});
|
|
|
|
test('filterGroupsByDateRange: keeps only items within an inclusive range', () => {
|
|
const groups = mkGroups({
|
|
roomId: '!r1',
|
|
items: [tsItem('$1', 50), tsItem('$2', 100), tsItem('$3', 150), tsItem('$4', 200)],
|
|
});
|
|
const out = filterGroupsByDateRange(groups, 100, 150);
|
|
assert.deepEqual(
|
|
out[0].items.map((i) => i.event.event_id),
|
|
['$2', '$3'],
|
|
);
|
|
});
|
|
|
|
test('filterGroupsByDateRange: drops groups left empty and supports one-sided bounds', () => {
|
|
const groups = mkGroups(
|
|
{ roomId: '!r1', items: [tsItem('$1', 50)] },
|
|
{ roomId: '!r2', items: [tsItem('$2', 500)] },
|
|
);
|
|
const out = filterGroupsByDateRange(groups, 100, undefined);
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].roomId, '!r2');
|
|
});
|
|
|
|
test('filterGroupsByPinned: disabled returns groups unchanged', () => {
|
|
const groups = mkGroups({ roomId: '!r1', items: [item('m.text', '$1')] });
|
|
assert.equal(
|
|
filterGroupsByPinned(groups, false, () => false),
|
|
groups,
|
|
);
|
|
});
|
|
|
|
test('filterGroupsByPinned: keeps pinned items and drops empty groups', () => {
|
|
const groups = mkGroups(
|
|
{ roomId: '!r1', items: [item('m.text', '$1'), item('m.text', '$2')] },
|
|
{ roomId: '!r2', items: [item('m.text', '$3')] },
|
|
);
|
|
const pinned = new Set(['!r1/$2']);
|
|
const out = filterGroupsByPinned(groups, true, (roomId, eventId) =>
|
|
pinned.has(`${roomId}/${eventId}`),
|
|
);
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].roomId, '!r1');
|
|
assert.deepEqual(
|
|
out[0].items.map((i) => i.event.event_id),
|
|
['$2'],
|
|
);
|
|
});
|
|
|
|
const sr = (eventId: string, ts: number, content: Record<string, unknown>) =>
|
|
({
|
|
rank: 1,
|
|
result: {
|
|
event_id: eventId,
|
|
room_id: '!r:x',
|
|
origin_server_ts: ts,
|
|
type: 'm.room.message',
|
|
content,
|
|
},
|
|
}) as unknown as Parameters<typeof foldEdits>[0][number];
|
|
|
|
test('foldEdits: one row per message, showing the latest edit (#288)', () => {
|
|
const edit = (id: string, ts: number, body: string) =>
|
|
sr(id, ts, {
|
|
body: `* ${body}`,
|
|
'm.new_content': { body },
|
|
'm.relates_to': { rel_type: 'm.replace', event_id: '$orig' },
|
|
});
|
|
const out = foldEdits([
|
|
edit('$e2', 30, 'Tuesday 11am room B'),
|
|
sr('$orig', 10, { body: 'Monday 10am room A' }),
|
|
edit('$e1', 20, 'Tuesday 10am room A'),
|
|
sr('$other', 5, { body: 'meeting notes' }),
|
|
]);
|
|
assert.deepEqual(
|
|
out.map((r) => r.result.event_id),
|
|
['$orig', '$other'],
|
|
);
|
|
assert.deepEqual(out[0].result.content['m.new_content'], { body: 'Tuesday 11am room B' });
|
|
assert.equal(out[0].result.content.body, 'Monday 10am room A');
|
|
});
|
|
|
|
test('foldEdits: an edit whose original did not match stands in for it', () => {
|
|
const out = foldEdits([
|
|
sr('$e1', 20, {
|
|
'm.new_content': { body: 'x' },
|
|
'm.relates_to': { rel_type: 'm.replace', event_id: '$orig' },
|
|
}),
|
|
]);
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].result.event_id, '$orig');
|
|
});
|