mirror of
https://github.com/facebook/lexical.git
synced 2025-08-06 16:39:33 +08:00
DFS Helper (#639)
This commit is contained in:
@ -30,6 +30,7 @@ module.name_mapper='^outline/HistoryHelpers' -> '<PROJECT_ROOT>/packages/outline
|
||||
module.name_mapper='^outline/SelectionHelpers' -> '<PROJECT_ROOT>/packages/outline/src/helpers/OutlineSelectionHelpers.js'
|
||||
module.name_mapper='^outline/TextHelpers' -> '<PROJECT_ROOT>/packages/outline/src/helpers/OutlineTextHelpers.js'
|
||||
module.name_mapper='^outline/KeyHelpers' -> '<PROJECT_ROOT>/packages/outline/src/helpers/OutlineKeyHelpers.js'
|
||||
module.name_mapper='^outline/NodeHelpers' -> '<PROJECT_ROOT>/packages/outline/src/helpers/OutlineNodeHelpers.js'
|
||||
|
||||
module.name_mapper='^outline-react/OutlineTreeView' -> '<PROJECT_ROOT>/packages/outline-react/src/OutlineTreeView.js'
|
||||
module.name_mapper='^outline-react/useOutlineEditor' -> '<PROJECT_ROOT>/packages/outline-react/src/useOutlineEditor.js'
|
||||
|
@ -45,6 +45,8 @@ module.exports = {
|
||||
'<rootDir>/packages/outline/src/helpers/OutlineTextHelpers.js',
|
||||
'^outline/KeyHelpers$':
|
||||
'<rootDir>/packages/outline/src/helpers/OutlineKeyHelpers.js',
|
||||
'^outline/NodeHelpers$':
|
||||
'<rootDir>/packages/outline/src/helpers/OutlineNodeHelpers.js',
|
||||
'^shared/getDOMTextNodeFromElement$':
|
||||
'<rootDir>/packages/shared/src/getDOMTextNodeFromElement.js',
|
||||
'^shared/isImmutableOrInert$':
|
||||
|
@ -15,6 +15,7 @@ module.exports = {
|
||||
'outline/TextHelpers': 'outline/dist/OutlineTextHelpers',
|
||||
'outline/HistoryHelpers': 'outline/dist/OutlineHistoryHelpers',
|
||||
'outline/KeyHelpers': 'outline/dist/OutlineKeyHelpers',
|
||||
'outline/NodeHelpers': 'outline/dist/OutlineNodeHelpers',
|
||||
// Outline React
|
||||
'outline-react/OutlineTreeView': 'outline-react/dist/OutlineTreeView',
|
||||
'outline-react/useOutlineEditor': 'outline-react/dist/useOutlineEditor',
|
||||
|
@ -12,19 +12,8 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactTestUtils from 'react-dom/test-utils';
|
||||
|
||||
import {BlockNode, createEditor, createTextNode} from 'outline';
|
||||
|
||||
class TestBlockNode extends BlockNode {
|
||||
static clone(node: BlockNode) {
|
||||
return new TestBlockNode(node.__key);
|
||||
}
|
||||
createDOM() {
|
||||
return document.createElement('div');
|
||||
}
|
||||
updateDOM() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
import {createEditor, createTextNode} from 'outline';
|
||||
import {createTestBlockNode} from '../utils';
|
||||
|
||||
describe('OutlineBlockNode tests', () => {
|
||||
let container = null;
|
||||
@ -76,7 +65,7 @@ describe('OutlineBlockNode tests', () => {
|
||||
|
||||
// Insert initial block
|
||||
await update((view) => {
|
||||
const block = new TestBlockNode();
|
||||
const block = createTestBlockNode();
|
||||
const text = createTextNode('Foo');
|
||||
const text2 = createTextNode('Bar');
|
||||
// Prevent text nodes from combining.
|
||||
@ -96,7 +85,7 @@ describe('OutlineBlockNode tests', () => {
|
||||
describe('getChildren()', () => {
|
||||
test('no children', async () => {
|
||||
await update(() => {
|
||||
const block = new TestBlockNode();
|
||||
const block = createTestBlockNode();
|
||||
const children = block.getChildren();
|
||||
expect(children).toHaveLength(0);
|
||||
expect(children).toEqual([]);
|
||||
@ -121,8 +110,8 @@ describe('OutlineBlockNode tests', () => {
|
||||
|
||||
test('nested', async () => {
|
||||
await update((view) => {
|
||||
const block = new TestBlockNode();
|
||||
const innerBlock = new TestBlockNode();
|
||||
const block = createTestBlockNode();
|
||||
const innerBlock = createTestBlockNode();
|
||||
const text = createTextNode('Foo');
|
||||
text.select(0, 0);
|
||||
const text2 = createTextNode('Bar');
|
||||
@ -140,7 +129,7 @@ describe('OutlineBlockNode tests', () => {
|
||||
expect(children).toHaveLength(4);
|
||||
expect(children).toEqual([text, text2, text3, text4]);
|
||||
|
||||
const innerInnerBlock = new TestBlockNode();
|
||||
const innerInnerBlock = createTestBlockNode();
|
||||
const text5 = createTextNode('More');
|
||||
const text6 = createTextNode('Stuff');
|
||||
innerInnerBlock.append(text5);
|
||||
@ -168,7 +157,7 @@ describe('OutlineBlockNode tests', () => {
|
||||
|
||||
test('empty', async () => {
|
||||
await update(() => {
|
||||
const block = new TestBlockNode();
|
||||
const block = createTestBlockNode();
|
||||
expect(block.getFirstChild()).toBe(null);
|
||||
});
|
||||
});
|
||||
@ -185,7 +174,7 @@ describe('OutlineBlockNode tests', () => {
|
||||
|
||||
test('empty', async () => {
|
||||
await update(() => {
|
||||
const block = new TestBlockNode();
|
||||
const block = createTestBlockNode();
|
||||
expect(block.getLastChild()).toBe(null);
|
||||
});
|
||||
});
|
||||
@ -202,15 +191,15 @@ describe('OutlineBlockNode tests', () => {
|
||||
|
||||
test('empty', async () => {
|
||||
await update(() => {
|
||||
const block = new TestBlockNode();
|
||||
const block = createTestBlockNode();
|
||||
expect(block.getTextContent()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
test('nested', async () => {
|
||||
await update((view) => {
|
||||
const block = new TestBlockNode();
|
||||
const innerBlock = new TestBlockNode();
|
||||
const block = createTestBlockNode();
|
||||
const innerBlock = createTestBlockNode();
|
||||
const text = createTextNode('Foo');
|
||||
text.select(0, 0);
|
||||
const text2 = createTextNode('Bar');
|
||||
@ -228,7 +217,7 @@ describe('OutlineBlockNode tests', () => {
|
||||
expect(block.getTextContent()).toEqual('FooBar\n\nQux');
|
||||
expect(block.getTextContent(true)).toEqual('FooBarBaz\n\nQux');
|
||||
|
||||
const innerInnerBlock = new TestBlockNode();
|
||||
const innerInnerBlock = createTestBlockNode();
|
||||
const text5 = createTextNode('More');
|
||||
text5.makeInert();
|
||||
const text6 = createTextNode('Stuff');
|
||||
|
@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
import {createCodeNode, CodeNode} from 'outline/CodeNode';
|
||||
import {ParagraphNode} from 'outline/ParagraphNode';
|
||||
import {TextNode} from 'outline';
|
||||
import {createParagraphNode} from 'outline/ParagraphNode';
|
||||
import {createTextNode} from 'outline';
|
||||
import {initializeUnitTest} from '../utils';
|
||||
|
||||
const editorConfig = Object.freeze({
|
||||
@ -60,8 +60,8 @@ describe('OutlineCodeNode tests', () => {
|
||||
const {editor} = testEnv;
|
||||
await editor.update((view) => {
|
||||
const root = view.getRoot();
|
||||
const paragraphNode = new ParagraphNode();
|
||||
const textNode = new TextNode('foo');
|
||||
const paragraphNode = createParagraphNode();
|
||||
const textNode = createTextNode('foo');
|
||||
paragraphNode.append(textNode);
|
||||
root.append(paragraphNode);
|
||||
textNode.select(0, 0);
|
||||
|
@ -22,22 +22,7 @@ import {
|
||||
import {createParagraphNode, ParagraphNode} from 'outline/ParagraphNode';
|
||||
import useOutlineRichText from 'outline-react/useOutlineRichText';
|
||||
import {getNodeByKey} from '../../core/OutlineNode';
|
||||
|
||||
class TestBlockNode extends BlockNode {
|
||||
static clone(node: TestBlockNode) {
|
||||
return new TestBlockNode(node.__key);
|
||||
}
|
||||
createDOM() {
|
||||
return document.createElement('div');
|
||||
}
|
||||
updateDOM() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function createTestBlockNode() {
|
||||
return new TestBlockNode();
|
||||
}
|
||||
import {createTestBlockNode} from '../utils';
|
||||
|
||||
function sanitizeHTML(html) {
|
||||
// Remove zero width characters
|
||||
|
@ -6,15 +6,15 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import type {OutlineEditor} from 'outline';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactTestUtils from 'react-dom/test-utils';
|
||||
|
||||
import {createEditor} from 'outline';
|
||||
import {createEditor, BlockNode} from 'outline';
|
||||
import {resetRandomKey} from '../../core/OutlineUtils';
|
||||
|
||||
import type {OutlineEditor} from 'outline';
|
||||
|
||||
type TestEnv = {
|
||||
editor: OutlineEditor | null,
|
||||
container: HTMLDivElement | null,
|
||||
@ -80,3 +80,19 @@ export const initializeUnitTest = (runTests: (testEnv: TestEnv) => void) => {
|
||||
|
||||
runTests(testEnv);
|
||||
};
|
||||
|
||||
export class TestBlockNode extends BlockNode {
|
||||
static clone(node: BlockNode) {
|
||||
return new TestBlockNode(node.__key);
|
||||
}
|
||||
createDOM() {
|
||||
return document.createElement('div');
|
||||
}
|
||||
updateDOM() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function createTestBlockNode(): TestBlockNode {
|
||||
return new TestBlockNode();
|
||||
}
|
||||
|
39
packages/outline/src/helpers/OutlineNodeHelpers.js
Normal file
39
packages/outline/src/helpers/OutlineNodeHelpers.js
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {OutlineNode} from 'outline';
|
||||
|
||||
import {isBlockNode} from 'outline';
|
||||
|
||||
export function dfs(
|
||||
startingNode: OutlineNode,
|
||||
nextNode: (OutlineNode) => OutlineNode | null,
|
||||
) {
|
||||
let node = startingNode;
|
||||
nextNode(node);
|
||||
while (node !== null) {
|
||||
if (isBlockNode(node) && node.getChildrenSize() > 0) {
|
||||
node = node.getFirstChild();
|
||||
} else {
|
||||
// Find immediate sibling or nearest parent sibling
|
||||
let sibling = null;
|
||||
while (sibling === null && node !== null) {
|
||||
sibling = node.getNextSibling();
|
||||
if (sibling === null) {
|
||||
node = node.getParent();
|
||||
} else {
|
||||
node = sibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node !== null) {
|
||||
node = nextNode(node);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {OutlineEditor, View, NodeKey, OutlineNode} from 'outline';
|
||||
|
||||
import {
|
||||
initializeUnitTest,
|
||||
createTestBlockNode,
|
||||
} from '../../../__tests__/utils';
|
||||
import {dfs} from '../../OutlineNodeHelpers';
|
||||
import {createParagraphNode, isParagraphNode} from 'outline/ParagraphNode';
|
||||
import {createTextNode} from 'outline';
|
||||
|
||||
describe('OutlineNodeHelpers tests', () => {
|
||||
initializeUnitTest((testEnv) => {
|
||||
/**
|
||||
* R
|
||||
* P1 P2
|
||||
* B1 B2 T4 T5 B3
|
||||
* T1 T2 T3 T6
|
||||
*
|
||||
* DFS: R, P1, B1, T1, B2, T2, T3, P2, T4, T5, B3, T6
|
||||
*/
|
||||
test('DFS node order', async () => {
|
||||
const editor: OutlineEditor = testEnv.editor;
|
||||
let expectedKeys: Array<NodeKey> = [];
|
||||
await editor.update((view: View) => {
|
||||
const root = view.getRoot();
|
||||
const paragraph1 = createParagraphNode();
|
||||
const paragraph2 = createParagraphNode();
|
||||
const block1 = createTestBlockNode();
|
||||
const block2 = createTestBlockNode();
|
||||
const block3 = createTestBlockNode();
|
||||
const text1 = createTextNode('text1');
|
||||
const text2 = createTextNode('text2');
|
||||
const text3 = createTextNode('text3');
|
||||
const text4 = createTextNode('text4');
|
||||
const text5 = createTextNode('text5');
|
||||
const text6 = createTextNode('text6');
|
||||
root.append(paragraph1);
|
||||
root.append(paragraph2);
|
||||
paragraph1.append(block1);
|
||||
paragraph1.append(block2);
|
||||
paragraph2.append(text4);
|
||||
paragraph2.append(text5);
|
||||
text5.toggleBold(); // Prevent from merging with text 4
|
||||
paragraph2.append(block3);
|
||||
block1.append(text1);
|
||||
block2.append(text2);
|
||||
block2.append(text3);
|
||||
text3.toggleBold(); // Prevent from merging with text2
|
||||
block3.append(text6);
|
||||
|
||||
expectedKeys = [
|
||||
root.getKey(),
|
||||
paragraph1.getKey(),
|
||||
block1.getKey(),
|
||||
text1.getKey(),
|
||||
block2.getKey(),
|
||||
text2.getKey(),
|
||||
text3.getKey(),
|
||||
paragraph2.getKey(),
|
||||
text4.getKey(),
|
||||
text5.getKey(),
|
||||
block3.getKey(),
|
||||
text6.getKey(),
|
||||
];
|
||||
});
|
||||
|
||||
const dfsKeys = [];
|
||||
await editor.update((view: View) => {
|
||||
const root = view.getRoot();
|
||||
dfs(root, (node: OutlineNode) => {
|
||||
dfsKeys.push(node.getKey());
|
||||
return node;
|
||||
});
|
||||
});
|
||||
expect(dfsKeys).toEqual(expectedKeys);
|
||||
});
|
||||
|
||||
test('Skip some DFS nodes', async () => {
|
||||
const editor: OutlineEditor = testEnv.editor;
|
||||
let expectedKeys: Array<NodeKey> = [];
|
||||
await editor.update((view: View) => {
|
||||
const root = view.getRoot();
|
||||
const paragraph1 = createParagraphNode();
|
||||
const block1 = createTestBlockNode();
|
||||
const block2 = createTestBlockNode();
|
||||
const block3 = createTestBlockNode();
|
||||
root.append(paragraph1);
|
||||
paragraph1.append(block1);
|
||||
paragraph1.append(block2);
|
||||
paragraph1.append(block3);
|
||||
|
||||
expectedKeys = [root.getKey(), paragraph1.getKey(), block3.getKey()];
|
||||
});
|
||||
|
||||
const dfsKeys = [];
|
||||
await editor.update((view: View) => {
|
||||
const root = view.getRoot();
|
||||
dfs(root, (node: OutlineNode) => {
|
||||
dfsKeys.push(node.getKey());
|
||||
if (isParagraphNode(node)) {
|
||||
return (
|
||||
node.getLastChild() && node.getLastChild().getPreviousSibling()
|
||||
);
|
||||
}
|
||||
return node;
|
||||
});
|
||||
});
|
||||
expect(dfsKeys).toEqual(expectedKeys);
|
||||
});
|
||||
});
|
||||
});
|
@ -143,6 +143,12 @@ async function build(name, inputFile, outputFile) {
|
||||
'packages/outline/src/helpers/OutlineKeyHelpers',
|
||||
),
|
||||
},
|
||||
{
|
||||
find: isWWW ? 'Outline/NodeHelpers' : 'outline/NodeHelpers',
|
||||
replacement: path.resolve(
|
||||
'packages/outline/src/helpers/OutlineNodeHelpers',
|
||||
),
|
||||
},
|
||||
{
|
||||
find: isWWW ? 'Outline/TextHelpers' : 'outline/TextHelpers',
|
||||
replacement: path.resolve(
|
||||
|
Reference in New Issue
Block a user