test: cover recentFiles and the history log
The two framework-free 0%-coverage modules: the recent-files list (dedupe by name, eight-entry cap, corrupt-localStorage tolerance) and the labelled undo/redo mirror against a live jsdom editor (labels per edit kind, undo/redo moves, redo branch discarded by a fresh edit).
This commit is contained in:
1 parent
82993aeda3
commit
32cf72a1d0
2 files changed
+126
No files matched your search
@@ -0,0 +1,77 @@
|
||||
// The labelled undo/redo mirror (utils/historyLog.svelte.ts) against a live editor:
|
||||
// labels per edit kind, entries moving between the stacks on undo/redo, and the
|
||||
// redo branch being discarded by a fresh edit.
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { Editor } from '@tiptap/core';
|
||||
import { closeHistory } from '@tiptap/pm/history';
|
||||
import { extensions } from '../../src/lib/editor/extensions';
|
||||
import { historyLog, recordTransaction, resetHistoryLog } from '../../src/lib/utils/historyLog.svelte';
|
||||
|
||||
function makeEditor() {
|
||||
const el = document.createElement('div');
|
||||
document.body.appendChild(el);
|
||||
return new Editor({
|
||||
element: el,
|
||||
extensions,
|
||||
content: { type: 'doc', content: [{ type: 'paragraph' }] },
|
||||
onTransaction: ({ editor, transaction }) => recordTransaction(editor.state, transaction),
|
||||
});
|
||||
}
|
||||
|
||||
// Consecutive edits in one test tick share a timestamp and would merge into one
|
||||
// undo group; an explicit group boundary keeps one log entry per edit.
|
||||
const endGroup = (editor: Editor) =>
|
||||
editor.view.dispatch(closeHistory(editor.state.tr));
|
||||
|
||||
const undoLabels = () => historyLog.undo.map((e) => e.label);
|
||||
const redoLabels = () => historyLog.redo.map((e) => e.label);
|
||||
|
||||
describe('history log', () => {
|
||||
beforeEach(resetHistoryLog);
|
||||
|
||||
it('labels typing, marks, structure and delete', () => {
|
||||
const editor = makeEditor();
|
||||
editor.commands.insertContent('Hello world');
|
||||
endGroup(editor);
|
||||
editor.chain().setTextSelection({ from: 1, to: 6 }).toggleBold().run();
|
||||
endGroup(editor);
|
||||
editor.commands.insertContentAt(editor.state.doc.content.size,
|
||||
{ type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Title' }] });
|
||||
endGroup(editor);
|
||||
editor.chain().setTextSelection({ from: 7, to: 12 }).deleteSelection().run();
|
||||
|
||||
expect(undoLabels()).toEqual(['Typing: “Hello world”', 'Bold', 'Heading 2', 'Delete']);
|
||||
expect(redoLabels()).toEqual([]);
|
||||
editor.destroy();
|
||||
});
|
||||
|
||||
it('moves entries across on undo/redo and keeps counts at the plugin depths', () => {
|
||||
const editor = makeEditor();
|
||||
editor.commands.insertContent('one');
|
||||
endGroup(editor);
|
||||
editor.chain().selectAll().toggleBold().run();
|
||||
|
||||
editor.commands.undo();
|
||||
expect(undoLabels()).toEqual(['Typing: “one”']);
|
||||
expect(redoLabels()).toEqual(['Bold']);
|
||||
|
||||
editor.commands.redo();
|
||||
expect(undoLabels()).toEqual(['Typing: “one”', 'Bold']);
|
||||
expect(redoLabels()).toEqual([]);
|
||||
editor.destroy();
|
||||
});
|
||||
|
||||
it('a fresh edit after undo discards the redo branch', () => {
|
||||
const editor = makeEditor();
|
||||
editor.commands.insertContent('one');
|
||||
endGroup(editor);
|
||||
editor.commands.insertContent(' two');
|
||||
editor.commands.undo();
|
||||
expect(redoLabels()).toHaveLength(1);
|
||||
|
||||
editor.commands.insertContent(' three');
|
||||
expect(redoLabels()).toEqual([]);
|
||||
expect(undoLabels()).toEqual(['Typing: “one”', 'Typing: “three”']);
|
||||
editor.destroy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
// The recent-files list (storage/recentFiles.ts): dedupe by name, newest first,
|
||||
// capped at eight, and tolerant of corrupt localStorage. jsdom has no IndexedDB,
|
||||
// so the handle side no-ops — exactly the browser-without-picker path.
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { loadRecentFiles, rememberRecentFile, forgetRecentFiles } from '../../src/lib/storage/recentFiles';
|
||||
|
||||
const KEY = 'edentext-recent-files';
|
||||
|
||||
describe('recent files', () => {
|
||||
beforeEach(() => localStorage.clear());
|
||||
|
||||
it('starts empty and survives corrupt or foreign values under its key', () => {
|
||||
expect(loadRecentFiles()).toEqual([]);
|
||||
localStorage.setItem(KEY, 'not json{');
|
||||
expect(loadRecentFiles()).toEqual([]);
|
||||
localStorage.setItem(KEY, '{"a":1}');
|
||||
expect(loadRecentFiles()).toEqual([]);
|
||||
localStorage.setItem(KEY, '[{"id":"x","name":"ok.odt","at":1},{"id":"y","at":2},null]');
|
||||
expect(loadRecentFiles().map((f) => f.name)).toEqual(['ok.odt']);
|
||||
});
|
||||
|
||||
it('remembers newest first and dedupes a handleless file by name, keeping its id', async () => {
|
||||
await rememberRecentFile('a.odt', null);
|
||||
await rememberRecentFile('b.odt', null);
|
||||
expect(loadRecentFiles().map((f) => f.name)).toEqual(['b.odt', 'a.odt']);
|
||||
|
||||
const idA = loadRecentFiles()[1].id;
|
||||
await rememberRecentFile('a.odt', null);
|
||||
const list = loadRecentFiles();
|
||||
expect(list.map((f) => f.name)).toEqual(['a.odt', 'b.odt']);
|
||||
expect(list[0].id).toBe(idA);
|
||||
});
|
||||
|
||||
it('keeps at most eight entries, dropping the oldest', async () => {
|
||||
for (let i = 1; i <= 10; i++) await rememberRecentFile(`f${i}.odt`, null);
|
||||
const names = loadRecentFiles().map((f) => f.name);
|
||||
expect(names).toHaveLength(8);
|
||||
expect(names[0]).toBe('f10.odt');
|
||||
expect(names).not.toContain('f1.odt');
|
||||
expect(names).not.toContain('f2.odt');
|
||||
});
|
||||
|
||||
it('forgetRecentFiles clears the list and removes the key', async () => {
|
||||
await rememberRecentFile('a.odt', null);
|
||||
forgetRecentFiles();
|
||||
expect(loadRecentFiles()).toEqual([]);
|
||||
expect(localStorage.getItem(KEY)).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user