From b1892bc8a8f9962d2dca86035d18fecdb1d87260 Mon Sep 17 00:00:00 2001 From: KyleMit Date: Thu, 7 Jul 2022 09:52:30 -0400 Subject: [PATCH 01/14] allow any block inside list item --- src/rich-text/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rich-text/schema.ts b/src/rich-text/schema.ts index 635c53a4..7a8d2409 100644 --- a/src/rich-text/schema.ts +++ b/src/rich-text/schema.ts @@ -169,7 +169,7 @@ const nodes: { }, list_item: { - content: "paragraph block*", + content: "block+", defining: true, parseDOM: [{ tag: "li" }], toDOM() { From d6f002a85098f802ed46dc98e7fd4cbe30281745 Mon Sep 17 00:00:00 2001 From: KyleMit Date: Mon, 11 Jul 2022 15:04:01 -0400 Subject: [PATCH 02/14] add test coverage for inserting blockquote into list item --- src/rich-text/commands/index.ts | 288 +++++++++++++------------- test/rich-text/commands/index.test.ts | 38 ++++ 2 files changed, 182 insertions(+), 144 deletions(-) diff --git a/src/rich-text/commands/index.ts b/src/rich-text/commands/index.ts index 19ba89e2..88e979c1 100644 --- a/src/rich-text/commands/index.ts +++ b/src/rich-text/commands/index.ts @@ -37,7 +37,7 @@ import { _t } from "../../shared/localization"; export * from "./tables"; //TODO -function toggleWrapIn(nodeType: NodeType) { +export function toggleWrapIn(nodeType: NodeType) { const nodeCheck = nodeTypeActive(nodeType); const wrapInCommand = wrapIn(nodeType); @@ -426,161 +426,161 @@ export const createMenuEntries = ( schema: Schema, options: CommonViewOptions ): MenuCommandEntry[] => [ - headingDropdown(schema), - { - key: "toggleBold", - command: toggleMark(schema.marks.strong), - dom: makeMenuIcon( - "Bold", - _t("commands.bold", { shortcut: getShortcut("Mod-b") }), - "bold-btn" - ), - active: markActive(schema.marks.strong), - }, - { - key: "toggleEmphasis", - command: toggleMark(schema.marks.em), - dom: makeMenuIcon( - "Italic", - _t("commands.emphasis", { shortcut: getShortcut("Mod-i") }), - "italic-btn" - ), - active: markActive(schema.marks.em), - }, - { - key: "toggleCode", - command: toggleMark(schema.marks.code), - dom: makeMenuIcon( - "Code", - _t("commands.inline_code", { shortcut: getShortcut("Mod-k") }), - "code-btn" - ), - active: markActive(schema.marks.code), - }, - addIf( + headingDropdown(schema), { - key: "toggleStrike", - command: toggleMark(schema.marks.strike), + key: "toggleBold", + command: toggleMark(schema.marks.strong), dom: makeMenuIcon( - "Strikethrough", - _t("commands.strikethrough"), - "strike-btn" + "Bold", + _t("commands.bold", { shortcut: getShortcut("Mod-b") }), + "bold-btn" ), - active: markActive(schema.marks.strike), + active: markActive(schema.marks.strong), }, - options.parserFeatures.extraEmphasis - ), - makeMenuSpacerEntry(), - { - key: "toggleLink", - command: insertLinkCommand, - dom: makeMenuIcon( - "Link", - _t("commands.link", { shortcut: getShortcut("Mod-l") }), - "insert-link-btn" - ), - }, - { - key: "toggleBlockquote", - command: toggleWrapIn(schema.nodes.blockquote), - dom: makeMenuIcon( - "Quote", - _t("commands.blockquote", { shortcut: getShortcut("Mod-q") }), - "blockquote-btn" - ), - active: nodeTypeActive(schema.nodes.blockquote), - }, - { - key: "toggleCodeblock", - command: toggleBlockType(schema.nodes.code_block), - dom: makeMenuIcon( - "Codeblock", - _t("commands.code_block", { shortcut: getShortcut("Mod-m") }), - "code-block-btn" - ), - active: nodeTypeActive(schema.nodes.code_block), - }, - addIf( { - key: "insertImage", - command: insertImageCommand, + key: "toggleEmphasis", + command: toggleMark(schema.marks.em), dom: makeMenuIcon( - "Image", - _t("commands.image", { shortcut: getShortcut("Mod-g") }), - "insert-image-btn" + "Italic", + _t("commands.emphasis", { shortcut: getShortcut("Mod-i") }), + "italic-btn" ), + active: markActive(schema.marks.em), }, - !!options.imageUpload?.handler - ), - addIf( { - key: "insertTable", - command: insertTableCommand, + key: "toggleCode", + command: toggleMark(schema.marks.code), dom: makeMenuIcon( - "Table", - _t("commands.table_insert", { shortcut: getShortcut("Mod-e") }), - "insert-table-btn" + "Code", + _t("commands.inline_code", { shortcut: getShortcut("Mod-k") }), + "code-btn" ), - visible: (state: EditorState) => - !inTable(state.schema, state.selection), + active: markActive(schema.marks.code), }, - options.parserFeatures.tables - ), - addIf(tableDropdown(), options.parserFeatures.tables), - makeMenuSpacerEntry(), - { - key: "toggleOrderedList", - command: toggleWrapIn(schema.nodes.ordered_list), - dom: makeMenuIcon( - "OrderedList", - _t("commands.ordered_list", { shortcut: getShortcut("Mod-o") }), - "numbered-list-btn" - ), - active: nodeTypeActive(schema.nodes.ordered_list), - }, - { - key: "toggleUnorderedList", - command: toggleWrapIn(schema.nodes.bullet_list), - dom: makeMenuIcon( - "UnorderedList", - _t("commands.unordered_list", { shortcut: getShortcut("Mod-u") }), - "bullet-list-btn" - ), - active: nodeTypeActive(schema.nodes.bullet_list), - }, - { - key: "insertRule", - command: insertHorizontalRuleCommand, - dom: makeMenuIcon( - "HorizontalRule", - _t("commands.horizontal_rule", { shortcut: getShortcut("Mod-r") }), - "horizontal-rule-btn" + addIf( + { + key: "toggleStrike", + command: toggleMark(schema.marks.strike), + dom: makeMenuIcon( + "Strikethrough", + _t("commands.strikethrough"), + "strike-btn" + ), + active: markActive(schema.marks.strike), + }, + options.parserFeatures.extraEmphasis ), - }, - makeMenuSpacerEntry(() => false, ["sm:d-inline-block"]), - { - key: "undo", - command: undo, - dom: makeMenuIcon( - "Undo", - _t("commands.undo", { shortcut: getShortcut("Mod-z") }), - "undo-btn", - ["sm:d-inline-block"] + makeMenuSpacerEntry(), + { + key: "toggleLink", + command: insertLinkCommand, + dom: makeMenuIcon( + "Link", + _t("commands.link", { shortcut: getShortcut("Mod-l") }), + "insert-link-btn" + ), + }, + { + key: "toggleBlockquote", + command: toggleWrapIn(schema.nodes.blockquote), + dom: makeMenuIcon( + "Quote", + _t("commands.blockquote", { shortcut: getShortcut("Mod-q") }), + "blockquote-btn" + ), + active: nodeTypeActive(schema.nodes.blockquote), + }, + { + key: "toggleCodeblock", + command: toggleBlockType(schema.nodes.code_block), + dom: makeMenuIcon( + "Codeblock", + _t("commands.code_block", { shortcut: getShortcut("Mod-m") }), + "code-block-btn" + ), + active: nodeTypeActive(schema.nodes.code_block), + }, + addIf( + { + key: "insertImage", + command: insertImageCommand, + dom: makeMenuIcon( + "Image", + _t("commands.image", { shortcut: getShortcut("Mod-g") }), + "insert-image-btn" + ), + }, + !!options.imageUpload?.handler ), - visible: () => false, - }, - { - key: "redo", - command: redo, - dom: makeMenuIcon( - "Refresh", - _t("commands.redo", { shortcut: getShortcut("Mod-y") }), - "redo-btn", - ["sm:d-inline-block"] + addIf( + { + key: "insertTable", + command: insertTableCommand, + dom: makeMenuIcon( + "Table", + _t("commands.table_insert", { shortcut: getShortcut("Mod-e") }), + "insert-table-btn" + ), + visible: (state: EditorState) => + !inTable(state.schema, state.selection), + }, + options.parserFeatures.tables ), - visible: () => false, - }, - makeMenuSpacerEntry(), - //TODO eventually this will mimic the "help" dropdown in the prod editor - makeMenuLinkEntry("Help", _t("commands.help"), options.editorHelpLink), -]; + addIf(tableDropdown(), options.parserFeatures.tables), + makeMenuSpacerEntry(), + { + key: "toggleOrderedList", + command: toggleWrapIn(schema.nodes.ordered_list), + dom: makeMenuIcon( + "OrderedList", + _t("commands.ordered_list", { shortcut: getShortcut("Mod-o") }), + "numbered-list-btn" + ), + active: nodeTypeActive(schema.nodes.ordered_list), + }, + { + key: "toggleUnorderedList", + command: toggleWrapIn(schema.nodes.bullet_list), + dom: makeMenuIcon( + "UnorderedList", + _t("commands.unordered_list", { shortcut: getShortcut("Mod-u") }), + "bullet-list-btn" + ), + active: nodeTypeActive(schema.nodes.bullet_list), + }, + { + key: "insertRule", + command: insertHorizontalRuleCommand, + dom: makeMenuIcon( + "HorizontalRule", + _t("commands.horizontal_rule", { shortcut: getShortcut("Mod-r") }), + "horizontal-rule-btn" + ), + }, + makeMenuSpacerEntry(() => false, ["sm:d-inline-block"]), + { + key: "undo", + command: undo, + dom: makeMenuIcon( + "Undo", + _t("commands.undo", { shortcut: getShortcut("Mod-z") }), + "undo-btn", + ["sm:d-inline-block"] + ), + visible: () => false, + }, + { + key: "redo", + command: redo, + dom: makeMenuIcon( + "Refresh", + _t("commands.redo", { shortcut: getShortcut("Mod-y") }), + "redo-btn", + ["sm:d-inline-block"] + ), + visible: () => false, + }, + makeMenuSpacerEntry(), + //TODO eventually this will mimic the "help" dropdown in the prod editor + makeMenuLinkEntry("Help", _t("commands.help"), options.editorHelpLink), + ]; diff --git a/test/rich-text/commands/index.test.ts b/test/rich-text/commands/index.test.ts index 9e4089d5..c78f9cd6 100644 --- a/test/rich-text/commands/index.test.ts +++ b/test/rich-text/commands/index.test.ts @@ -3,6 +3,7 @@ import { exitInclusiveMarkCommand, insertHorizontalRuleCommand, toggleHeadingLevel, + toggleWrapIn, } from "../../../src/rich-text/commands"; import { applySelection, @@ -193,6 +194,43 @@ describe("commands", () => { ], }); }); + + it("should toggle blockquote within list item", () => { + const state = applySelection( + createState("", []), + 3 + ); + const resolvedNode = state.selection.$from; + // default li child is paragraph + expect(resolvedNode.node().type.name).toBe("paragraph"); + + const toggleBlockQuote = toggleWrapIn(state.schema.nodes.blockquote); + const { newState, isValid } = executeTransaction( + state, + toggleBlockQuote + ); + + expect(isValid).toBeTruthy(); + expect(newState.doc).toMatchNodeTree({ + "type.name": "doc", + "content": [{ + "type.name": "bullet_list", + content: [{ + "type.name": "list_item", + content: [{ + "type.name": "blockquote", + content: [{ + "type.name": "paragraph", + childCount: 1 + }] + } + ] + } + ] + }, + ], + }); + }); }); describe("insertHorizontalRuleCommand", () => { From 3392680f4786ac8fa5f746e30c661e9af46a5907 Mon Sep 17 00:00:00 2001 From: KyleMit Date: Mon, 11 Jul 2022 15:13:53 -0400 Subject: [PATCH 03/14] linting --- src/rich-text/commands/index.ts | 286 +++++++++++++------------- test/rich-text/commands/index.test.ts | 39 ++-- 2 files changed, 166 insertions(+), 159 deletions(-) diff --git a/src/rich-text/commands/index.ts b/src/rich-text/commands/index.ts index 88e979c1..c82ec29b 100644 --- a/src/rich-text/commands/index.ts +++ b/src/rich-text/commands/index.ts @@ -426,161 +426,161 @@ export const createMenuEntries = ( schema: Schema, options: CommonViewOptions ): MenuCommandEntry[] => [ - headingDropdown(schema), - { - key: "toggleBold", - command: toggleMark(schema.marks.strong), - dom: makeMenuIcon( - "Bold", - _t("commands.bold", { shortcut: getShortcut("Mod-b") }), - "bold-btn" - ), - active: markActive(schema.marks.strong), - }, - { - key: "toggleEmphasis", - command: toggleMark(schema.marks.em), - dom: makeMenuIcon( - "Italic", - _t("commands.emphasis", { shortcut: getShortcut("Mod-i") }), - "italic-btn" - ), - active: markActive(schema.marks.em), - }, - { - key: "toggleCode", - command: toggleMark(schema.marks.code), - dom: makeMenuIcon( - "Code", - _t("commands.inline_code", { shortcut: getShortcut("Mod-k") }), - "code-btn" - ), - active: markActive(schema.marks.code), - }, - addIf( - { - key: "toggleStrike", - command: toggleMark(schema.marks.strike), - dom: makeMenuIcon( - "Strikethrough", - _t("commands.strikethrough"), - "strike-btn" - ), - active: markActive(schema.marks.strike), - }, - options.parserFeatures.extraEmphasis + headingDropdown(schema), + { + key: "toggleBold", + command: toggleMark(schema.marks.strong), + dom: makeMenuIcon( + "Bold", + _t("commands.bold", { shortcut: getShortcut("Mod-b") }), + "bold-btn" ), - makeMenuSpacerEntry(), - { - key: "toggleLink", - command: insertLinkCommand, - dom: makeMenuIcon( - "Link", - _t("commands.link", { shortcut: getShortcut("Mod-l") }), - "insert-link-btn" - ), - }, - { - key: "toggleBlockquote", - command: toggleWrapIn(schema.nodes.blockquote), - dom: makeMenuIcon( - "Quote", - _t("commands.blockquote", { shortcut: getShortcut("Mod-q") }), - "blockquote-btn" - ), - active: nodeTypeActive(schema.nodes.blockquote), - }, - { - key: "toggleCodeblock", - command: toggleBlockType(schema.nodes.code_block), - dom: makeMenuIcon( - "Codeblock", - _t("commands.code_block", { shortcut: getShortcut("Mod-m") }), - "code-block-btn" - ), - active: nodeTypeActive(schema.nodes.code_block), - }, - addIf( - { - key: "insertImage", - command: insertImageCommand, - dom: makeMenuIcon( - "Image", - _t("commands.image", { shortcut: getShortcut("Mod-g") }), - "insert-image-btn" - ), - }, - !!options.imageUpload?.handler + active: markActive(schema.marks.strong), + }, + { + key: "toggleEmphasis", + command: toggleMark(schema.marks.em), + dom: makeMenuIcon( + "Italic", + _t("commands.emphasis", { shortcut: getShortcut("Mod-i") }), + "italic-btn" ), - addIf( - { - key: "insertTable", - command: insertTableCommand, - dom: makeMenuIcon( - "Table", - _t("commands.table_insert", { shortcut: getShortcut("Mod-e") }), - "insert-table-btn" - ), - visible: (state: EditorState) => - !inTable(state.schema, state.selection), - }, - options.parserFeatures.tables + active: markActive(schema.marks.em), + }, + { + key: "toggleCode", + command: toggleMark(schema.marks.code), + dom: makeMenuIcon( + "Code", + _t("commands.inline_code", { shortcut: getShortcut("Mod-k") }), + "code-btn" ), - addIf(tableDropdown(), options.parserFeatures.tables), - makeMenuSpacerEntry(), - { - key: "toggleOrderedList", - command: toggleWrapIn(schema.nodes.ordered_list), - dom: makeMenuIcon( - "OrderedList", - _t("commands.ordered_list", { shortcut: getShortcut("Mod-o") }), - "numbered-list-btn" - ), - active: nodeTypeActive(schema.nodes.ordered_list), - }, - { - key: "toggleUnorderedList", - command: toggleWrapIn(schema.nodes.bullet_list), - dom: makeMenuIcon( - "UnorderedList", - _t("commands.unordered_list", { shortcut: getShortcut("Mod-u") }), - "bullet-list-btn" - ), - active: nodeTypeActive(schema.nodes.bullet_list), - }, + active: markActive(schema.marks.code), + }, + addIf( { - key: "insertRule", - command: insertHorizontalRuleCommand, + key: "toggleStrike", + command: toggleMark(schema.marks.strike), dom: makeMenuIcon( - "HorizontalRule", - _t("commands.horizontal_rule", { shortcut: getShortcut("Mod-r") }), - "horizontal-rule-btn" + "Strikethrough", + _t("commands.strikethrough"), + "strike-btn" ), + active: markActive(schema.marks.strike), }, - makeMenuSpacerEntry(() => false, ["sm:d-inline-block"]), + options.parserFeatures.extraEmphasis + ), + makeMenuSpacerEntry(), + { + key: "toggleLink", + command: insertLinkCommand, + dom: makeMenuIcon( + "Link", + _t("commands.link", { shortcut: getShortcut("Mod-l") }), + "insert-link-btn" + ), + }, + { + key: "toggleBlockquote", + command: toggleWrapIn(schema.nodes.blockquote), + dom: makeMenuIcon( + "Quote", + _t("commands.blockquote", { shortcut: getShortcut("Mod-q") }), + "blockquote-btn" + ), + active: nodeTypeActive(schema.nodes.blockquote), + }, + { + key: "toggleCodeblock", + command: toggleBlockType(schema.nodes.code_block), + dom: makeMenuIcon( + "Codeblock", + _t("commands.code_block", { shortcut: getShortcut("Mod-m") }), + "code-block-btn" + ), + active: nodeTypeActive(schema.nodes.code_block), + }, + addIf( { - key: "undo", - command: undo, + key: "insertImage", + command: insertImageCommand, dom: makeMenuIcon( - "Undo", - _t("commands.undo", { shortcut: getShortcut("Mod-z") }), - "undo-btn", - ["sm:d-inline-block"] + "Image", + _t("commands.image", { shortcut: getShortcut("Mod-g") }), + "insert-image-btn" ), - visible: () => false, }, + !!options.imageUpload?.handler + ), + addIf( { - key: "redo", - command: redo, + key: "insertTable", + command: insertTableCommand, dom: makeMenuIcon( - "Refresh", - _t("commands.redo", { shortcut: getShortcut("Mod-y") }), - "redo-btn", - ["sm:d-inline-block"] + "Table", + _t("commands.table_insert", { shortcut: getShortcut("Mod-e") }), + "insert-table-btn" ), - visible: () => false, + visible: (state: EditorState) => + !inTable(state.schema, state.selection), }, - makeMenuSpacerEntry(), - //TODO eventually this will mimic the "help" dropdown in the prod editor - makeMenuLinkEntry("Help", _t("commands.help"), options.editorHelpLink), - ]; + options.parserFeatures.tables + ), + addIf(tableDropdown(), options.parserFeatures.tables), + makeMenuSpacerEntry(), + { + key: "toggleOrderedList", + command: toggleWrapIn(schema.nodes.ordered_list), + dom: makeMenuIcon( + "OrderedList", + _t("commands.ordered_list", { shortcut: getShortcut("Mod-o") }), + "numbered-list-btn" + ), + active: nodeTypeActive(schema.nodes.ordered_list), + }, + { + key: "toggleUnorderedList", + command: toggleWrapIn(schema.nodes.bullet_list), + dom: makeMenuIcon( + "UnorderedList", + _t("commands.unordered_list", { shortcut: getShortcut("Mod-u") }), + "bullet-list-btn" + ), + active: nodeTypeActive(schema.nodes.bullet_list), + }, + { + key: "insertRule", + command: insertHorizontalRuleCommand, + dom: makeMenuIcon( + "HorizontalRule", + _t("commands.horizontal_rule", { shortcut: getShortcut("Mod-r") }), + "horizontal-rule-btn" + ), + }, + makeMenuSpacerEntry(() => false, ["sm:d-inline-block"]), + { + key: "undo", + command: undo, + dom: makeMenuIcon( + "Undo", + _t("commands.undo", { shortcut: getShortcut("Mod-z") }), + "undo-btn", + ["sm:d-inline-block"] + ), + visible: () => false, + }, + { + key: "redo", + command: redo, + dom: makeMenuIcon( + "Refresh", + _t("commands.redo", { shortcut: getShortcut("Mod-y") }), + "redo-btn", + ["sm:d-inline-block"] + ), + visible: () => false, + }, + makeMenuSpacerEntry(), + //TODO eventually this will mimic the "help" dropdown in the prod editor + makeMenuLinkEntry("Help", _t("commands.help"), options.editorHelpLink), +]; diff --git a/test/rich-text/commands/index.test.ts b/test/rich-text/commands/index.test.ts index c78f9cd6..00b299d4 100644 --- a/test/rich-text/commands/index.test.ts +++ b/test/rich-text/commands/index.test.ts @@ -204,7 +204,9 @@ describe("commands", () => { // default li child is paragraph expect(resolvedNode.node().type.name).toBe("paragraph"); - const toggleBlockQuote = toggleWrapIn(state.schema.nodes.blockquote); + const toggleBlockQuote = toggleWrapIn( + state.schema.nodes.blockquote + ); const { newState, isValid } = executeTransaction( state, toggleBlockQuote @@ -213,21 +215,26 @@ describe("commands", () => { expect(isValid).toBeTruthy(); expect(newState.doc).toMatchNodeTree({ "type.name": "doc", - "content": [{ - "type.name": "bullet_list", - content: [{ - "type.name": "list_item", - content: [{ - "type.name": "blockquote", - content: [{ - "type.name": "paragraph", - childCount: 1 - }] - } - ] - } - ] - }, + "content": [ + { + "type.name": "bullet_list", + "content": [ + { + "type.name": "list_item", + "content": [ + { + "type.name": "blockquote", + "content": [ + { + "type.name": "paragraph", + "childCount": 1, + }, + ], + }, + ], + }, + ], + }, ], }); }); From 44e645a82288a99a4a5791b706f030bcb450701f Mon Sep 17 00:00:00 2001 From: KyleMit Date: Thu, 14 Jul 2022 13:10:18 -0400 Subject: [PATCH 04/14] refactor(tests): add createBasicNodeTree test helper --- test/matchers.test.ts | 23 +++++++++++++++++++ test/matchers.ts | 53 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 test/matchers.test.ts diff --git a/test/matchers.test.ts b/test/matchers.test.ts new file mode 100644 index 00000000..07dd2fe7 --- /dev/null +++ b/test/matchers.test.ts @@ -0,0 +1,23 @@ +import { createBasicNodeTree } from "./matchers"; + +describe("createBasicNodeTree", () => { + it("creates nested tree", () => { + const input = "doc>blockquote>paragraph>1"; + const expected = { + "type.name": "doc", + "content": [ + { + "type.name": "blockquote", + "content": [ + { + "type.name": "paragraph", + "childCount": 1, + }, + ], + }, + ], + }; + const result = createBasicNodeTree(input); + expect(result).toEqual(expected); + }); +}); diff --git a/test/matchers.ts b/test/matchers.ts index 864ea525..f6e972ac 100644 --- a/test/matchers.ts +++ b/test/matchers.ts @@ -126,3 +126,56 @@ expect.extend({ }; }, }); + +export function createBasicNodeTree(input: string): CompareTree { + const branches = input + .split(">") + .map((x) => x.trim()) + .reverse(); + if (!branches.length) return {}; + + let branch = branches.pop(); + const root: CompareTree = { + "type.name": branch, + }; + + branch = branches.pop(); + let node: CompareTree = root; + while (branch) { + if (isNaN(Number(branch))) { + const child = { + "type.name": branch, + }; + node.content = [child]; + + node = child; + branch = branches.pop(); + } else { + node.childCount = Number(branch); + return root; + } + } + return root; +} + +describe("createBasicNodeTree", () => { + it("creates nested tree", () => { + const input = "doc>blockquote>paragraph>1"; + const expected = { + "type.name": "doc", + "content": [ + { + "type.name": "blockquote", + "content": [ + { + "type.name": "paragraph", + "childCount": 1, + }, + ], + }, + ], + }; + const result = createBasicNodeTree(input); + expect(result).toEqual(expected); + }); +}); From 12360c1e3e5adcee4baf7ffe2830754ead7a618c Mon Sep 17 00:00:00 2001 From: KyleMit Date: Thu, 14 Jul 2022 13:12:06 -0400 Subject: [PATCH 05/14] test(rich-text-editor) add toggleWrapIn tests --- src/rich-text/commands/index.ts | 23 +++++++-- test/rich-text/commands/index.test.ts | 74 ++++++++++++++++++--------- 2 files changed, 70 insertions(+), 27 deletions(-) diff --git a/src/rich-text/commands/index.ts b/src/rich-text/commands/index.ts index c82ec29b..2f0b0bcd 100644 --- a/src/rich-text/commands/index.ts +++ b/src/rich-text/commands/index.ts @@ -1,7 +1,12 @@ import { setBlockType, toggleMark, wrapIn } from "prosemirror-commands"; import { redo, undo } from "prosemirror-history"; import { Mark, MarkType, NodeType, Schema } from "prosemirror-model"; -import { EditorState, TextSelection, Transaction } from "prosemirror-state"; +import { + Command, + EditorState, + TextSelection, + Transaction, +} from "prosemirror-state"; import { liftTarget } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; import { @@ -36,8 +41,20 @@ import { _t } from "../../shared/localization"; export * from "./tables"; -//TODO -export function toggleWrapIn(nodeType: NodeType) { +/** + * Builds a command which toggle the passed in node type + * @param nodeType + * @returns A command to toggle the node type + * Commands are functions that take a state and an optional + * transaction dispatch function and... + * + * - determine whether they apply to this state + * - if not, return false + * - if `dispatch` was passed, perform their effect, possibly by + * passing a transaction to `dispatch` + * - return true + */ +export function toggleWrapIn(nodeType: NodeType): Command { const nodeCheck = nodeTypeActive(nodeType); const wrapInCommand = wrapIn(nodeType); diff --git a/test/rich-text/commands/index.test.ts b/test/rich-text/commands/index.test.ts index 00b299d4..f4883eb8 100644 --- a/test/rich-text/commands/index.test.ts +++ b/test/rich-text/commands/index.test.ts @@ -11,6 +11,7 @@ import { testRichTextSchema, } from "../test-helpers"; import "../../matchers"; +import { createBasicNodeTree } from "../../matchers"; function getEndOfNode(state: EditorState, nodePos: number) { let from = nodePos; @@ -194,6 +195,51 @@ describe("commands", () => { ], }); }); + }); + + describe("toggleWrapIn", () => { + it("should apply blockquote within paragraph", () => { + const state = applySelection(createState("

quote

", []), 3); + expect(state.doc).toMatchNodeTree( + createBasicNodeTree("doc>paragraph>1") + ); + + const toggleBlockQuote = toggleWrapIn( + state.schema.nodes.blockquote + ); + const { newState, isValid } = executeTransaction( + state, + toggleBlockQuote + ); + + expect(isValid).toBeTruthy(); + expect(newState.doc).toMatchNodeTree( + createBasicNodeTree("doc>blockquote>paragraph>1") + ); + }); + + it("should remove blockquote within blockquote", () => { + const state = applySelection( + createState("
quote
", []), + 3 + ); + expect(state.doc).toMatchNodeTree( + createBasicNodeTree("doc>blockquote>paragraph>1") + ); + + const toggleBlockQuote = toggleWrapIn( + state.schema.nodes.blockquote + ); + const { newState, isValid } = executeTransaction( + state, + toggleBlockQuote + ); + + expect(isValid).toBeTruthy(); + expect(newState.doc).toMatchNodeTree( + createBasicNodeTree("doc>paragraph>1") + ); + }); it("should toggle blockquote within list item", () => { const state = applySelection( @@ -213,30 +259,10 @@ describe("commands", () => { ); expect(isValid).toBeTruthy(); - expect(newState.doc).toMatchNodeTree({ - "type.name": "doc", - "content": [ - { - "type.name": "bullet_list", - "content": [ - { - "type.name": "list_item", - "content": [ - { - "type.name": "blockquote", - "content": [ - { - "type.name": "paragraph", - "childCount": 1, - }, - ], - }, - ], - }, - ], - }, - ], - }); + const expected = createBasicNodeTree( + "doc>bullet_list>list_item>blockquote>paragraph>1" + ); + expect(newState.doc).toMatchNodeTree(expected); }); }); From feae786173dca1fe4238694b2dddedf43aa8ba25 Mon Sep 17 00:00:00 2001 From: KyleMit Date: Thu, 14 Jul 2022 13:22:47 -0400 Subject: [PATCH 06/14] chore(tests): remove unused test method --- test/matchers.ts | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/test/matchers.ts b/test/matchers.ts index f6e972ac..d183a7fb 100644 --- a/test/matchers.ts +++ b/test/matchers.ts @@ -157,25 +157,3 @@ export function createBasicNodeTree(input: string): CompareTree { } return root; } - -describe("createBasicNodeTree", () => { - it("creates nested tree", () => { - const input = "doc>blockquote>paragraph>1"; - const expected = { - "type.name": "doc", - "content": [ - { - "type.name": "blockquote", - "content": [ - { - "type.name": "paragraph", - "childCount": 1, - }, - ], - }, - ], - }; - const result = createBasicNodeTree(input); - expect(result).toEqual(expected); - }); -}); From 526f3c8e1ab8ce3fba4b36efd01c22ea60bf65db Mon Sep 17 00:00:00 2001 From: KyleMit Date: Thu, 14 Jul 2022 13:30:47 -0400 Subject: [PATCH 07/14] docs(commands): improved toggleWrapIn jsdoc comment --- src/rich-text/commands/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rich-text/commands/index.ts b/src/rich-text/commands/index.ts index 2f0b0bcd..444e1000 100644 --- a/src/rich-text/commands/index.ts +++ b/src/rich-text/commands/index.ts @@ -42,9 +42,9 @@ import { _t } from "../../shared/localization"; export * from "./tables"; /** - * Builds a command which toggle the passed in node type + * Builds a command which wraps/unwraps the current selection with the passed in node type * @param nodeType - * @returns A command to toggle the node type + * @returns A command to toggle the wrapper node * Commands are functions that take a state and an optional * transaction dispatch function and... * From 63f7965899899a5a98307dbb1a937377443d31f7 Mon Sep 17 00:00:00 2001 From: KyleMit Date: Thu, 14 Jul 2022 13:33:31 -0400 Subject: [PATCH 08/14] docs(matchers): add doc comment for createBasicNodeTree --- test/matchers.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/matchers.ts b/test/matchers.ts index d183a7fb..ca127380 100644 --- a/test/matchers.ts +++ b/test/matchers.ts @@ -127,6 +127,11 @@ expect.extend({ }, }); +/** + * Creates a simple nested node tree with the passed in path + * @param input valid node names separated by a `>` symbol, and optionally ending in a number of child nodes + * @returns a CompareTree to be used with `toMatchNodeTree` + */ export function createBasicNodeTree(input: string): CompareTree { const branches = input .split(">") From acffe456b4f9c9f69a3f0699c5f38ce50d46c0c5 Mon Sep 17 00:00:00 2001 From: KyleMit Date: Mon, 18 Jul 2022 11:41:53 -0400 Subject: [PATCH 09/14] move toMatchNodeTreeString to custom matchers --- src/rich-text/commands/index.ts | 2 +- test/matchers.ts | 46 ++++++++++++++++++--------- test/rich-text/commands/index.test.ts | 22 +++---------- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/rich-text/commands/index.ts b/src/rich-text/commands/index.ts index 444e1000..c8fead22 100644 --- a/src/rich-text/commands/index.ts +++ b/src/rich-text/commands/index.ts @@ -43,7 +43,7 @@ export * from "./tables"; /** * Builds a command which wraps/unwraps the current selection with the passed in node type - * @param nodeType + * @param nodeType the type of node to wrap the selection in * @returns A command to toggle the wrapper node * Commands are functions that take a state and an optional * transaction dispatch function and... diff --git a/test/matchers.ts b/test/matchers.ts index ca127380..64a98b39 100644 --- a/test/matchers.ts +++ b/test/matchers.ts @@ -1,5 +1,4 @@ import { Node as ProsemirrorNode } from "prosemirror-model"; - /* * NOTE: Add all exposed matches to `expect.extend` at the bottom * and add all the definitions below so TS can pick them up without error @@ -11,6 +10,7 @@ declare global { namespace jest { interface Matchers { toMatchNodeTree(tree: CompareTree): R; + toMatchNodeTreeString(tree: string): R; } } } @@ -113,20 +113,6 @@ function expectNodeTree(doc: ProsemirrorNode, tree: CompareTree): void { } } -expect.extend({ - toMatchNodeTree(doc: ProsemirrorNode, tree: CompareTree) { - // call the backing expect wrapper - expectNodeTree(doc, tree); - - return { - // no error message on pass - we don't support "not" here - message: () => null, - // always assume a pass, expectNodeTree will throw an exception if it doesn't - pass: true, - }; - }, -}); - /** * Creates a simple nested node tree with the passed in path * @param input valid node names separated by a `>` symbol, and optionally ending in a number of child nodes @@ -162,3 +148,33 @@ export function createBasicNodeTree(input: string): CompareTree { } return root; } + +function toMatchNodeTree( + doc: ProsemirrorNode, + tree: CompareTree +): jest.CustomMatcherResult { + // call the backing expect wrapper + expectNodeTree(doc, tree); + + return { + // no error message on pass - we don't support "not" here + message: () => null, + // always assume a pass, expectNodeTree will throw an exception if it doesn't + pass: true, + }; +} + +expect.extend({ + toMatchNodeTree, + /** + * Matches doc against a CSS-like tree of nodes, separated by `>` + * @param doc the current document to match against + * @param tree a string of nodes, with an optional last number of children + * ex. "doc>blockquote>paragraph>1" + * ex. "doc>paragraph" + * @returns the expect result + */ + toMatchNodeTreeString(doc: ProsemirrorNode, tree: string) { + return toMatchNodeTree(doc, createBasicNodeTree(tree)); + }, +}); diff --git a/test/rich-text/commands/index.test.ts b/test/rich-text/commands/index.test.ts index f4883eb8..f586c362 100644 --- a/test/rich-text/commands/index.test.ts +++ b/test/rich-text/commands/index.test.ts @@ -11,7 +11,6 @@ import { testRichTextSchema, } from "../test-helpers"; import "../../matchers"; -import { createBasicNodeTree } from "../../matchers"; function getEndOfNode(state: EditorState, nodePos: number) { let from = nodePos; @@ -200,9 +199,7 @@ describe("commands", () => { describe("toggleWrapIn", () => { it("should apply blockquote within paragraph", () => { const state = applySelection(createState("

quote

", []), 3); - expect(state.doc).toMatchNodeTree( - createBasicNodeTree("doc>paragraph>1") - ); + expect(state.doc).toMatchNodeTreeString("doc>paragraph>1"); const toggleBlockQuote = toggleWrapIn( state.schema.nodes.blockquote @@ -213,9 +210,7 @@ describe("commands", () => { ); expect(isValid).toBeTruthy(); - expect(newState.doc).toMatchNodeTree( - createBasicNodeTree("doc>blockquote>paragraph>1") - ); + expect(newState.doc).toMatchNodeTreeString("doc>blockquote>paragraph>1"); }); it("should remove blockquote within blockquote", () => { @@ -223,9 +218,7 @@ describe("commands", () => { createState("
quote
", []), 3 ); - expect(state.doc).toMatchNodeTree( - createBasicNodeTree("doc>blockquote>paragraph>1") - ); + expect(state.doc).toMatchNodeTreeString("doc>blockquote>paragraph>1"); const toggleBlockQuote = toggleWrapIn( state.schema.nodes.blockquote @@ -236,9 +229,7 @@ describe("commands", () => { ); expect(isValid).toBeTruthy(); - expect(newState.doc).toMatchNodeTree( - createBasicNodeTree("doc>paragraph>1") - ); + expect(newState.doc).toMatchNodeTreeString("doc>paragraph>1"); }); it("should toggle blockquote within list item", () => { @@ -259,10 +250,7 @@ describe("commands", () => { ); expect(isValid).toBeTruthy(); - const expected = createBasicNodeTree( - "doc>bullet_list>list_item>blockquote>paragraph>1" - ); - expect(newState.doc).toMatchNodeTree(expected); + expect(newState.doc).toMatchNodeTreeString("doc>bullet_list>list_item>blockquote>paragraph>1"); }); }); From 4b6d813e1bc06b8263e2e1067ff3c4d6aad279a9 Mon Sep 17 00:00:00 2001 From: KyleMit Date: Mon, 18 Jul 2022 14:48:31 -0400 Subject: [PATCH 10/14] fix(formatting): prettier --write --- test/rich-text/commands/index.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/rich-text/commands/index.test.ts b/test/rich-text/commands/index.test.ts index f586c362..80949eca 100644 --- a/test/rich-text/commands/index.test.ts +++ b/test/rich-text/commands/index.test.ts @@ -210,7 +210,9 @@ describe("commands", () => { ); expect(isValid).toBeTruthy(); - expect(newState.doc).toMatchNodeTreeString("doc>blockquote>paragraph>1"); + expect(newState.doc).toMatchNodeTreeString( + "doc>blockquote>paragraph>1" + ); }); it("should remove blockquote within blockquote", () => { @@ -218,7 +220,9 @@ describe("commands", () => { createState("
quote
", []), 3 ); - expect(state.doc).toMatchNodeTreeString("doc>blockquote>paragraph>1"); + expect(state.doc).toMatchNodeTreeString( + "doc>blockquote>paragraph>1" + ); const toggleBlockQuote = toggleWrapIn( state.schema.nodes.blockquote @@ -250,7 +254,9 @@ describe("commands", () => { ); expect(isValid).toBeTruthy(); - expect(newState.doc).toMatchNodeTreeString("doc>bullet_list>list_item>blockquote>paragraph>1"); + expect(newState.doc).toMatchNodeTreeString( + "doc>bullet_list>list_item>blockquote>paragraph>1" + ); }); }); From d3e769ce387e9485a2d4135dc08515b71df2b0a0 Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Wed, 20 Jul 2022 15:17:23 -0400 Subject: [PATCH 11/14] drop file that tests tests --- test/matchers.test.ts | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 test/matchers.test.ts diff --git a/test/matchers.test.ts b/test/matchers.test.ts deleted file mode 100644 index 07dd2fe7..00000000 --- a/test/matchers.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createBasicNodeTree } from "./matchers"; - -describe("createBasicNodeTree", () => { - it("creates nested tree", () => { - const input = "doc>blockquote>paragraph>1"; - const expected = { - "type.name": "doc", - "content": [ - { - "type.name": "blockquote", - "content": [ - { - "type.name": "paragraph", - "childCount": 1, - }, - ], - }, - ], - }; - const result = createBasicNodeTree(input); - expect(result).toEqual(expected); - }); -}); From 97eeabd80793eda463428773a6de23bec8db1aec Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Wed, 20 Jul 2022 16:00:19 -0400 Subject: [PATCH 12/14] add markdown-parser tests --- test/matchers.ts | 34 ++++++++++----------------- test/rich-text/commands/index.test.ts | 10 ++++---- test/shared/markdown-parser.test.ts | 17 ++++++++++++++ 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/test/matchers.ts b/test/matchers.ts index 3c692ba3..1f7e4ddf 100644 --- a/test/matchers.ts +++ b/test/matchers.ts @@ -133,33 +133,23 @@ function expectNodeTree(doc: ProsemirrorNode, tree: CompareTree): void { * @returns a CompareTree to be used with `toMatchNodeTree` */ export function createBasicNodeTree(input: string): CompareTree { - const branches = input - .split(">") - .map((x) => x.trim()) - .reverse(); + const branches = input.split(">").map((x) => x.trim()); + if (!branches.length) return {}; - let branch = branches.pop(); const root: CompareTree = { - "type.name": branch, + "type.name": "doc", }; - - branch = branches.pop(); - let node: CompareTree = root; - while (branch) { - if (isNaN(Number(branch))) { - const child = { - "type.name": branch, - }; - node.content = [child]; - - node = child; - branch = branches.pop(); - } else { - node.childCount = Number(branch); - return root; - } + let tree = root; + + for (const branch of branches) { + const child = { + "type.name": branch, + }; + tree.content = [child]; + tree = child; } + return root; } diff --git a/test/rich-text/commands/index.test.ts b/test/rich-text/commands/index.test.ts index 7bf90c13..03c99e85 100644 --- a/test/rich-text/commands/index.test.ts +++ b/test/rich-text/commands/index.test.ts @@ -198,7 +198,7 @@ describe("commands", () => { describe("toggleWrapIn", () => { it("should apply blockquote within paragraph", () => { const state = applySelection(createState("

quote

", []), 3); - expect(state.doc).toMatchNodeTreeString("doc>paragraph>1"); + expect(state.doc).toMatchNodeTreeString("paragraph>text"); const toggleBlockQuote = toggleWrapIn( state.schema.nodes.blockquote @@ -210,7 +210,7 @@ describe("commands", () => { expect(isValid).toBeTruthy(); expect(newState.doc).toMatchNodeTreeString( - "doc>blockquote>paragraph>1" + "blockquote>paragraph>text" ); }); @@ -220,7 +220,7 @@ describe("commands", () => { 3 ); expect(state.doc).toMatchNodeTreeString( - "doc>blockquote>paragraph>1" + "blockquote>paragraph>text" ); const toggleBlockQuote = toggleWrapIn( @@ -232,7 +232,7 @@ describe("commands", () => { ); expect(isValid).toBeTruthy(); - expect(newState.doc).toMatchNodeTreeString("doc>paragraph>1"); + expect(newState.doc).toMatchNodeTreeString("paragraph>text"); }); it("should toggle blockquote within list item", () => { @@ -254,7 +254,7 @@ describe("commands", () => { expect(isValid).toBeTruthy(); expect(newState.doc).toMatchNodeTreeString( - "doc>bullet_list>list_item>blockquote>paragraph>1" + "bullet_list>list_item>blockquote>paragraph>text" ); }); }); diff --git a/test/shared/markdown-parser.test.ts b/test/shared/markdown-parser.test.ts index 1da6af14..c5a8c364 100644 --- a/test/shared/markdown-parser.test.ts +++ b/test/shared/markdown-parser.test.ts @@ -312,6 +312,23 @@ console.log("test"); expect(doc.content[0].type).toContain("_list"); expect(doc.content[0].attrs.tight).toBe(isTight); }); + + it.each([ + [`- > blockquote in list`, "bullet_list>list_item>blockquote"], + [`- paragraph in list`, "bullet_list>list_item>paragraph"], + [`- # heading in list`, "bullet_list>list_item>heading"], + [ + `- ~~~\n code in list\n ~~~`, + "bullet_list>list_item>code_block", + ], + [`- - list in list`, "bullet_list>list_item>bullet_list"], + ])( + "should parse lists with direct block children", + (input, expected) => { + const doc = markdownParser.parse(input); + expect(doc).toMatchNodeTreeString(expected); + } + ); }); describe("reference links", () => { From 6878f95dbd829ed2def7d8c5f8485d7562fae6ff Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Wed, 20 Jul 2022 16:10:06 -0400 Subject: [PATCH 13/14] add markdown-serializer tests --- test/shared/markdown-serializer.test.ts | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/shared/markdown-serializer.test.ts b/test/shared/markdown-serializer.test.ts index b4ec9bba..49a9aaf8 100644 --- a/test/shared/markdown-serializer.test.ts +++ b/test/shared/markdown-serializer.test.ts @@ -211,6 +211,36 @@ describe("markdown-serializer", () => { it.todo("should serialize elements from complex html markup"); + it.each([ + [ + `
  • blockquote in list

`, + `- > blockquote in list`, + ], + [`
  • paragraph in list

`, `- paragraph in list`], + [`
  • heading in list

`, `- # heading in list`], + [ + `
    +
  • +
    +
  • +
`, + `-\n code in list`, + ], + [ + `
    +
  • +
      +
    • list in list
    • +
    +
  • +
`, + `- - list in list`, + ], + ])("should serialize lists containing block children", (input, output) => { + const view = nonMarkdownRichView(input); + expect(view.content).toBe(output); + }); + // TODO lots of complicated cases in the spec // see https://spec.commonmark.org/0.30/#reference-link // see https://spec.commonmark.org/0.30/#link-reference-definitions From 49acd27da7ea8d42c1541c7e287595345483f859 Mon Sep 17 00:00:00 2001 From: KyleMit Date: Wed, 20 Jul 2022 18:24:54 -0400 Subject: [PATCH 14/14] fix breaking test --- test/shared/markdown-serializer.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/shared/markdown-serializer.test.ts b/test/shared/markdown-serializer.test.ts index 49a9aaf8..ce8232ca 100644 --- a/test/shared/markdown-serializer.test.ts +++ b/test/shared/markdown-serializer.test.ts @@ -221,10 +221,10 @@ describe("markdown-serializer", () => { [ `
  • -
    +
    code in list
`, - `-\n code in list`, + `- code in list`, ], [ `