import "jest"; import { EditorState } from "prosemirror-state"; import { EditorStateFactory, editorStateFactoryFactory, namedDescribeRootsFactory } from "../../__specs__/buildEditorState"; import "../../__specs__/expect.toMapEditorState"; import { Command } from "../../types"; import { addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, deleteColumn, deleteRow, deleteTable, insertTable, mergeCells, selectAll, setCellAttr, splitCell, toggleHeaderColumn, toggleHeaderRow } from "../commands"; const describeRoots = namedDescribeRootsFactory([["doc", editorStateFactoryFactory(child => ({ doc }) => doc(child))]]); function runAddColumnAfterSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("extends a col selection when adding a column after", () => test( build(({ table, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => table(tr(cEmpty, cEmpty, cEmpty), tr(cEmptyAnchor, cEmpty, cEmptyHead), tr(cEmpty, cEmpty, cEmpty)) ), build(({ table, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => table( tr(cEmpty, cEmpty, cEmpty, cEmpty), tr(cEmptyAnchor, cEmpty, cEmpty, cEmptyHead), tr(cEmpty, cEmpty, cEmpty, cEmpty) ) ) )); it("can add a plain column", () => test( build(({ table, tr, c11, cCursor }) => table(tr(c11, c11, c11), tr(c11, cCursor, c11), tr(c11, c11, c11))), build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(c11, c11, cEmpty, c11), tr(c11, cCursor, cEmpty, c11), tr(c11, c11, cEmpty, c11)) ) )); it("can add a column at the right of the table", () => test( build(({ table, tr, c11, cCursor }) => table(tr(c11, c11, c11), tr(c11, c11, c11), tr(c11, c11, cCursor))), build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(c11, c11, c11, cEmpty), tr(c11, c11, c11, cEmpty), tr(c11, c11, cCursor, cEmpty)) ) )); it("can add a second cell", () => test( build(({ table, tr, cCursor }) => table(tr(cCursor))), build(({ table, tr, cCursor, cEmpty }) => table(tr(cCursor, cEmpty))) )); it("can grow a colspan cell", () => test( build(({ table, tr, c11, cCursor, c }) => table(tr(cCursor, c11), tr(c(2, 1)))), build(({ table, tr, c11, cEmpty, cCursor, c }) => table(tr(cCursor, cEmpty, c11), tr(c(3, 1)))) )); it("places new cells in the right spot when there's row spans", () => test( build(({ table, tr, c11, cCursor, c }) => table(tr(c11, c(1, 2), c(1, 2)), tr(c11), tr(c11, cCursor, c11))), build(({ table, tr, c11, cCursor, cEmpty, c }) => table(tr(c11, c(1, 2), cEmpty, c(1, 2)), tr(c11, cEmpty), tr(c11, cCursor, cEmpty, c11)) ) )); it("can place new cells into an empty row", () => test( build(({ table, tr, c11, cCursor, c }) => table(tr(c(1, 2), c(1, 2)), tr(), tr(cCursor, c11))), build(({ table, tr, c11, cCursor, cEmpty, c }) => table(tr(c(1, 2), cEmpty, c(1, 2)), tr(cEmpty), tr(cCursor, cEmpty, c11)) ) )); it("will skip ahead when growing a rowspan cell", () => test( build(({ table, tr, c11, cCursor, c }) => table(tr(c(2, 2), c11), tr(c11), tr(cCursor, c11, c11))), build(({ table, tr, c11, cCursor, cEmpty, c }) => table(tr(c(3, 2), c11), tr(c11), tr(cCursor, cEmpty, c11, c11))) )); it("will use the right side of a single cell selection", () => test( build(({ table, tr, c11, cAnchor }) => table(tr(cAnchor, c11), tr(c11, c11))), build(({ table, tr, c11, cAnchor, cEmpty }) => table(tr(cAnchor, cEmpty, c11), tr(c11, cEmpty, c11))) )); it("will use the right side of a bigger cell selection", () => test( build(({ table, tr, c11, cHead, cAnchor }) => table(tr(cHead, c11, c11), tr(c11, cAnchor, c11))), build(({ table, tr, c11, cHead, cAnchor, cEmpty }) => table(tr(cHead, c11, cEmpty, c11), tr(c11, cAnchor, cEmpty, c11))) )); it("properly handles a cell node selection", () => test( build(({ table, tr, c11 }) => table(tr("{node}", c11, c11), tr(c11, c11))), build(({ table, tr, c11, cEmpty }) => table(tr("{node}", c11, cEmpty, c11), tr(c11, cEmpty, c11))) )); it("preserves header rows", () => test( build(({ table, tr, c11, h11, cCursor }) => table(tr(h11, h11), tr(c11, cCursor))), build(({ table, tr, c11, h11, cCursor, hEmpty, cEmpty }) => table(tr(h11, h11, hEmpty), tr(c11, cCursor, cEmpty))) )); it("uses column after as reference when header column before", () => test( build(({ table, tr, c11, h11, hCursor }) => table(tr(h11, h11), tr(hCursor, c11))), build(({ table, tr, c11, h11, hCursor, hEmpty, cEmpty }) => table(tr(h11, hEmpty, h11), tr(hCursor, cEmpty, c11))) )); it("creates regular cells when only next to a header column", () => test( build(({ table, tr, c11, h11, hCursor }) => table(tr(c11, h11), tr(c11, hCursor))), build(({ table, tr, c11, h11, hCursor, cEmpty }) => table(tr(c11, h11, cEmpty), tr(c11, hCursor, cEmpty))) )); it("does nothing outside of a table", () => expect(command).not.toMapEditorState(build(({ p }) => p("foo{^}")))); it("preserves column widths", () => test( build(({ table, tr, c11, cAnchor, p, td_ }) => table(tr(cAnchor, c11), tr(td_({ colspan: 2, colwidth: [100, 200] })(p("a")))) ), build(({ table, tr, c11, cAnchor, cEmpty, p, td_ }) => table(tr(cAnchor, cEmpty, c11), tr(td_({ colspan: 3, colwidth: [100, 0, 200] })(p("a")))) ) )); } function runAddColumnBeforeSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("extends a col selection when adding a column before", () => test( build(({ table, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => table(tr(cEmpty, cEmpty, cEmpty), tr(cEmptyAnchor, cEmpty, cEmptyHead), tr(cEmpty, cEmpty, cEmpty)) ), build(({ table, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => table( tr(cEmpty, cEmpty, cEmpty, cEmpty), tr(cEmptyAnchor, cEmpty, cEmpty, cEmptyHead), tr(cEmpty, cEmpty, cEmpty, cEmpty) ) ) )); it("can add a plain column", () => test( build(({ table, tr, c11, cCursor }) => table(tr(c11, c11, c11), tr(c11, cCursor, c11), tr(c11, c11, c11))), build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(c11, cEmpty, c11, c11), tr(c11, cEmpty, cCursor, c11), tr(c11, cEmpty, c11, c11)) ) )); it("can add a column at the left of the table", () => test( build(({ table, tr, c11, cCursor }) => table(tr(cCursor, c11, c11), tr(c11, c11, c11), tr(c11, c11, c11))), build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(cEmpty, cCursor, c11, c11), tr(cEmpty, c11, c11, c11), tr(cEmpty, c11, c11, c11)) ) )); it("will use the left side of a single cell selection", () => test( build(({ table, tr, c11, cAnchor }) => table(tr(cAnchor, c11), tr(c11, c11))), build(({ table, tr, c11, cAnchor, cEmpty }) => table(tr(cEmpty, cAnchor, c11), tr(cEmpty, c11, c11))) )); it("will use the left side of a bigger cell selection", () => test( build(({ table, tr, c11, cHead, cAnchor }) => table(tr(c11, cHead, c11), tr(c11, c11, cAnchor))), build(({ table, tr, c11, cHead, cAnchor, cEmpty }) => table(tr(c11, cEmpty, cHead, c11), tr(c11, cEmpty, c11, cAnchor))) )); it("preserves header rows", () => test( build(({ table, tr, c11, h11, cCursor }) => table(tr(h11, h11), tr(cCursor, c11))), build(({ table, tr, c11, h11, cCursor, hEmpty, cEmpty }) => table(tr(hEmpty, h11, h11), tr(cEmpty, cCursor, c11))) )); } function runDeleteColumnSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("can delete a plain column", () => test( build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(cEmpty, c11, c11), tr(c11, cCursor, c11), tr(c11, c11, cEmpty))), build(({ table, tr, c11, cEmpty, p, td }) => table(tr(cEmpty, c11), tr(c11, td(p("{^}x"))), tr(c11, cEmpty))) )); it("can delete the first column", () => test( build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(cCursor, cEmpty, c11), tr(c11, c11, c11), tr(c11, c11, c11))), build(({ table, tr, c11, td, p }) => table(tr(td(p("{^}")), c11), tr(c11, c11), tr(c11, c11))) )); it("can delete the last column", () => test( build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(c11, cEmpty, cCursor), tr(c11, c11, c11), tr(c11, c11, c11))), build(({ table, tr, c11, td, p, cEmpty }) => table(tr(c11, cEmpty), tr(td(p("{^}x")), c11), tr(c11, c11))) )); it("can reduce a cell's colspan", () => test( build(({ table, tr, c11, cCursor, c }) => table(tr(c11, cCursor), tr(c(2, 1)))), build(({ table, tr, c11, td, p }) => table(tr(c11), tr(td(p("{^}x"))))) )); it("will skip rows after a rowspan", () => test( build(({ table, tr, c11, cCursor, c }) => table(tr(c11, cCursor), tr(c11, c(1, 2)), tr(c11))), build(({ table, tr, c11, td, p }) => table(tr(c11), tr(td(p("{^}x"))), tr(c11))) )); it("will delete all columns under a colspan cell", () => test( build(({ table, tr, c11, cEmpty, td_, p }) => table(tr(c11, td_({ colspan: 2 })(p("{^}"))), tr(cEmpty, c11, c11))), build(({ table, tr, c11, td, p }) => table(tr(c11), tr(td(p("{^}"))))) )); it("deletes a cell-selected column", () => test( build(({ table, tr, c11, cAnchor, cHead, cEmpty }) => table(tr(cEmpty, cAnchor), tr(c11, cHead))), build(({ table, tr, cEmpty, td, p }) => table(tr(cEmpty), tr(td(p("{^}x{$}"))))) )); xit("deletes multiple cell-selected columns", () => test( build(({ table, tr, c11, cAnchor, cHead, cEmpty, c }) => table(tr(c(1, 2), cAnchor, c11), tr(c11, cEmpty), tr(cHead, c11, c11)) ), build(({ table, tr, c11, cEmpty }) => table(tr(c11), tr(cEmpty), tr(c11))) )); it("leaves column widths intact", () => test( build(({ table, tr, c11, cAnchor, td_, p }) => table(tr(c11, cAnchor, c11), tr(td_({ colspan: 3, colwidth: [100, 200, 300] })(p("y")))) ), build(({ table, tr, c11, cAnchor, td_, p }) => table(tr(c11, cAnchor), tr(td_({ colspan: 2, colwidth: [100, 300] })(p("y")))) ) )); it("resets column width when all zeroes", () => test( build(({ table, tr, c11, cAnchor, td_, p }) => table(tr(c11, cAnchor, c11), tr(td_({ colspan: 3, colwidth: [0, 200, 0] })(p("y")))) ), build(({ table, tr, c11, cAnchor, td_, p }) => table(tr(c11, cAnchor), tr(td_({ colspan: 2 })(p("y"))))) )); it("deleting the final column deletes the table", () => test(build(({ table, tr, cEmptyAnchor }) => table(tr(cEmptyAnchor))), build(({ p }) => p()))); } function runAddRowAfterSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("extends a column selection when adding a row after", () => test( build(({ table, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => table(tr(cEmpty, cEmptyAnchor, cEmpty), tr(cEmpty, cEmpty, cEmpty), tr(cEmpty, cEmptyHead, cEmpty)) ), build(({ table, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => table( tr(cEmpty, cEmptyAnchor, cEmpty), tr(cEmpty, cEmpty, cEmpty), tr(cEmpty, cEmpty, cEmpty), tr(cEmpty, cEmptyHead, cEmpty) ) ) )); it("can add a simple row", () => test( build(({ table, tr, c11, cCursor }) => table(tr(cCursor, c11), tr(c11, c11))), build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(cCursor, c11), tr(cEmpty, cEmpty), tr(c11, c11))) )); it("can add a row at the end", () => test( build(({ table, tr, c11, cCursor }) => table(tr(c11, c11), tr(c11, cCursor))), build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(c11, c11), tr(c11, cCursor), tr(cEmpty, cEmpty))) )); it("increases rowspan when needed", () => test( build(({ table, tr, c11, cCursor, c }) => table(tr(cCursor, c(1, 2)), tr(c11))), build(({ table, tr, c11, cCursor, cEmpty, c }) => table(tr(cCursor, c(1, 3)), tr(cEmpty), tr(c11))) )); it("skips columns for colspan cells", () => test( build(({ table, tr, c11, cCursor, c }) => table(tr(cCursor, c(2, 2)), tr(c11))), build(({ table, tr, c11, cCursor, cEmpty, c }) => table(tr(cCursor, c(2, 3)), tr(cEmpty), tr(c11))) )); it("picks the row after a cell selection", () => test( build(({ table, tr, c11, cAnchor, cHead, c }) => table(tr(cHead, c11, c11), tr(c11, cAnchor, c11), tr(c(3, 1)))), build(({ table, tr, c11, cAnchor, cHead, cEmpty, c }) => table(tr(cHead, c11, c11), tr(c11, cAnchor, c11), tr(cEmpty, cEmpty, cEmpty), tr(c(3, 1))) ) )); it("preserves header columns", () => test( build(({ table, tr, h11, c11, hCursor }) => table(tr(c11, hCursor), tr(c11, h11))), build(({ table, tr, h11, hEmpty, hCursor, c11, cEmpty }) => table(tr(c11, hCursor), tr(cEmpty, hEmpty), tr(c11, h11))) )); it("uses next row as reference when row before is a header", () => test( build(({ table, tr, c11, h11, hCursor }) => table(tr(h11, hCursor), tr(c11, h11))), build(({ table, tr, c11, h11, hCursor, hEmpty, cEmpty }) => table(tr(h11, hCursor), tr(cEmpty, hEmpty), tr(c11, h11))) )); it("creates regular cells when no reference row is available", () => test( build(({ table, tr, h11, hCursor }) => table(tr(h11, hCursor))), build(({ table, tr, h11, hCursor, cEmpty }) => table(tr(h11, hCursor), tr(cEmpty, cEmpty))) )); } function runAddRowBeforeSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("extends a column selection when adding a row before", () => test( build(({ table, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => table(tr(cEmpty, cEmptyAnchor, cEmpty), tr(cEmpty, cEmpty, cEmpty), tr(cEmpty, cEmptyHead, cEmpty)) ), build(({ table, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => table( tr(cEmpty, cEmptyAnchor, cEmpty), tr(cEmpty, cEmpty, cEmpty), tr(cEmpty, cEmpty, cEmpty), tr(cEmpty, cEmptyHead, cEmpty) ) ) )); it("can add a simple row", () => test( build(({ table, tr, c11, cCursor }) => table(tr(c11, c11), tr(cCursor, c11))), build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(c11, c11), tr(cEmpty, cEmpty), tr(cCursor, c11))) )); it("can add a row at the start", () => test( build(({ table, tr, c11, cCursor }) => table(tr(cCursor, c11), tr(c11, c11))), build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(cEmpty, cEmpty), tr(cCursor, c11), tr(c11, c11))) )); it("picks the row before a cell selection", () => test( build(({ table, tr, c11, cAnchor, cHead, c }) => table(tr(c11, c(2, 1)), tr(cAnchor, c11, c11), tr(c11, cHead, c11))), build(({ table, tr, c11, cAnchor, cHead, cEmpty, c }) => table(tr(c11, c(2, 1)), tr(cEmpty, cEmpty, cEmpty), tr(cAnchor, c11, c11), tr(c11, cHead, c11)) ) )); it("preserves header columns", () => test( build(({ table, tr, c11, h11, hCursor }) => table(tr(hCursor, c11), tr(h11, c11))), build(({ table, tr, c11, h11, hCursor, hEmpty, cEmpty }) => table(tr(hEmpty, cEmpty), tr(hCursor, c11), tr(h11, c11))) )); } function runDeleteRowSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("can delete a simple row", () => test( build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(c11, cEmpty), tr(cCursor, c11), tr(c11, cEmpty))), build(({ table, tr, c11, cEmpty, td, p }) => table(tr(c11, cEmpty), tr(td(p("{^}x")), cEmpty))) )); it("can delete the first row", () => test( build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(c11, cCursor), tr(cEmpty, c11), tr(c11, cEmpty))), build(({ table, tr, c11, cEmpty, td, p }) => table(tr(td(p("{^}")), c11), tr(c11, cEmpty))) )); it("can delete the last row", () => test( build(({ table, tr, c11, cCursor, cEmpty }) => table(tr(cEmpty, c11), tr(c11, cEmpty), tr(c11, cCursor))), build(({ table, tr, c11, cEmpty, td, p }) => table(tr(cEmpty, c11), tr(c11, td(p("{^}"))))) )); it("can shrink rowspan cells", () => test( build(({ table, tr, c11, cCursor, c }) => table(tr(c(1, 2), c11, c(1, 3)), tr(cCursor), tr(c11, c11))), build(({ table, tr, c11, td, p, c }) => table(tr(c11, c11, c(1, 2)), tr(td(p("{^}x")), c11))) )); it("can move cells that start in the deleted row", () => test( build(({ table, tr, cCursor, cEmpty, c }) => table(tr(c(1, 2), cCursor), tr(cEmpty))), build(({ table, tr, c11, cEmpty }) => table(tr(c11, cEmpty))) )); it("deletes multiple rows when the start cell has a rowspan", () => test( build(({ table, tr, c11, td_, p }) => table(tr(td_({ rowspan: 3 })(p("{^}")), c11), tr(c11), tr(c11), tr(c11, c11))), build(({ table, tr, c11 }) => table(tr(c11, c11))) )); it("skips columns when adjusting rowspan", () => test( build(({ table, tr, c11, cCursor, c }) => table(tr(cCursor, c(2, 2)), tr(c11))), build(({ table, tr, td, p, c }) => table(tr(td(p("{^}x")), c(2, 1)))) )); it("can delete a cell selection", () => test( build(({ table, tr, c11, cAnchor, cEmpty }) => table(tr(cAnchor, c11), tr(c11, cEmpty))), build(({ table, tr, cEmpty, td, p }) => table(tr(td(p("{^}x")), cEmpty))) )); it("will delete all rows in the cell selection", () => test( build(({ table, tr, c11, cAnchor, cHead, cEmpty }) => table(tr(c11, cEmpty), tr(cAnchor, c11), tr(c11, cHead), tr(cEmpty, c11)) ), build(({ table, tr, td, p, c11, cEmpty }) => table(tr(c11, cEmpty), tr(td(p("{^}")), c11))) )); it("deleting the final row deletes the table", () => test(build(({ table, tr, cEmptyAnchor }) => table(tr(cEmptyAnchor))), build(({ p }) => p()))); } function runDeleteTableSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("removes the table", () => test(build(({ table, tr, cEmptyCursor }) => table(tr(cEmptyCursor))), build(({ p }) => p("{^}")))); } function runInsertTableSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("replaces an empty paragraph with a 3x3 table, places cursor in the first cell", () => test( build(({ p }) => p("{^}")), build(({ table, tr, cEmptyCursor, cEmpty }) => table(tr(cEmptyCursor, cEmpty, cEmpty), tr(cEmpty, cEmpty, cEmpty), tr(cEmpty, cEmpty, cEmpty)) ) )); it("after a non-empty paragraph, inserts a 3x3 table, places cursor in the first cell", () => test( build(({ p }) => p("word{^}")), build(({ table, tr, cEmptyCursor, cEmpty, p }) => [ p("word"), table(tr(cEmptyCursor, cEmpty, cEmpty), tr(cEmpty, cEmpty, cEmpty), tr(cEmpty, cEmpty, cEmpty)) ]) )); } function runMergeCellsSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("does nothing when only one cell is selected", () => expect(command).not.toMapEditorState(build(({ table, tr, c11, cAnchor }) => table(tr(cAnchor, c11))))); it("does nothing when the selection cuts across spanning cells", () => expect(command).not.toMapEditorState( build(({ table, tr, c11, cAnchor, cHead, c }) => table(tr(cAnchor, c(2, 1)), tr(c11, cHead, c11))) )); it("can merge two cells in a column", () => test( build(({ table, tr, c11, cAnchor, cHead }) => table(tr(cAnchor, cHead, c11))), build(({ table, tr, c11, td_, p }) => table(tr(td_({ colspan: 2 })("{[^]}", p("x"), p("x")), c11))) )); it("can merge two cells in a row", () => test( build(({ table, tr, c11, cAnchor, cHead }) => table(tr(cAnchor, c11), tr(cHead, c11))), build(({ table, tr, c11, td_, p }) => table(tr(td_({ rowspan: 2 })("{[^]}", p("x"), p("x")), c11), tr(c11))) )); it("can merge a rectangle of cells", () => test( build(({ table, tr, c11, cAnchor, cHead, cEmpty }) => table(tr(c11, cAnchor, cEmpty, cEmpty, c11), tr(c11, cEmpty, cEmpty, cHead, c11)) ), build(({ table, tr, c11, td_, p }) => table(tr(c11, td_({ rowspan: 2, colspan: 3 })("{[^]}", p("x"), p("x")), c11), tr(c11, c11)) ) )); it("can merge already spanning cells", () => test( build(({ table, tr, c11, cAnchor, cHead, cEmpty, c }) => table(tr(c11, cAnchor, c(1, 2), cEmpty, c11), tr(c11, cEmpty, cHead, c11)) ), build(({ table, tr, c11, td_, p }) => table(tr(c11, td_({ rowspan: 2, colspan: 3 })("{[^]}", p("x"), p("x"), p("x")), c11), tr(c11, c11)) ) )); it("keeps the column width of the first col", () => test( build(({ table, tr, c11, cHead, td_, p }) => table(tr(td_({ colwidth: [100] })(p("x{[^]}")), c11), tr(c11, cHead))), build(({ table, tr, td_, p }) => table(tr(td_({ colspan: 2, rowspan: 2, colwidth: [100, 0] })("{[^]}", p("x"), p("x"), p("x"), p("x"))), tr()) ) )); } function runSplitCellSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("does nothing when there isn't a cell selection", () => expect(command).not.toMapEditorState(build(({ table, tr, c11, cCursor }) => table(tr(cCursor, c11))))); it("does nothing for a multi-cell selection", () => expect(command).not.toMapEditorState(build(({ table, tr, c11, cAnchor, cHead }) => table(tr(cAnchor, cHead, c11))))); it("does nothing when the selected cell doesn't span anything", () => expect(command).not.toMapEditorState(build(({ table, tr, c11, cAnchor }) => table(tr(cAnchor, c11))))); it("can split a col-spanning cell", () => test( build(({ table, tr, c11, td_, p }) => table(tr(td_({ colspan: 2 })(p("foo{[^]}")), c11))), build(({ table, tr, c11, td, p }) => table(tr(td(p("foo{[^]}")), td(p("{[$]}")), c11))) )); it("can split a row-spanning cell", () => test( build(({ table, tr, c11, td_, p }) => table(tr(c11, td_({ rowspan: 2 })(p("foo{[^]}")), c11), tr(c11, c11))), build(({ table, tr, c11, td, p }) => table(tr(c11, td(p("foo{[^]}")), c11), tr(c11, td(p("{[$]}")), c11))) )); it("can split a rectangular cell", () => test( build(({ table, tr, c11, td_, c, p }) => table(tr(c(4, 1)), tr(c11, td_({ rowspan: 2, colspan: 2 })(p("foo{[^]}")), c11), tr(c11, c11)) ), build(({ table, tr, c11, td, cEmpty, c, p }) => table(tr(c(4, 1)), tr(c11, td(p("foo{[^]}")), cEmpty, c11), tr(c11, cEmpty, td(p("{[$]}")), c11)) ) )); it("distributes column widths", () => test( build(({ table, tr, td_, p }) => table(tr(td_({ colspan: 3, colwidth: [100, 0, 200] })(p("a{[^]}"))))), build(({ table, tr, td_, p, cEmpty }) => table(tr(td_({ colwidth: [100] })(p("a{[^]}")), cEmpty, td_({ colwidth: [200] })(p("{[$]}")))) ) )); } function runSetCellAttrSuite(commandFactory: typeof setCellAttr, build: EditorStateFactory) { it("can set an attribute on a parent cell", () => expect(commandFactory("colwidth", [1, 2, 3])).toMapEditorState( build(({ tbl, tr, td, p, c11 }) => tbl(tr(td(p("x{^}")), c11))), build(({ tbl, tr, td_, p, c11 }) => tbl(tr(td_({ colwidth: [1, 2, 3] })(p("x{^}")), c11))) )); it("does nothing when the attribute is already there", () => expect(setCellAttr("colwidth", null)).not.toMapEditorState( build(({ tbl, tr, td_, p, c11 }) => tbl(tr(td_({ colwidth: null })(p("x{^}")), c11))) )); it("will set attributes on all cells covered by a cell selection", () => { const attrs = { colwidth: [1, 2, 3] }; expect(setCellAttr("colwidth", attrs.colwidth)).toMapEditorState( build(({ tbl, tr, td_, p, c11, cAnchor, cHead }) => tbl(tr(c11, cAnchor, c11), tr(td_({ colspan: 2, rowspan: 1 })(p("x")), cHead), tr(c11, c11, c11)) ), build(({ tbl, tr, td_, p, c11 }) => tbl( tr(c11, td_(attrs)(p("x{[^]}")), td_(attrs)(p("x"))), tr(td_({ colspan: 2, rowspan: 1 })(p("x")), td_(attrs)(p("x{[$]}"))), tr(c11, c11, c11) ) ) ); }); } function runSelectAllSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("creates cell selection for nearest table from text selection cursor", () => test( build(({ tbl, tr, cEmpty, cEmptyCursor }) => tbl(tr(cEmpty, cEmptyCursor, cEmpty))), build(({ tbl, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => tbl(tr(cEmptyAnchor, cEmpty, cEmptyHead))) )); it("creates cell selection for nearest table from cell selection", () => test( build(({ tbl, tr, cEmpty, cEmptyAnchor }) => tbl(tr(cEmpty, cEmptyAnchor, cEmpty))), build(({ tbl, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => tbl(tr(cEmptyAnchor, cEmpty, cEmptyHead))) )); it("does not apply when the table is already covered by a total cell selection", () => expect(command(build(({ tbl, tr, cEmpty, cEmptyAnchor, cEmptyHead }) => tbl(tr(cEmptyAnchor, cEmpty, cEmptyHead))))).toBe( false )); } function runToggleHeaderRowSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("turns a non-header row into header", () => test( build(({ tbl, tr, cCursor, c11 }) => tbl(tr(cCursor, c11), tr(c11, c11))), build(({ tbl, tr, hCursor, c11, h11 }) => tbl(tr(hCursor, h11), tr(c11, c11))) )); it("turns a header row into regular cells", () => test( build(({ tbl, tr, hCursor, c11, h11 }) => tbl(tr(hCursor, h11), tr(c11, c11))), build(({ tbl, tr, cCursor, c11 }) => tbl(tr(cCursor, c11), tr(c11, c11))) )); it("turns a partial header row into regular cells", () => test( build(({ tbl, tr, cCursor, c11, h11 }) => tbl(tr(cCursor, h11), tr(c11, c11))), build(({ tbl, tr, cCursor, c11 }) => tbl(tr(cCursor, c11), tr(c11, c11))) )); it("leaves cell spans intact", () => test( build(({ tbl, tr, c, cCursor, c11 }) => tbl(tr(cCursor, c(2, 2)), tr(c11), tr(c11, c11, c11))), build(({ tbl, tr, h, hCursor, c11 }) => tbl(tr(hCursor, h(2, 2)), tr(c11), tr(c11, c11, c11))) )); } function runToggleHeaderColumnSuite(command: Command, build: EditorStateFactory) { function test(initial: EditorState, expected: EditorState) { expect(command).toMapEditorState(initial, expected); } it("turns a non-header column into header", () => test( build(({ tbl, tr, c11, cCursor }) => tbl(tr(cCursor, c11), tr(c11, c11))), build(({ tbl, tr, th, p, c11, h11 }) => tbl(tr(th(p("x{^}")), c11), tr(h11, c11))) )); it("turns a header column into regular cells", () => test( build(({ tbl, tr, th, p, c11, h11 }) => tbl(tr(th(p("x{^}")), h11), tr(h11, c11))), build(({ tbl, tr, c11, cCursor, h11 }) => tbl(tr(cCursor, h11), tr(c11, c11))) )); it("turns a partial header column into regular cells", () => test( build(({ tbl, tr, th, p, c11 }) => tbl(tr(th(p("x{^}")), c11), tr(c11, c11))), build(({ tbl, tr, c11, cCursor }) => tbl(tr(cCursor, c11), tr(c11, c11))) )); } describeRoots("addColumnAfter", build => runAddColumnAfterSuite(addColumnAfter, build)); describeRoots("addColumnBefore", build => runAddColumnBeforeSuite(addColumnBefore, build)); describeRoots("addRowAfter", build => runAddRowAfterSuite(addRowAfter, build)); describeRoots("addRowBefore", build => runAddRowBeforeSuite(addRowBefore, build)); describeRoots("deleteColumn", build => runDeleteColumnSuite(deleteColumn, build)); describeRoots("deleteRow", build => runDeleteRowSuite(deleteRow, build)); describeRoots("deleteTable", build => runDeleteTableSuite(deleteTable, build)); describeRoots("insertTable", build => runInsertTableSuite(insertTable, build)); describeRoots("mergeCells", build => runMergeCellsSuite(mergeCells, build)); describeRoots("selectAll", build => runSelectAllSuite(selectAll, build)); describeRoots("setCellAttr", build => runSetCellAttrSuite(setCellAttr, build)); describeRoots("splitCell", build => runSplitCellSuite(splitCell, build)); describeRoots("toggleHeaderRow", build => runToggleHeaderRowSuite(toggleHeaderRow, build)); describeRoots("toggleHeaderColumn", build => runToggleHeaderColumnSuite(toggleHeaderColumn, build));