m.replace events no longer appear as their own "* text" line — the original row gets m.new_content and an (edited) marker. Media rows carry a [Image]/[Video]/[Audio]/[File] prefix and the JSON export includes mediaUrl; the UI notes attachments aren't included. Unit-tested. Fixes #50 Fixes #81 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
94 lines
2.6 KiB
TypeScript
94 lines
2.6 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
resolveMessageEdits,
|
|
formatExportBody,
|
|
RawExportRecord,
|
|
ResolvedExportRecord,
|
|
} from './exportRoomHistory.utils';
|
|
|
|
const msg = (over: Partial<RawExportRecord>): RawExportRecord => ({
|
|
eventId: '$1',
|
|
ts: 1000,
|
|
sender: '@alice:example.org',
|
|
body: 'hello',
|
|
msgtype: 'm.text',
|
|
...over,
|
|
});
|
|
|
|
test('resolveMessageEdits leaves unedited messages untouched', () => {
|
|
const out = resolveMessageEdits([msg({ eventId: '$1', body: 'hi' })]);
|
|
assert.deepEqual(out, [
|
|
{
|
|
eventId: '$1',
|
|
ts: 1000,
|
|
sender: '@alice:example.org',
|
|
body: 'hi',
|
|
msgtype: 'm.text',
|
|
edited: false,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('resolveMessageEdits substitutes the new content into the original row and drops the edit row', () => {
|
|
const out = resolveMessageEdits([
|
|
msg({ eventId: '$1', body: 'origianl typo' }),
|
|
msg({ eventId: '$2', editsEventId: '$1', newBody: 'original fixed' }),
|
|
]);
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].eventId, '$1');
|
|
assert.equal(out[0].body, 'original fixed');
|
|
assert.equal(out[0].edited, true);
|
|
});
|
|
|
|
test('resolveMessageEdits works regardless of whether the edit appears before its target', () => {
|
|
const out = resolveMessageEdits([
|
|
msg({ eventId: '$2', editsEventId: '$1', newBody: 'fixed' }),
|
|
msg({ eventId: '$1', body: 'orig' }),
|
|
]);
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].body, 'fixed');
|
|
assert.equal(out[0].edited, true);
|
|
});
|
|
|
|
test('resolveMessageEdits ignores an edit with no matching target (target never collected)', () => {
|
|
const out = resolveMessageEdits([msg({ eventId: '$2', editsEventId: '$1', newBody: 'fixed' })]);
|
|
assert.deepEqual(out, []);
|
|
});
|
|
|
|
test('formatExportBody prefixes media messages with a type marker', () => {
|
|
const record: ResolvedExportRecord = {
|
|
eventId: '$1',
|
|
ts: 1000,
|
|
sender: '@alice:example.org',
|
|
body: 'photo.jpg',
|
|
msgtype: 'm.image',
|
|
edited: false,
|
|
};
|
|
assert.equal(formatExportBody(record), '[Image] photo.jpg');
|
|
});
|
|
|
|
test('formatExportBody appends an (edited) suffix', () => {
|
|
const record: ResolvedExportRecord = {
|
|
eventId: '$1',
|
|
ts: 1000,
|
|
sender: '@alice:example.org',
|
|
body: 'fixed text',
|
|
msgtype: 'm.text',
|
|
edited: true,
|
|
};
|
|
assert.equal(formatExportBody(record), 'fixed text (edited)');
|
|
});
|
|
|
|
test('formatExportBody combines media prefix and edited suffix', () => {
|
|
const record: ResolvedExportRecord = {
|
|
eventId: '$1',
|
|
ts: 1000,
|
|
sender: '@alice:example.org',
|
|
body: 'photo.jpg',
|
|
msgtype: 'm.file',
|
|
edited: true,
|
|
};
|
|
assert.equal(formatExportBody(record), '[File] photo.jpg (edited)');
|
|
});
|