test(editor-ext): extract shared ProseMirror table-test fixture (F1)

The schema + cell/row/table/doc builders + grid/stateFor/trFor were copied
verbatim into the 3 new table-utils test files (and the pre-existing
table-utils.test.ts) — a schema change would have to be synced across all four.
Move them into a shared table-test-helpers.ts (test-only, excluded from the
build like footnote-corpus.ts) and import it everywhere; cell uses the
(txt, attrs?) superset (a drop-in for the bare (txt) copies). No assertion
changes — test counts unchanged (223 passed + 3 expected-fail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
claude code agent 227
2026-06-29 21:17:18 +03:00
parent f9b58a0e3d
commit b35950ef94
6 changed files with 84 additions and 141 deletions
@@ -1,9 +1,6 @@
import { describe, it, expect } from "vitest";
import { Schema } from "@tiptap/pm/model";
import type { Node as PMNode } from "@tiptap/pm/model";
import { tableNodes } from "@tiptap/pm/tables";
import { EditorState, Selection } from "@tiptap/pm/state";
import { getSelectionRangeInColumn } from "./get-selection-range-in-column";
import { cell, row, table, doc, trFor } from "./table-test-helpers";
/**
* getSelectionRangeInColumn computes the rectangular column range (the set of
@@ -13,36 +10,6 @@ import { getSelectionRangeInColumn } from "./get-selection-range-in-column";
* real EditorState whose selection sits inside the table.
*/
// Real ProseMirror table schema (same primitives the editor uses) so TableMap /
// cellsInRect behave exactly as in production.
const tNodes = tableNodes({
tableGroup: "block",
cellContent: "inline*",
cellAttributes: {},
});
const schema = new Schema({
nodes: {
doc: { content: "block+" },
paragraph: { group: "block", content: "inline*", toDOM: () => ["p", 0] },
text: { group: "inline" },
...tNodes,
},
marks: {},
});
const cell = (txt: string, attrs?: Record<string, unknown>): PMNode =>
schema.nodes.table_cell.createChecked(attrs ?? null, schema.text(txt));
const row = (...cells: PMNode[]): PMNode =>
schema.nodes.table_row.createChecked(null, cells);
const table = (...rows: PMNode[]): PMNode =>
schema.nodes.table.createChecked(null, rows);
const doc = (...content: PMNode[]): PMNode =>
schema.nodes.doc.createChecked(null, content);
// Build a transaction whose selection is inside the table (the function locates
// the table via `tr.selection.$from`).
const trFor = (d: PMNode) =>
EditorState.create({ doc: d, selection: Selection.atStart(d) }).tr;
// A 2-row x 3-col grid; each column is identifiable by its top-row letter.
const grid3x2 = () =>
doc(
@@ -1,11 +1,15 @@
import { describe, it, expect } from "vitest";
import { Schema } from "@tiptap/pm/model";
import type { Node as PMNode } from "@tiptap/pm/model";
import { tableNodes, CellSelection } from "@tiptap/pm/tables";
import { EditorState, Selection } from "@tiptap/pm/state";
import { CellSelection } from "@tiptap/pm/tables";
import { moveColumn } from "./move-column";
import { convertTableNodeToArrayOfRows } from "./convert-table-node-to-array-of-rows";
import { findTable } from "./query";
import {
schema,
cell,
row,
table,
doc,
grid,
stateFor,
} from "./table-test-helpers";
/**
* moveColumn reorders whole columns of a real ProseMirror table by mutating a
@@ -14,36 +18,6 @@ import { findTable } from "./query";
* cell's content preserved and nothing dropped or duplicated.
*/
const tNodes = tableNodes({
tableGroup: "block",
cellContent: "inline*",
cellAttributes: {},
});
const schema = new Schema({
nodes: {
doc: { content: "block+" },
paragraph: { group: "block", content: "inline*", toDOM: () => ["p", 0] },
text: { group: "inline" },
...tNodes,
},
marks: {},
});
const cell = (txt: string): PMNode =>
schema.nodes.table_cell.createChecked(null, schema.text(txt));
const row = (...cells: PMNode[]): PMNode =>
schema.nodes.table_row.createChecked(null, cells);
const table = (...rows: PMNode[]): PMNode =>
schema.nodes.table.createChecked(null, rows);
const doc = (...content: PMNode[]): PMNode =>
schema.nodes.doc.createChecked(null, content);
const grid = (tr: any): string[][] => {
const t = findTable(tr.doc.resolve(tr.selection.from))!;
return convertTableNodeToArrayOfRows(t.node).map((r) =>
r.map((c) => (c ? c.textContent : "")),
);
};
// 2-row x 3-col table; column k is (rowX-col-k). Columns: 0=(a,d) 1=(b,e) 2=(c,f).
const grid3x2 = () =>
doc(
@@ -53,9 +27,6 @@ const grid3x2 = () =>
),
);
const stateFor = (d: PMNode) =>
EditorState.create({ doc: d, selection: Selection.atStart(d) });
describe("moveColumn", () => {
it("moves the first column to the last index, preserving column content", () => {
// origin 0 -> target 2 sends column (a,d) to the right: cols become 1,2,0.
@@ -1,11 +1,15 @@
import { describe, it, expect } from "vitest";
import { Schema } from "@tiptap/pm/model";
import type { Node as PMNode } from "@tiptap/pm/model";
import { tableNodes, CellSelection } from "@tiptap/pm/tables";
import { EditorState, Selection } from "@tiptap/pm/state";
import { CellSelection } from "@tiptap/pm/tables";
import { moveRow } from "./move-row";
import { convertTableNodeToArrayOfRows } from "./convert-table-node-to-array-of-rows";
import { findTable } from "./query";
import {
schema,
cell,
row,
table,
doc,
grid,
stateFor,
} from "./table-test-helpers";
/**
* moveRow reorders whole rows of a real ProseMirror table by mutating a
@@ -15,38 +19,6 @@ import { findTable } from "./query";
* content preserved, and no rows are dropped or duplicated.
*/
const tNodes = tableNodes({
tableGroup: "block",
cellContent: "inline*",
cellAttributes: {},
});
const schema = new Schema({
nodes: {
doc: { content: "block+" },
paragraph: { group: "block", content: "inline*", toDOM: () => ["p", 0] },
text: { group: "inline" },
...tNodes,
},
marks: {},
});
const cell = (txt: string): PMNode =>
schema.nodes.table_cell.createChecked(null, schema.text(txt));
const row = (...cells: PMNode[]): PMNode =>
schema.nodes.table_row.createChecked(null, cells);
const table = (...rows: PMNode[]): PMNode =>
schema.nodes.table.createChecked(null, rows);
const doc = (...content: PMNode[]): PMNode =>
schema.nodes.doc.createChecked(null, content);
// Read the table's content as a grid of cell texts (rows x cols) from whatever
// table currently lives in `tr.doc`.
const grid = (tr: any): string[][] => {
const t = findTable(tr.doc.resolve(tr.selection.from))!;
return convertTableNodeToArrayOfRows(t.node).map((r) =>
r.map((c) => (c ? c.textContent : "")),
);
};
// 3-row x 2-col table; each row identifiable by its cells.
const grid2x3 = () =>
doc(
@@ -57,9 +29,6 @@ const grid2x3 = () =>
),
);
const stateFor = (d: PMNode) =>
EditorState.create({ doc: d, selection: Selection.atStart(d) });
describe("moveRow", () => {
it("moves the first row down to the last index, preserving content", () => {
// origin 0 -> target 2 makes row 0 land after the other rows: [r1, r2, r0].
@@ -0,0 +1,59 @@
import { Schema } from "@tiptap/pm/model";
import type { Node as PMNode } from "@tiptap/pm/model";
import { tableNodes } from "@tiptap/pm/tables";
import { EditorState, Selection } from "@tiptap/pm/state";
import { findTable } from "./query";
import { convertTableNodeToArrayOfRows } from "./convert-table-node-to-array-of-rows";
/**
* Shared test fixtures for the table utility tests. Several test files exercise
* the row/column move and selection helpers against a real ProseMirror table
* schema (the same primitives the editor uses) so TableMap / cellsInRect behave
* exactly as in production. Keeping the schema and node builders in one place
* means a schema change (e.g. cellAttributes) is applied once instead of being
* copied across every test file.
*
* This is a test-only helper (not shipped) and intentionally contains no test
* cases, so vitest does not pick it up as a spec file.
*/
const tNodes = tableNodes({
tableGroup: "block",
cellContent: "inline*",
cellAttributes: {},
});
export const schema = new Schema({
nodes: {
doc: { content: "block+" },
paragraph: { group: "block", content: "inline*", toDOM: () => ["p", 0] },
text: { group: "inline" },
...tNodes,
},
marks: {},
});
export const cell = (txt: string, attrs?: Record<string, unknown>): PMNode =>
schema.nodes.table_cell.createChecked(attrs ?? null, schema.text(txt));
export const row = (...cells: PMNode[]): PMNode =>
schema.nodes.table_row.createChecked(null, cells);
export const table = (...rows: PMNode[]): PMNode =>
schema.nodes.table.createChecked(null, rows);
export const doc = (...content: PMNode[]): PMNode =>
schema.nodes.doc.createChecked(null, content);
// Read the table's content as a grid of cell texts (rows x cols) from whatever
// table currently lives in `tr.doc`.
export const grid = (tr: any): string[][] => {
const t = findTable(tr.doc.resolve(tr.selection.from))!;
return convertTableNodeToArrayOfRows(t.node).map((r) =>
r.map((c) => (c ? c.textContent : "")),
);
};
export const stateFor = (d: PMNode) =>
EditorState.create({ doc: d, selection: Selection.atStart(d) });
// Build a transaction whose selection is inside the doc (helpers locate the
// table via `tr.selection.$from`).
export const trFor = (d: PMNode) => stateFor(d).tr;
@@ -1,11 +1,11 @@
import { describe, it, expect } from "vitest";
import { Schema } from "@tiptap/pm/model";
import type { Node as PMNode } from "@tiptap/pm/model";
import { tableNodes, TableMap } from "@tiptap/pm/tables";
import { TableMap } from "@tiptap/pm/tables";
import { transpose } from "./transpose";
import { moveRowInArrayOfRows } from "./move-row-in-array-of-rows";
import { convertTableNodeToArrayOfRows } from "./convert-table-node-to-array-of-rows";
import { convertArrayOfRowsToTableNode } from "./convert-array-of-rows-to-table-node";
import { cell, row, table } from "./table-test-helpers";
/**
* Unit tests for the pure table data-transformation utilities. These functions
@@ -14,30 +14,6 @@ import { convertArrayOfRowsToTableNode } from "./convert-array-of-rows-to-table-
* ProseMirror table schema (the same primitives the editor uses).
*/
// Minimal schema containing real ProseMirror table nodes so TableMap behaves
// exactly as it does in the editor (merged cells, colspan, etc.).
const tNodes = tableNodes({
tableGroup: "block",
cellContent: "inline*",
cellAttributes: {},
});
const schema = new Schema({
nodes: {
doc: { content: "block+" },
paragraph: { group: "block", content: "inline*", toDOM: () => ["p", 0] },
text: { group: "inline" },
...tNodes,
},
marks: {},
});
const cell = (txt: string, attrs?: Record<string, unknown>): PMNode =>
schema.nodes.table_cell.createChecked(attrs ?? null, schema.text(txt));
const row = (...cells: PMNode[]): PMNode =>
schema.nodes.table_row.createChecked(null, cells);
const table = (...rows: PMNode[]): PMNode =>
schema.nodes.table.createChecked(null, rows);
// Read the text content of each (non-null) cell so we can compare structure
// without depending on ProseMirror node identity.
const textGrid = (rows: (PMNode | null)[][]): (string | null)[][] =>
+2 -1
View File
@@ -27,6 +27,7 @@
"dist",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/lib/footnote/footnote-corpus.ts"
"src/lib/footnote/footnote-corpus.ts",
"src/lib/table/utils/table-test-helpers.ts"
]
}