1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-21 09:38:01 +02:00
joplin/packages/lib/models/Revision.test.ts

247 lines
6.3 KiB
TypeScript
Raw Normal View History

import { expectNotThrow, naughtyStrings, setupDatabaseAndSynchronizer, switchClient } from '../testing/test-utils';
import Note from '../models/Note';
import Revision, { ObjectPatch } from '../models/Revision';
describe('models/Revision', () => {
2022-11-15 12:23:50 +02:00
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
});
it('should create patches of text and apply it', (async () => {
const note1 = await Note.save({ body: 'my note\nsecond line' });
const patch = Revision.createTextPatch(note1.body, 'my new note\nsecond line');
const merged = Revision.applyTextPatch(note1.body, patch);
expect(merged).toBe('my new note\nsecond line');
}));
it('should check if it is an empty revision', async () => {
const testCases = [
[false, {
title_diff: '',
body_diff: '',
metadata_diff: '{"new":{"id":"aaa"},"deleted":[]}',
}],
[true, {
title_diff: '',
body_diff: '',
metadata_diff: '',
}],
[true, {
title_diff: '[]',
body_diff: '',
metadata_diff: '{"new":{},"deleted":[]}',
}],
[true, {
title_diff: '',
body_diff: '[]',
metadata_diff: '{"new":{},"deleted":[]}',
}],
[false, {
title_diff: '[{"diffs":[[1,"hello"]],"start1":0,"start2":0,"length1":0,"length2":5}]',
body_diff: '[]',
metadata_diff: '{"new":{},"deleted":[]}',
}],
];
for (const t of testCases) {
const [expected, input] = t;
expect(Revision.isEmptyRevision(input as any)).toBe(expected);
}
});
it('should not fail to create revisions on naughty strings', (async () => {
// Previously this pattern would fail:
// - Create a patch between an empty string and smileys
// - Use that patch on the empty string to get back the smileys
// - Create a patch between those smileys and new smileys
// https://github.com/JackuB/diff-match-patch/issues/22
const nss = await naughtyStrings();
// First confirm that it indeed fails with the legacy approach.
let errorCount = 0;
for (let i = 0; i < nss.length - 1; i++) {
const ns1 = nss[i];
const ns2 = nss[i + 1];
try {
const patchText = Revision.createTextPatchLegacy('', ns1);
const patchedText = Revision.applyTextPatchLegacy('', patchText);
Revision.createTextPatchLegacy(patchedText, ns2);
} catch (error) {
errorCount++;
}
}
expect(errorCount).toBe(10);
// Now feed the naughty list again but using the new approach. In that
// case it should work fine.
await expectNotThrow(async () => {
for (let i = 0; i < nss.length - 1; i++) {
const ns1 = nss[i];
const ns2 = nss[i + 1];
const patchText = Revision.createTextPatch('', ns1);
const patchedText = Revision.applyTextPatch('', patchText);
Revision.createTextPatch(patchedText, ns2);
}
});
}));
it('should successfully handle legacy patches', async () => {
// The code should handle applying a series of new style patches and
// legacy patches, and the correct text should be recovered at the end.
const changes = [
'',
'one',
'one three',
'one two three',
];
const patches = [
Revision.createTextPatch(changes[0], changes[1]),
Revision.createTextPatchLegacy(changes[1], changes[2]),
Revision.createTextPatch(changes[2], changes[3]),
];
// Sanity check - verify that the patches are as expected
expect(patches[0].substr(0, 2)).toBe('[{'); // New
expect(patches[1].substr(0, 2)).toBe('@@'); // Legacy
expect(patches[2].substr(0, 2)).toBe('[{'); // New
let finalString = Revision.applyTextPatch(changes[0], patches[0]);
finalString = Revision.applyTextPatch(finalString, patches[1]);
finalString = Revision.applyTextPatch(finalString, patches[2]);
expect(finalString).toBe('one two three');
});
it('should create patches of objects and apply it', (async () => {
const oldObject = {
one: '123',
two: '456',
three: '789',
};
const newObject = {
one: '123',
three: '999',
};
const patch = Revision.createObjectPatch(oldObject, newObject);
const merged = Revision.applyObjectPatch(oldObject, patch);
expect(JSON.stringify(merged)).toBe(JSON.stringify(newObject));
}));
it('should handle invalid object patch', (async () => {
const oldObject = {
one: '123',
two: '456',
three: '789',
};
const brokenPatch = `{"new":{"four":"444
"},"deleted":["one"]}`;
const expected = {
two: '456',
three: '789',
four: '444',
};
const merged = Revision.applyObjectPatch(oldObject, brokenPatch);
expect(JSON.stringify(merged)).toBe(JSON.stringify(expected));
}));
it('should not strip off newlines from object values', (async () => {
const oldObject = {
one: '123',
two: '456',
three: '789',
};
const patch: ObjectPatch = {
'new': {
'four': 'one line\ntwo line',
},
'deleted': [],
};
const expected = {
one: '123',
two: '456',
three: '789',
four: 'one line\ntwo line',
};
const merged = Revision.applyObjectPatch(oldObject, JSON.stringify(patch));
expect(JSON.stringify(merged)).toBe(JSON.stringify(expected));
}));
it('should move target revision to the top', (async () => {
const revs = [
{ id: '123' },
{ id: '456' },
{ id: '789' },
];
let newRevs;
newRevs = Revision.moveRevisionToTop({ id: '456' }, revs);
expect(newRevs[0].id).toBe('123');
expect(newRevs[1].id).toBe('789');
expect(newRevs[2].id).toBe('456');
newRevs = Revision.moveRevisionToTop({ id: '789' }, revs);
expect(newRevs[0].id).toBe('123');
expect(newRevs[1].id).toBe('456');
expect(newRevs[2].id).toBe('789');
}));
// cSpell:disable
it('should create patch stats', (async () => {
const tests = [
{
patch: `@@ -625,16 +625,48 @@
rrupted download
+%0A- %5B %5D Fix mobile screen options`,
expected: [-0, +32],
},
{
patch: `@@ -564,17 +564,17 @@
ages%0A- %5B
-
+x
%5D Check `,
expected: [-1, +1],
},
{
patch: `@@ -1022,56 +1022,415 @@
.%0A%0A#
- How to view a note history%0A%0AWhile all the apps
+%C2%A0How does it work?%0A%0AAll the apps save a version of the modified notes every 10 minutes.
%0A%0A# `,
expected: [-(19 + 27 + 2), 17 + 67 + 4],
},
{
patch: '',
expected: [-0, +0],
},
];
for (const test of tests) {
const stats = Revision.patchStats(test.patch);
expect(stats.removed).toBe(-test.expected[0]);
expect(stats.added).toBe(test.expected[1]);
}
}));
// cSpell:enable
});