UNPKG

@blocknote/core

Version:

A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.

572 lines (491 loc) 16.1 kB
import { describe, expect, it, beforeEach } from "vitest"; import { setupTestEnv } from "./blockManipulation/setupTestEnv.js"; import { getBlocksChangedByTransaction } from "./getBlocksChangedByTransaction.js"; import { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; const getEditor = setupTestEnv(); describe("getBlocksChangedByTransaction", () => { let editor: BlockNoteEditor; beforeEach(() => { editor = getEditor(); }); it("should return the correct blocks changed by a transaction", () => { const blocksChanged = editor.transact((tr) => { return getBlocksChangedByTransaction(tr); }); expect(blocksChanged).toEqual([]); }); it("should return blocks inserted by a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.insertBlocks([{ type: "paragraph" }], "paragraph-0", "after"); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-inserted.json", ); }); it("should return nested blocks inserted by a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.insertBlocks( [ { type: "paragraph", children: [{ type: "paragraph", content: "Nested" }], }, ], "paragraph-0", "after", ); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-inserted-nested.json", ); }); it("should return blocks deleted by a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.removeBlocks(["paragraph-0"]); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-deleted.json", ); }); it("should return deeply nested blocks deleted by a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.removeBlocks(["double-nested-paragraph-0"]); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-deleted-nested-deep.json", ); }); it("should return nested blocks deleted by a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.removeBlocks(["nested-paragraph-0"]); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-deleted-nested.json", ); }); it("should return blocks updated by a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.updateBlock("paragraph-0", { props: { backgroundColor: "red", }, }); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-updated.json", ); }); it("should return nested blocks updated by a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.updateBlock("nested-paragraph-0", { props: { backgroundColor: "red", }, }); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-updated-nested.json", ); }); it("should return deeply nested blocks updated by a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.updateBlock("double-nested-paragraph-0", { content: "Example Text", }); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-updated-nested-deep.json", ); }); it("should return multiple nested blocks updated by a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.updateBlock("nested-paragraph-0", { props: { backgroundColor: "red", }, }); editor.updateBlock("double-nested-paragraph-0", { content: "Example Text", }); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-updated-nested-multiple.json", ); }); it("should only return a single block, if multiple updates change a single block in a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.updateBlock("paragraph-0", { props: { backgroundColor: "red", }, }); editor.updateBlock("paragraph-0", { props: { backgroundColor: "blue", }, }); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-updated-single.json", ); }); it("should return multiple blocks, if multiple updates change multiple blocks in a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.updateBlock("paragraph-0", { props: { backgroundColor: "red", }, }); editor.updateBlock("paragraph-1", { props: { backgroundColor: "blue", }, }); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-updated-multiple.json", ); }); it("should return multiple blocks, if multiple inserts add new blocks in a transaction", async () => { const blocksChanged = editor.transact((tr) => { editor.insertBlocks( [{ type: "paragraph", content: "ABC" }], "paragraph-0", "after", ); editor.insertBlocks( [{ type: "paragraph", content: "DEF" }], "paragraph-1", "after", ); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-updated-multiple-insert.json", ); }); it("should return blocks which have had content inserted into them", async () => { const blocksChanged = editor.transact((tr) => { editor.setTextCursorPosition("paragraph-2", "start"); editor.insertInlineContent("Hello"); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-updated-content-inserted.json", ); }); it("should return blocks which have been indented", async () => { editor.replaceBlocks(editor.document, [ { id: "paragraph-with-children", type: "paragraph", content: "A", children: [ { id: "nested-paragraph-0", type: "paragraph", content: "B", children: [], }, { id: "double-nested-paragraph-0", type: "paragraph", content: "C", }, ], }, ]); const blocksChanged = editor.transact((tr) => { editor.setTextCursorPosition("double-nested-paragraph-0", "start"); editor.nestBlock(); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-indented-changed.json", ); }); it("should return blocks which have been outdented", async () => { editor.replaceBlocks(editor.document, [ { id: "paragraph-with-children", type: "paragraph", content: "A", children: [ { id: "nested-paragraph-0", type: "paragraph", content: "B", children: [ { id: "double-nested-paragraph-0", type: "paragraph", content: "C", }, ], }, ], }, ]); // This test is different from the other tests because it uses the onChange hook to get the blocks changed // This is because unnesting a block is not allowed within a transaction let blocksChanged: any = null; const unsubscribe = editor.onChange((_e, { getChanges }) => { blocksChanged = getChanges(); }); // Make the change editor.setTextCursorPosition("double-nested-paragraph-0", "start"); editor.unnestBlock(); // Clean up if (unsubscribe) { unsubscribe(); } await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-outdented-changed.json", ); }); it("should return blocks which have been moved to a different parent", async () => { editor.replaceBlocks(editor.document, [ { id: "parent-1", type: "paragraph", content: "Parent 1", children: [ { id: "child-1", type: "paragraph", content: "Child 1", }, ], }, { id: "parent-2", type: "paragraph", content: "Parent 2", children: [], }, ]); const blocksChanged = editor.transact((tr) => { const childBlock = editor.getBlock("child-1"); editor.removeBlocks(["child-1"]); editor.insertBlocks([{ ...childBlock }], "parent-2", "after"); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-moved-to-different-parent.json", ); }); it("should return blocks which have been moved to root level", async () => { editor.replaceBlocks(editor.document, [ { id: "parent", type: "paragraph", content: "Parent", children: [ { id: "child", type: "paragraph", content: "Child", }, ], }, ]); const blocksChanged = editor.transact((tr) => { const childBlock = editor.getBlock("child"); editor.removeBlocks(["child"]); editor.insertBlocks([{ ...childBlock }], "parent", "after"); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-moved-to-root-level.json", ); }); it("should return blocks which have been moved deeper into nesting", async () => { editor.replaceBlocks(editor.document, [ { id: "root", type: "paragraph", content: "Root", children: [ { id: "level-1", type: "paragraph", content: "Level 1", children: [ { id: "level-2", type: "paragraph", content: "Level 2", }, ], }, { id: "target", type: "paragraph", content: "Target", }, ], }, ]); const blocksChanged = editor.transact((tr) => { const targetBlock = editor.getBlock("target"); editor.removeBlocks(["target"]); editor.insertBlocks([{ ...targetBlock }], "level-2", "after"); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-moved-deeper-into-nesting.json", ); }); it("should return multiple blocks when multiple blocks are moved in the same transaction", async () => { editor.replaceBlocks(editor.document, [ { id: "parent-1", type: "paragraph", content: "Parent 1", children: [ { id: "child-1", type: "paragraph", content: "Child 1", }, { id: "child-2", type: "paragraph", content: "Child 2", }, ], }, { id: "parent-2", type: "paragraph", content: "Parent 2", children: [], }, ]); const blocksChanged = editor.transact((tr) => { const child1Block = editor.getBlock("child-1"); const child2Block = editor.getBlock("child-2"); editor.removeBlocks(["child-1", "child-2"]); editor.insertBlocks( [{ ...child1Block }, { ...child2Block }], "parent-2", "after", ); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-moved-multiple-in-same-transaction.json", ); }); it("should return blocks which have been moved up or down in the same transaction", async () => { editor.replaceBlocks(editor.document, [ { id: "top", type: "paragraph", content: "Top", }, { id: "middle", type: "paragraph", content: "Middle", }, { id: "bottom", type: "paragraph", content: "Bottom", }, ]); const blocksChanged = editor.transact((tr) => { editor.setTextCursorPosition("top"); editor.moveBlocksDown(); return getBlocksChangedByTransaction(tr); }); // Should report a single minimal move within the same parent await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-moved-up-down-in-same-transaction.json", ); }); it("should detect moving the bottom block up within the same parent", async () => { editor.replaceBlocks(editor.document, [ { id: "top", type: "paragraph", content: "Top" }, { id: "middle", type: "paragraph", content: "Middle" }, { id: "bottom", type: "paragraph", content: "Bottom" }, ]); const blocksChanged = editor.transact((tr) => { editor.setTextCursorPosition("bottom"); editor.moveBlocksUp(); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-moved-up-down-in-same-parent.json", ); }); it("should detect moving a block down twice within the same parent as a single move", async () => { editor.replaceBlocks(editor.document, [ { id: "a", type: "paragraph", content: "A" }, { id: "b", type: "paragraph", content: "B" }, { id: "c", type: "paragraph", content: "C" }, ]); const blocksChanged = editor.transact((tr) => { editor.setTextCursorPosition("a"); editor.moveBlocksDown(); editor.moveBlocksDown(); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-moved-down-twice-in-same-parent.json", ); }); it("should detect nested sibling reorder within the same parent", async () => { editor.replaceBlocks(editor.document, [ { id: "parent", type: "paragraph", content: "Parent", children: [ { id: "child-a", type: "paragraph", content: "A" }, { id: "child-b", type: "paragraph", content: "B" }, { id: "child-c", type: "paragraph", content: "C" }, ], }, { id: "sibling", type: "paragraph", content: "S" }, ]); const blocksChanged = editor.transact((tr) => { editor.setTextCursorPosition("child-a"); editor.moveBlocksDown(); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-moved-nested-sibling-reorder.json", ); }); it("should not report moves when an insert changes sibling order", async () => { editor.replaceBlocks(editor.document, [ { id: "a", type: "paragraph", content: "A" }, { id: "b", type: "paragraph", content: "B" }, { id: "c", type: "paragraph", content: "C" }, ]); const blocksChanged = editor.transact((tr) => { editor.insertBlocks( [{ id: "x", type: "paragraph", content: "X" }], "a", "after", ); return getBlocksChangedByTransaction(tr); }); await expect(blocksChanged).toMatchFileSnapshot( "__snapshots__/blocks-moved-insert-changes-sibling-order.json", ); }); });