Lexical: Fixed auto-link issue
Added extra test helper to check the editor state directly via string notation access rather than juggling types/objects to access deep properties.
This commit is contained in:
parent
786a434c03
commit
0d1a237f81
|
@ -37,8 +37,6 @@ import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
||||||
import {DetailsNode} from "@lexical/rich-text/LexicalDetailsNode";
|
import {DetailsNode} from "@lexical/rich-text/LexicalDetailsNode";
|
||||||
import {EditorUiContext} from "../../../../ui/framework/core";
|
import {EditorUiContext} from "../../../../ui/framework/core";
|
||||||
import {EditorUIManager} from "../../../../ui/framework/manager";
|
import {EditorUIManager} from "../../../../ui/framework/manager";
|
||||||
import {turtle} from "@codemirror/legacy-modes/mode/turtle";
|
|
||||||
|
|
||||||
|
|
||||||
type TestEnv = {
|
type TestEnv = {
|
||||||
readonly container: HTMLDivElement;
|
readonly container: HTMLDivElement;
|
||||||
|
@ -47,6 +45,9 @@ type TestEnv = {
|
||||||
readonly innerHTML: string;
|
readonly innerHTML: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated - Consider using `createTestContext` instead within the test case.
|
||||||
|
*/
|
||||||
export function initializeUnitTest(
|
export function initializeUnitTest(
|
||||||
runTests: (testEnv: TestEnv) => void,
|
runTests: (testEnv: TestEnv) => void,
|
||||||
editorConfig: CreateEditorArgs = {namespace: 'test', theme: {}},
|
editorConfig: CreateEditorArgs = {namespace: 'test', theme: {}},
|
||||||
|
@ -795,6 +796,30 @@ export function expectNodeShapeToMatch(editor: LexicalEditor, expected: nodeShap
|
||||||
expect(shape.children).toMatchObject(expected);
|
expect(shape.children).toMatchObject(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expect a given prop within the JSON editor state structure to be the given value.
|
||||||
|
* Uses dot notation for the provided `propPath`. Example:
|
||||||
|
* 0.5.cat => First child, Sixth child, cat property
|
||||||
|
*/
|
||||||
|
export function expectEditorStateJSONPropToEqual(editor: LexicalEditor, propPath: string, expected: any) {
|
||||||
|
let currentItem: any = editor.getEditorState().toJSON().root;
|
||||||
|
let currentPath = [];
|
||||||
|
const pathParts = propPath.split('.');
|
||||||
|
|
||||||
|
for (const part of pathParts) {
|
||||||
|
currentPath.push(part);
|
||||||
|
const childAccess = Number.isInteger(Number(part)) && Array.isArray(currentItem.children);
|
||||||
|
const target = childAccess ? currentItem.children : currentItem;
|
||||||
|
|
||||||
|
if (typeof target[part] === 'undefined') {
|
||||||
|
throw new Error(`Could not resolve editor state at path ${currentPath.join('.')}`)
|
||||||
|
}
|
||||||
|
currentItem = target[part];
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(currentItem).toBe(expected);
|
||||||
|
}
|
||||||
|
|
||||||
function formatHtml(s: string): string {
|
function formatHtml(s: string): string {
|
||||||
return s.replace(/>\s+</g, '><').replace(/\s*\n\s*/g, ' ').trim();
|
return s.replace(/>\s+</g, '><').replace(/\s*\n\s*/g, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,91 +1,76 @@
|
||||||
import {initializeUnitTest} from "lexical/__tests__/utils";
|
import {
|
||||||
import {SerializedLinkNode} from "@lexical/link";
|
createTestContext,
|
||||||
|
dispatchKeydownEventForNode, expectEditorStateJSONPropToEqual,
|
||||||
|
expectNodeShapeToMatch
|
||||||
|
} from "lexical/__tests__/utils";
|
||||||
import {
|
import {
|
||||||
$getRoot,
|
$getRoot,
|
||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
SerializedParagraphNode,
|
|
||||||
SerializedTextNode,
|
|
||||||
TextNode
|
TextNode
|
||||||
} from "lexical";
|
} from "lexical";
|
||||||
import {registerAutoLinks} from "../auto-links";
|
import {registerAutoLinks} from "../auto-links";
|
||||||
|
|
||||||
describe('Auto-link service tests', () => {
|
describe('Auto-link service tests', () => {
|
||||||
initializeUnitTest((testEnv) => {
|
test('space after link in text', async () => {
|
||||||
|
const {editor} = createTestContext();
|
||||||
|
registerAutoLinks(editor);
|
||||||
|
let pNode!: ParagraphNode;
|
||||||
|
|
||||||
test('space after link in text', async () => {
|
editor.updateAndCommit(() => {
|
||||||
const {editor} = testEnv;
|
pNode = new ParagraphNode();
|
||||||
|
const text = new TextNode('Some https://example.com?test=true text');
|
||||||
|
pNode.append(text);
|
||||||
|
$getRoot().append(pNode);
|
||||||
|
|
||||||
registerAutoLinks(editor);
|
text.select(34, 34);
|
||||||
let pNode!: ParagraphNode;
|
|
||||||
|
|
||||||
editor.update(() => {
|
|
||||||
pNode = new ParagraphNode();
|
|
||||||
const text = new TextNode('Some https://example.com?test=true text');
|
|
||||||
pNode.append(text);
|
|
||||||
$getRoot().append(pNode);
|
|
||||||
|
|
||||||
text.select(34, 34);
|
|
||||||
});
|
|
||||||
|
|
||||||
editor.commitUpdates();
|
|
||||||
|
|
||||||
const pDomEl = editor.getElementByKey(pNode.getKey());
|
|
||||||
const event = new KeyboardEvent('keydown', {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
key: ' ',
|
|
||||||
keyCode: 62,
|
|
||||||
});
|
|
||||||
pDomEl?.dispatchEvent(event);
|
|
||||||
|
|
||||||
editor.commitUpdates();
|
|
||||||
|
|
||||||
const paragraph = editor!.getEditorState().toJSON().root
|
|
||||||
.children[0] as SerializedParagraphNode;
|
|
||||||
expect(paragraph.children[1].type).toBe('link');
|
|
||||||
|
|
||||||
const link = paragraph.children[1] as SerializedLinkNode;
|
|
||||||
expect(link.url).toBe('https://example.com?test=true');
|
|
||||||
const linkText = link.children[0] as SerializedTextNode;
|
|
||||||
expect(linkText.text).toBe('https://example.com?test=true');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('enter after link in text', async () => {
|
dispatchKeydownEventForNode(pNode, editor, ' ');
|
||||||
const {editor} = testEnv;
|
|
||||||
|
|
||||||
registerAutoLinks(editor);
|
expectEditorStateJSONPropToEqual(editor, '0.1.url', 'https://example.com?test=true');
|
||||||
let pNode!: ParagraphNode;
|
expectEditorStateJSONPropToEqual(editor, '0.1.0.text', 'https://example.com?test=true');
|
||||||
|
});
|
||||||
|
|
||||||
editor.update(() => {
|
test('space after link at end of line', async () => {
|
||||||
pNode = new ParagraphNode();
|
const {editor} = createTestContext();
|
||||||
const text = new TextNode('Some https://example.com?test=true text');
|
registerAutoLinks(editor);
|
||||||
pNode.append(text);
|
let pNode!: ParagraphNode;
|
||||||
$getRoot().append(pNode);
|
|
||||||
|
|
||||||
text.select(34, 34);
|
editor.updateAndCommit(() => {
|
||||||
});
|
pNode = new ParagraphNode();
|
||||||
|
const text = new TextNode('Some https://example.com?test=true');
|
||||||
|
pNode.append(text);
|
||||||
|
$getRoot().append(pNode);
|
||||||
|
|
||||||
editor.commitUpdates();
|
text.selectEnd();
|
||||||
|
|
||||||
const pDomEl = editor.getElementByKey(pNode.getKey());
|
|
||||||
const event = new KeyboardEvent('keydown', {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
key: 'Enter',
|
|
||||||
keyCode: 66,
|
|
||||||
});
|
|
||||||
pDomEl?.dispatchEvent(event);
|
|
||||||
|
|
||||||
editor.commitUpdates();
|
|
||||||
|
|
||||||
const paragraph = editor!.getEditorState().toJSON().root
|
|
||||||
.children[0] as SerializedParagraphNode;
|
|
||||||
expect(paragraph.children[1].type).toBe('link');
|
|
||||||
|
|
||||||
const link = paragraph.children[1] as SerializedLinkNode;
|
|
||||||
expect(link.url).toBe('https://example.com?test=true');
|
|
||||||
const linkText = link.children[0] as SerializedTextNode;
|
|
||||||
expect(linkText.text).toBe('https://example.com?test=true');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dispatchKeydownEventForNode(pNode, editor, ' ');
|
||||||
|
|
||||||
|
expectNodeShapeToMatch(editor, [{type: 'paragraph', children: [
|
||||||
|
{text: 'Some '},
|
||||||
|
{type: 'link', children: [{text: 'https://example.com?test=true'}]}
|
||||||
|
]}]);
|
||||||
|
expectEditorStateJSONPropToEqual(editor, '0.1.url', 'https://example.com?test=true');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enter after link in text', async () => {
|
||||||
|
const {editor} = createTestContext();
|
||||||
|
registerAutoLinks(editor);
|
||||||
|
let pNode!: ParagraphNode;
|
||||||
|
|
||||||
|
editor.updateAndCommit(() => {
|
||||||
|
pNode = new ParagraphNode();
|
||||||
|
const text = new TextNode('Some https://example.com?test=true text');
|
||||||
|
pNode.append(text);
|
||||||
|
$getRoot().append(pNode);
|
||||||
|
|
||||||
|
text.select(34, 34);
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatchKeydownEventForNode(pNode, editor, 'Enter');
|
||||||
|
|
||||||
|
expectEditorStateJSONPropToEqual(editor, '0.1.url', 'https://example.com?test=true');
|
||||||
|
expectEditorStateJSONPropToEqual(editor, '0.1.0.text', 'https://example.com?test=true');
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -43,7 +43,7 @@ function handlePotentialLinkEvent(node: TextNode, selection: BaseSelection, edit
|
||||||
linkNode.append(new TextNode(textSegment));
|
linkNode.append(new TextNode(textSegment));
|
||||||
|
|
||||||
const splits = node.splitText(startIndex, cursorPoint);
|
const splits = node.splitText(startIndex, cursorPoint);
|
||||||
const targetIndex = splits.length === 3 ? 1 : 0;
|
const targetIndex = startIndex > 0 ? 1 : 0;
|
||||||
const targetText = splits[targetIndex];
|
const targetText = splits[targetIndex];
|
||||||
if (targetText) {
|
if (targetText) {
|
||||||
targetText.replace(linkNode);
|
targetText.replace(linkNode);
|
||||||
|
|
|
@ -2,11 +2,7 @@
|
||||||
|
|
||||||
## In progress
|
## In progress
|
||||||
|
|
||||||
Reorg
|
//
|
||||||
- Merge custom nodes into original nodes
|
|
||||||
- Reduce down to use CommonBlockNode where possible
|
|
||||||
- Remove existing formatType/ElementFormatType references (replaced with alignment).
|
|
||||||
- Remove existing indent references (replaced with inset).
|
|
||||||
|
|
||||||
## Main Todo
|
## Main Todo
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue