fix(export): resolve edits into the original row; mark media rows
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
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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)');
|
||||
});
|
||||
Reference in New Issue
Block a user