b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
402 lines
9.2 KiB
TypeScript
402 lines
9.2 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
|
|
import {
|
|
_portableTextToProsemirror,
|
|
_prosemirrorToPortableText,
|
|
} from "../../src/components/PortableTextEditor";
|
|
|
|
describe("Table conversion: PortableText ↔ ProseMirror", () => {
|
|
describe("PortableText → ProseMirror", () => {
|
|
it("converts simple table with text", () => {
|
|
const ptBlocks = [
|
|
{
|
|
_type: "table",
|
|
_key: "t1",
|
|
hasHeaderRow: true,
|
|
rows: [
|
|
{
|
|
_type: "tableRow",
|
|
_key: "r1",
|
|
cells: [
|
|
{
|
|
_type: "tableCell",
|
|
_key: "c1",
|
|
isHeader: true,
|
|
content: [{ _type: "span", _key: "s1", text: "Header 1" }],
|
|
},
|
|
{
|
|
_type: "tableCell",
|
|
_key: "c2",
|
|
isHeader: true,
|
|
content: [{ _type: "span", _key: "s2", text: "Header 2" }],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
_type: "tableRow",
|
|
_key: "r2",
|
|
cells: [
|
|
{
|
|
_type: "tableCell",
|
|
_key: "c3",
|
|
content: [{ _type: "span", _key: "s3", text: "Cell 1" }],
|
|
},
|
|
{
|
|
_type: "tableCell",
|
|
_key: "c4",
|
|
content: [{ _type: "span", _key: "s4", text: "Cell 2" }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const result = _portableTextToProsemirror(ptBlocks);
|
|
|
|
expect(result.type).toBe("doc");
|
|
expect(result.content).toHaveLength(1);
|
|
|
|
const table = result.content[0];
|
|
expect(table.type).toBe("table");
|
|
expect(table.content).toHaveLength(2);
|
|
|
|
const headerRow = table.content[0];
|
|
expect(headerRow.type).toBe("tableRow");
|
|
expect(headerRow.content[0].type).toBe("tableHeader");
|
|
expect(headerRow.content[1].type).toBe("tableHeader");
|
|
|
|
const dataRow = table.content[1];
|
|
expect(dataRow.type).toBe("tableRow");
|
|
expect(dataRow.content[0].type).toBe("tableCell");
|
|
});
|
|
|
|
it("converts table with text formatting marks", () => {
|
|
const ptBlocks = [
|
|
{
|
|
_type: "table",
|
|
_key: "t1",
|
|
rows: [
|
|
{
|
|
_type: "tableRow",
|
|
_key: "r1",
|
|
cells: [
|
|
{
|
|
_type: "tableCell",
|
|
_key: "c1",
|
|
content: [{ _type: "span", _key: "s1", text: "Bold", marks: ["strong"] }],
|
|
},
|
|
{
|
|
_type: "tableCell",
|
|
_key: "c2",
|
|
content: [{ _type: "span", _key: "s2", text: "Italic", marks: ["em"] }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const result = _portableTextToProsemirror(ptBlocks);
|
|
|
|
const table = result.content[0];
|
|
const cell1 = table.content[0].content[0];
|
|
const cell1Para = cell1.content[0];
|
|
const cell1Text = cell1Para.content[0];
|
|
|
|
expect(cell1Text.text).toBe("Bold");
|
|
expect(cell1Text.marks).toContainEqual({ type: "bold" });
|
|
|
|
const cell2 = table.content[0].content[1];
|
|
const cell2Para = cell2.content[0];
|
|
const cell2Text = cell2Para.content[0];
|
|
|
|
expect(cell2Text.text).toBe("Italic");
|
|
expect(cell2Text.marks).toContainEqual({ type: "italic" });
|
|
});
|
|
|
|
it("converts table with links (markDefs)", () => {
|
|
const ptBlocks = [
|
|
{
|
|
_type: "table",
|
|
_key: "t1",
|
|
markDefs: [
|
|
{
|
|
_type: "link",
|
|
_key: "link1",
|
|
href: "https://example.com",
|
|
blank: true,
|
|
},
|
|
],
|
|
rows: [
|
|
{
|
|
_type: "tableRow",
|
|
_key: "r1",
|
|
cells: [
|
|
{
|
|
_type: "tableCell",
|
|
_key: "c1",
|
|
content: [{ _type: "span", _key: "s1", text: "Click here", marks: ["link1"] }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const result = _portableTextToProsemirror(ptBlocks);
|
|
|
|
const table = result.content[0];
|
|
const cell = table.content[0].content[0];
|
|
const para = cell.content[0];
|
|
const text = para.content[0];
|
|
|
|
expect(text.text).toBe("Click here");
|
|
expect(text.marks).toContainEqual({
|
|
type: "link",
|
|
attrs: { href: "https://example.com", target: "_blank" },
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("ProseMirror → PortableText", () => {
|
|
it("converts simple table to PT", () => {
|
|
const pmDoc = {
|
|
type: "doc",
|
|
content: [
|
|
{
|
|
type: "table",
|
|
content: [
|
|
{
|
|
type: "tableRow",
|
|
content: [
|
|
{
|
|
type: "tableHeader",
|
|
content: [
|
|
{
|
|
type: "paragraph",
|
|
content: [{ type: "text", text: "Header 1" }],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "tableHeader",
|
|
content: [
|
|
{
|
|
type: "paragraph",
|
|
content: [{ type: "text", text: "Header 2" }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "tableRow",
|
|
content: [
|
|
{
|
|
type: "tableCell",
|
|
content: [
|
|
{
|
|
type: "paragraph",
|
|
content: [{ type: "text", text: "Cell 1" }],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "tableCell",
|
|
content: [
|
|
{
|
|
type: "paragraph",
|
|
content: [{ type: "text", text: "Cell 2" }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = _prosemirrorToPortableText(pmDoc);
|
|
|
|
expect(result).toHaveLength(1);
|
|
const table = result[0] as {
|
|
_type: string;
|
|
rows: Array<{
|
|
cells: Array<{ content: Array<{ text: string }>; isHeader: boolean }>;
|
|
}>;
|
|
hasHeaderRow: boolean;
|
|
};
|
|
|
|
expect(table._type).toBe("table");
|
|
expect(table.hasHeaderRow).toBe(true);
|
|
expect(table.rows).toHaveLength(2);
|
|
expect(table.rows[0].cells[0].isHeader).toBe(true);
|
|
expect(table.rows[0].cells[0].content[0].text).toBe("Header 1");
|
|
expect(table.rows[1].cells[0].isHeader).toBe(false);
|
|
expect(table.rows[1].cells[0].content[0].text).toBe("Cell 1");
|
|
});
|
|
|
|
it("converts table with text marks to PT", () => {
|
|
const pmDoc = {
|
|
type: "doc",
|
|
content: [
|
|
{
|
|
type: "table",
|
|
content: [
|
|
{
|
|
type: "tableRow",
|
|
content: [
|
|
{
|
|
type: "tableCell",
|
|
content: [
|
|
{
|
|
type: "paragraph",
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: "Bold text",
|
|
marks: [{ type: "bold" }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = _prosemirrorToPortableText(pmDoc);
|
|
|
|
const table = result[0] as {
|
|
rows: Array<{
|
|
cells: Array<{ content: Array<{ text: string; marks?: string[] }> }>;
|
|
}>;
|
|
};
|
|
|
|
expect(table.rows[0].cells[0].content[0].text).toBe("Bold text");
|
|
expect(table.rows[0].cells[0].content[0].marks).toContain("strong");
|
|
});
|
|
|
|
it("converts table with links to PT (preserves markDefs)", () => {
|
|
const pmDoc = {
|
|
type: "doc",
|
|
content: [
|
|
{
|
|
type: "table",
|
|
content: [
|
|
{
|
|
type: "tableRow",
|
|
content: [
|
|
{
|
|
type: "tableCell",
|
|
content: [
|
|
{
|
|
type: "paragraph",
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: "Link text",
|
|
marks: [
|
|
{
|
|
type: "link",
|
|
attrs: {
|
|
href: "https://example.com",
|
|
target: "_blank",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = _prosemirrorToPortableText(pmDoc);
|
|
|
|
const table = result[0] as {
|
|
rows: Array<{
|
|
cells: Array<{
|
|
content: Array<{ text: string; marks?: string[] }>;
|
|
markDefs?: Array<{ _key: string; _type: string; href: string; blank?: boolean }>;
|
|
}>;
|
|
}>;
|
|
};
|
|
|
|
const cell = table.rows[0].cells[0];
|
|
expect(cell.markDefs).toBeDefined();
|
|
expect(cell.markDefs).toHaveLength(1);
|
|
expect(cell.markDefs![0]._type).toBe("link");
|
|
expect(cell.markDefs![0].href).toBe("https://example.com");
|
|
|
|
const linkMarkKey = cell.markDefs![0]._key;
|
|
expect(cell.content[0].marks).toContain(linkMarkKey);
|
|
});
|
|
});
|
|
|
|
describe("Round-trip conversion", () => {
|
|
it("PT → PM → PT preserves table structure", () => {
|
|
const original = [
|
|
{
|
|
_type: "table",
|
|
_key: "t1",
|
|
hasHeaderRow: true,
|
|
rows: [
|
|
{
|
|
_type: "tableRow",
|
|
_key: "r1",
|
|
cells: [
|
|
{
|
|
_type: "tableCell",
|
|
_key: "c1",
|
|
isHeader: true,
|
|
content: [{ _type: "span", _key: "s1", text: "Header" }],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
_type: "tableRow",
|
|
_key: "r2",
|
|
cells: [
|
|
{
|
|
_type: "tableCell",
|
|
_key: "c2",
|
|
content: [{ _type: "span", _key: "s2", text: "Data" }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const pm = _portableTextToProsemirror(original);
|
|
const roundTripped = _prosemirrorToPortableText(pm);
|
|
|
|
const table = roundTripped[0] as {
|
|
_type: string;
|
|
hasHeaderRow: boolean;
|
|
rows: Array<{
|
|
cells: Array<{ content: Array<{ text: string }>; isHeader: boolean }>;
|
|
}>;
|
|
};
|
|
|
|
expect(table._type).toBe("table");
|
|
expect(table.hasHeaderRow).toBe(true);
|
|
expect(table.rows[0].cells[0].isHeader).toBe(true);
|
|
expect(table.rows[0].cells[0].content[0].text).toBe("Header");
|
|
expect(table.rows[1].cells[0].isHeader).toBe(false);
|
|
expect(table.rows[1].cells[0].content[0].text).toBe("Data");
|
|
});
|
|
});
|
|
});
|