Files
edentext/tests/unit/context-menu.test.ts

77 lines
3.1 KiB
TypeScript

// The right-click menu's contents (Word's text menu). Drives a real body editor so the
// entries are built against a live selection, exactly as Editor.svelte does.
import { describe, it, expect } from 'vitest';
import { Editor } from '@tiptap/core';
import { extensions } from '../../src/lib/editor/extensions';
import { buildContextMenu, type MenuEntry } from '../../src/lib/editor/contextMenuItems';
type N = any;
function makeEditor(content: N) {
const el = document.createElement('div');
document.body.appendChild(el);
return new Editor({ element: el, extensions, content });
}
const para = (text: string, marks?: N[]): N => ({
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text, ...(marks ? { marks } : {}) }] }],
});
const labels = (entries: MenuEntry[]) =>
entries.filter((e): e is Extract<MenuEntry, { kind: 'item' }> => e.kind === 'item').map((e) => e.label);
const item = (entries: MenuEntry[], label: string) =>
entries.find((e): e is Extract<MenuEntry, { kind: 'item' }> => e.kind === 'item' && e.label === label);
describe('context menu items', () => {
it('disables cut/copy without a selection and enables them with one', () => {
const editor = makeEditor(para('Hello world'));
editor.commands.setTextSelection(3);
expect(item(buildContextMenu(editor), 'Cut')!.disabled).toBe(true);
expect(item(buildContextMenu(editor), 'Copy')!.disabled).toBe(true);
editor.commands.setTextSelection({ from: 1, to: 6 });
expect(item(buildContextMenu(editor), 'Cut')!.disabled).toBe(false);
expect(item(buildContextMenu(editor), 'Copy')!.disabled).toBe(false);
editor.destroy();
});
it('shows spelling suggestions in the first section, above the clipboard', () => {
const editor = makeEditor(para('Helo'));
const plain = buildContextMenu(editor);
expect(labels(plain)).not.toContain('Hello');
const withSpell = buildContextMenu(editor, {
spell: { suggestions: ['Hello', 'Halo'], onReplace: () => {}, onAdd: () => {}, onIgnore: () => {} },
});
const names = labels(withSpell);
expect(names.slice(0, 2)).toEqual(['Hello', 'Halo']);
expect(names.indexOf('Ignore All')).toBeLessThan(names.indexOf('Cut'));
editor.destroy();
});
it('offers edit/remove only when the cursor sits in a link', () => {
const editor = makeEditor(para('Hello'));
expect(labels(buildContextMenu(editor))).toContain('Link…');
expect(labels(buildContextMenu(editor))).not.toContain('Remove Link');
const linked = makeEditor(para('Hello', [{ type: 'link', attrs: { href: 'https://example.com' } }]));
linked.commands.setTextSelection(3);
const names = labels(buildContextMenu(linked));
expect(names).toContain('Edit Link…');
expect(names).toContain('Remove Link');
editor.destroy();
linked.destroy();
});
it('clears direct formatting through its entry', () => {
const editor = makeEditor(para('Hello'));
editor.commands.setTextSelection({ from: 1, to: 6 });
editor.commands.setFontSize('20pt');
item(buildContextMenu(editor), 'Clear Formatting')!.run();
expect(JSON.stringify(editor.getJSON())).not.toContain('20pt');
editor.destroy();
});
});