mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-02 08:15:55 +00:00
120 lines
4.0 KiB
TypeScript
120 lines
4.0 KiB
TypeScript
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { Editor } from '@tiptap/core';
|
|
import Document from '@tiptap/extension-document';
|
|
import Paragraph from '@tiptap/extension-paragraph';
|
|
import Text from '@tiptap/extension-text';
|
|
import { NodeSelection, TextSelection } from '@tiptap/pm/state';
|
|
import type { Editor as ReactEditor } from '@tiptap/react';
|
|
|
|
import { QuotedHtml, buildQuotedHtmlBlock, serializeEditorContent } from '../quoted-html';
|
|
|
|
// A reply opened with the quoted original selected as a node. Tabbing from
|
|
// Subject into the body kept that selection, so the first key typed replaced
|
|
// the whole quote and the reply went out without it.
|
|
|
|
let editor: Editor | null = null;
|
|
afterEach(() => {
|
|
editor?.destroy();
|
|
editor = null;
|
|
});
|
|
|
|
function mountReply(): Editor {
|
|
return new Editor({
|
|
extensions: [Document, Paragraph, Text, QuotedHtml],
|
|
content: `<p></p>${buildQuotedHtmlBlock('<p>Original message</p>')}`,
|
|
});
|
|
}
|
|
|
|
function selectQuote(e: Editor) {
|
|
let pos = -1;
|
|
e.state.doc.descendants((node, p) => {
|
|
if (node.type.name === 'quotedHtml') pos = p;
|
|
});
|
|
e.view.dispatch(e.state.tr.setSelection(NodeSelection.create(e.state.doc, pos)));
|
|
}
|
|
|
|
describe('QuotedHtml focus', () => {
|
|
it('moves a selection resting on the quote to the start of the body on focus', () => {
|
|
editor = mountReply();
|
|
selectQuote(editor);
|
|
|
|
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
|
|
|
expect(editor.state.selection).toBeInstanceOf(TextSelection);
|
|
expect(editor.state.selection.from).toBe(1);
|
|
});
|
|
|
|
it('keeps the quote when the user types right after focusing', () => {
|
|
editor = mountReply();
|
|
selectQuote(editor);
|
|
|
|
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
|
editor.commands.insertContent('Sure, numbers on Thursday.');
|
|
|
|
const html = serializeEditorContent(editor as unknown as ReactEditor);
|
|
expect(html).toContain('Sure, numbers on Thursday.');
|
|
expect(html).toContain('Original message');
|
|
});
|
|
|
|
it('leaves a text selection alone', () => {
|
|
editor = mountReply();
|
|
editor.view.dispatch(editor.state.tr.setSelection(TextSelection.create(editor.state.doc, 1)));
|
|
const before = editor.state.selection;
|
|
|
|
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
|
|
|
expect(editor.state.selection.eq(before)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('editor focus from the keyboard', () => {
|
|
// A new message is built with the signature in it, which leaves the
|
|
// selection after the signature. Tabbing in from Subject typed there.
|
|
function mountCompose(): Editor {
|
|
const e = new Editor({
|
|
extensions: [Document, Paragraph, Text, QuotedHtml],
|
|
content: '<p></p><p>-- </p><p>User A</p>',
|
|
});
|
|
e.view.dispatch(e.state.tr.setSelection(TextSelection.atEnd(e.state.doc)));
|
|
return e;
|
|
}
|
|
|
|
it('puts the caret at the start on the first focus', () => {
|
|
editor = mountCompose();
|
|
|
|
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
|
|
|
expect(editor.state.selection.from).toBe(1);
|
|
});
|
|
|
|
it('keeps the caret where a click put it', () => {
|
|
editor = mountCompose();
|
|
const end = editor.state.selection.from;
|
|
|
|
// ProseMirror's own click handling asks for the element under the
|
|
// pointer, which jsdom does not implement.
|
|
const doc = document as Document & { elementFromPoint: Document['elementFromPoint'] };
|
|
const original = doc.elementFromPoint;
|
|
doc.elementFromPoint = () => null;
|
|
try {
|
|
editor.view.dom.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
|
|
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
|
} finally {
|
|
doc.elementFromPoint = original;
|
|
}
|
|
|
|
expect(editor.state.selection.from).toBe(end);
|
|
});
|
|
|
|
it('keeps the position when focus comes back later', () => {
|
|
editor = mountCompose();
|
|
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
|
editor.view.dispatch(editor.state.tr.setSelection(TextSelection.atEnd(editor.state.doc)));
|
|
const end = editor.state.selection.from;
|
|
|
|
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
|
|
|
expect(editor.state.selection.from).toBe(end);
|
|
});
|
|
});
|