import { Fragment } from "prosemirror-model"; import { EditorState } from "prosemirror-state"; import { buildNode } from "../../__specs__/buildEditorState"; import "../../__specs__/expect.toMatchFragment"; import "../../__specs__/expect.toMatchNode"; import { RefsNode } from "../../composing"; import { TableNode, TablePosOfCell, TableStart } from "../../types"; import { clipCells, insertCells, pastedCells } from "../copypaste"; import { TableMap } from "../TableMap"; import { cellAround } from "../util"; const cEmpty = buildNode(({ cEmpty }) => cEmpty); const c11 = buildNode(({ c11 }) => c11); describe("pastedCells", () => { function test(refsNode: RefsNode, width?: number, height?: number, content?: Array>): void { const { node, refMap } = refsNode; const result = pastedCells(node.slice(refMap.get("a")!, refMap.get("b")!)); if (width === undefined) { expect(result).toBe(null); } else { expect(result!.rows.length).toBe(result!.height); expect(result!.width).toBe(width); expect(result!.height).toBe(height); if (content !== undefined) { result!.rows.forEach((row, i) => expect(row).toMatchFragment(Fragment.from(content[i].map(n => n.node)))); } } } it("returns simple cells", () => test(buildNode(({ table, tr }) => table(tr("{a}", cEmpty, cEmpty, "{b}"))), 2, 1, [[cEmpty, cEmpty]])); it("returns cells wrapped in a row", () => test(buildNode(({ table, tr }) => table("{a}", tr(cEmpty, cEmpty), "{b}")), 2, 1, [[cEmpty, cEmpty]])); it("returns cells when the cursor is inside them", () => test(buildNode(({ table, tr, td, p }) => table(tr(td(p("{a}foo")), td(p("{b}bar"))))), 2, 1, [ [buildNode(({ td, p }) => td(p("foo"))), cEmpty] ])); it("returns multiple rows", () => test(buildNode(({ table, tr, c11 }) => table(tr("{a}", cEmpty, cEmpty), tr(cEmpty, c11), "{b}")), 2, 2, [ [cEmpty, cEmpty], [cEmpty, c11] ])); it("will enter a fully selected table", () => test(buildNode(({ table, tr, doc, c11 }) => doc("{a}", table(tr(c11)), "{b}")), 1, 1, [[c11]])); it("can normalize a partially-selected row", () => test(buildNode(({ table, tr, td, p }) => table(tr(td(p(), "{a}"), cEmpty, c11), tr(c11, c11), "{b}")), 2, 2, [ [cEmpty, c11], [c11, c11] ])); it("will make sure the result is rectangular", () => test(buildNode(({ table, tr, c }) => table("{a}", tr(c(2, 2), c11), tr(), tr(c11, c11), "{b}")), 3, 3, [ [buildNode(({ c }) => c(2, 2)), c11], [cEmpty], [c11, c11, cEmpty] ])); it("can handle rowspans sticking out", () => test(buildNode(({ table, tr, c }) => table("{a}", tr(c(1, 3), c11), "{b}")), 2, 3, [ [buildNode(({ c }) => c(1, 3)), c11], [cEmpty], [cEmpty] ])); it("returns null for non-cell selection", () => test(buildNode(({ p, doc }) => doc(p("foo{a}bar"), p("baz{b}"))), undefined)); }); describe("clipCells", () => { function test(sliceRefsNode: RefsNode, width: number, height: number, content: Array>) { const { node: slice, refMap } = sliceRefsNode; const result = clipCells(pastedCells(slice.slice(refMap.get("a")!, refMap.get("b")!))!, width, height); expect(result.rows.length).toBe(result.height); expect(result.width).toBe(width); expect(result.height).toBe(height); result.rows.forEach((row, i) => expect(row).toMatchFragment(Fragment.from(content[i].map(n => n.node)))); } it("can clip off excess cells", () => test(buildNode(({ table, tr }) => table("{a}", tr(cEmpty, c11), tr(c11, c11), "{b}")), 1, 1, [[cEmpty]])); it("will pad by repeating cells", () => test(buildNode(({ table, tr }) => table("{a}", tr(cEmpty, c11), tr(c11, cEmpty), "{b}")), 4, 4, [ [cEmpty, c11, cEmpty, c11], [c11, cEmpty, c11, cEmpty], [cEmpty, c11, cEmpty, c11], [c11, cEmpty, c11, cEmpty] ])); it("takes rowspan into account when counting width", () => test(buildNode(({ table, tr, c }) => table("{a}", tr(c(2, 2), c11), tr(c11), "{b}")), 6, 2, [ [buildNode(({ c }) => c(2, 2)), c11, buildNode(({ c }) => c(2, 2)), c11], [c11, c11] ])); it("clips off excess colspan", () => test(buildNode(({ table, tr, c }) => table("{a}", tr(c(2, 2), c11), tr(c11), "{b}")), 4, 2, [ [buildNode(({ c }) => c(2, 2)), c11, buildNode(({ c }) => c(1, 2))], [c11] ])); it("clips off excess rowspan", () => test(buildNode(({ table, tr, c }) => table("{a}", tr(c(2, 2), c11), tr(c11), "{b}")), 2, 3, [ [buildNode(({ c }) => c(2, 2))], [], [buildNode(({ c }) => c(2, 1))] ])); }); describe("insertCells", () => { function test(tableRefsNode: RefsNode, cells: RefsNode, result: RefsNode) { const { node: table, refMap } = tableRefsNode; let state = EditorState.create({ doc: table }); const $cell = cellAround(table.resolve(refMap.get("[^]")!))!; const map = TableMap.get(table as TableNode); // $cell.pos and 0 can be used because the doc is a table. const cellPos = $cell.pos as TablePosOfCell; const tableStart = 0 as TableStart; insertCells( state, tr => (state = state.apply(tr)), tableStart, map.findCell(cellPos), pastedCells(cells.node.slice(cells.refMap.get("a")!, cells.refMap.get("b")!))! ); expect(state.doc).toMatchNode(result.node); } it("keeps the original cells", () => test( buildNode(({ table, tr, cAnchor }) => table(tr(cAnchor, c11, c11), tr(c11, c11, c11))), buildNode(({ table, tr, td, p, c }) => table(tr(td(p("{a}foo")), cEmpty), tr(c(2, 1), "{b}"))), buildNode(({ table, tr, td, p, c }) => table(tr(td(p("foo")), cEmpty, c11), tr(c(2, 1), c11))) )); it("makes sure the table is big enough", () => test( buildNode(({ table, tr, cAnchor }) => table(tr(cAnchor))), buildNode(({ table, tr, td, p, c }) => table(tr(td(p("{a}foo")), cEmpty), tr(c(2, 1), "{b}"))), buildNode(({ table, tr, td, p, c }) => table(tr(td(p("foo")), cEmpty), tr(c(2, 1)))) )); it("preserves headers while growing a table", () => test( buildNode(({ table, tr, cAnchor, h11 }) => table(tr(h11, h11, h11), tr(h11, c11, c11), tr(h11, c11, cAnchor))), buildNode(({ table, tr, td, p }) => table(tr(td(p("{a}foo")), cEmpty), tr(c11, c11, "{b}"))), buildNode(({ table, tr, h11, td, p, hEmpty }) => table( tr(h11, h11, h11, hEmpty), tr(h11, c11, c11, cEmpty), tr(h11, c11, td(p("foo")), cEmpty), tr(hEmpty, cEmpty, c11, c11) ) ) )); it("will split interfering rowspan cells", () => test( buildNode(({ table, tr, cAnchor, c }) => table(tr(c11, c(1, 4), c11), tr(cAnchor, c11), tr(c11, c11), tr(c11, c11))), buildNode(({ table, tr }) => table(tr("{a}", cEmpty, cEmpty, cEmpty, "{b}"))), buildNode(({ table, tr, td_, p }) => table(tr(c11, c11, c11), tr(cEmpty, cEmpty, cEmpty), tr(c11, td_({ rowspan: 2 })(p()), c11), tr(c11, c11)) ) )); it("will split interfering colspan cells", () => test( buildNode(({ table, tr, cAnchor, c }) => table(tr(c11, cAnchor, c11), tr(c(2, 1), c11), tr(c11, c(2, 1)))), buildNode(({ table, tr }) => table("{a}", tr(cEmpty), tr(cEmpty), tr(cEmpty), "{b}")), buildNode(({ table, tr }) => table(tr(c11, cEmpty, c11), tr(c11, cEmpty, c11), tr(c11, cEmpty, cEmpty))) )); it("preserves widths when splitting", () => test( buildNode(({ table, tr, cAnchor, td_, p }) => table(tr(c11, cAnchor, c11), tr(td_({ colspan: 3, colwidth: [100, 200, 300] })(p("x")))) ), buildNode(({ table, tr }) => table("{a}", tr(cEmpty), tr(cEmpty), "{b}")), buildNode(({ table, tr, td_, p }) => table(tr(c11, cEmpty, c11), tr(td_({ colwidth: [100] })(p("x")), cEmpty, td_({ colwidth: [300] })(p()))) ) )); });