testomatio-editor-blocks 0.4.29 → 0.4.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -266,6 +266,16 @@ function serializeBlock(block, ctx, orderedIndex, stepIndex) {
266
266
  }
267
267
  return flattenWithBlankLine(lines, true);
268
268
  }
269
+ case "file": {
270
+ const url = block.props.url || "";
271
+ const name = block.props.name || "";
272
+ const caption = block.props.caption || "";
273
+ if (url) {
274
+ const displayUrl = caption || url;
275
+ lines.push(`[![${name}](${displayUrl})](${url})`);
276
+ }
277
+ return flattenWithBlankLine(lines, true);
278
+ }
269
279
  case "testStep":
270
280
  case "snippet": {
271
281
  const isSnippet = block.type === "snippet";
@@ -1214,6 +1224,20 @@ export function markdownToBlocks(markdown) {
1214
1224
  index = nextIndex;
1215
1225
  continue;
1216
1226
  }
1227
+ const fileMatch = line.trim().match(/^\[!\[([^\]]*)\]\(([^)]*)\)\]\(([^)]+)\)$/);
1228
+ if (fileMatch) {
1229
+ blocks.push({
1230
+ type: "file",
1231
+ props: {
1232
+ name: fileMatch[1] || "",
1233
+ caption: fileMatch[2] || "",
1234
+ url: fileMatch[3],
1235
+ },
1236
+ children: [],
1237
+ });
1238
+ index += 1;
1239
+ continue;
1240
+ }
1217
1241
  const imageMatch = line.trim().match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
1218
1242
  if (imageMatch) {
1219
1243
  blocks.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testomatio-editor-blocks",
3
- "version": "0.4.29",
3
+ "version": "0.4.30",
4
4
  "description": "Custom BlockNote schema, markdown conversion helpers, and UI for Testomatio-style test cases and steps.",
5
5
  "type": "module",
6
6
  "main": "./package/index.js",
package/src/App.tsx CHANGED
@@ -349,6 +349,9 @@ function App() {
349
349
  const editor = useCreateBlockNote({
350
350
  schema: customSchema,
351
351
  pasteHandler: createMarkdownPasteHandler(markdownToBlocks),
352
+ uploadFile: async (file: File) => {
353
+ return `https://placehold.co/600x400?text=${encodeURIComponent(file.name)}`;
354
+ },
352
355
  });
353
356
  const [markdown, setMarkdown] = useState("");
354
357
  const [conversionError, setConversionError] = useState<string | null>(null);
@@ -1513,3 +1513,114 @@ describe("markdownToBlocks", () => {
1513
1513
  expect(roundTripMarkdown).not.toMatch(/\n!\s*$/);
1514
1514
  });
1515
1515
  });
1516
+
1517
+ describe("file block serialization", () => {
1518
+ it("serializes a file block with name, caption (display_url), and url", () => {
1519
+ const blocks: CustomEditorBlock[] = [
1520
+ {
1521
+ id: "1",
1522
+ type: "file",
1523
+ props: {
1524
+ ...baseProps,
1525
+ url: "https://example.com/file.pdf",
1526
+ name: "report.pdf",
1527
+ caption: "/images/file-type-icons/pdf.svg",
1528
+ },
1529
+ content: undefined as any,
1530
+ children: [],
1531
+ },
1532
+ ];
1533
+ const md = blocksToMarkdown(blocks);
1534
+ expect(md).toBe("[![report.pdf](/images/file-type-icons/pdf.svg)](https://example.com/file.pdf)");
1535
+ });
1536
+
1537
+ it("falls back to url when caption is empty", () => {
1538
+ const blocks: CustomEditorBlock[] = [
1539
+ {
1540
+ id: "1",
1541
+ type: "file",
1542
+ props: {
1543
+ ...baseProps,
1544
+ url: "https://example.com/file.pdf",
1545
+ name: "file.pdf",
1546
+ caption: "",
1547
+ },
1548
+ content: undefined as any,
1549
+ children: [],
1550
+ },
1551
+ ];
1552
+ const md = blocksToMarkdown(blocks);
1553
+ expect(md).toBe("[![file.pdf](https://example.com/file.pdf)](https://example.com/file.pdf)");
1554
+ });
1555
+
1556
+ it("outputs nothing when url is empty", () => {
1557
+ const blocks: CustomEditorBlock[] = [
1558
+ {
1559
+ id: "1",
1560
+ type: "file",
1561
+ props: {
1562
+ ...baseProps,
1563
+ url: "",
1564
+ name: "file.pdf",
1565
+ caption: "",
1566
+ },
1567
+ content: undefined as any,
1568
+ children: [],
1569
+ },
1570
+ ];
1571
+ const md = blocksToMarkdown(blocks);
1572
+ expect(md.trim()).toBe("");
1573
+ });
1574
+ });
1575
+
1576
+ describe("file block parsing", () => {
1577
+ it("parses file markdown into a file block", () => {
1578
+ const markdown = "[![report.pdf](/images/file-type-icons/pdf.svg)](https://example.com/file.pdf)";
1579
+ const blocks = markdownToBlocks(markdown);
1580
+ expect(blocks).toHaveLength(1);
1581
+ expect(blocks[0].type).toBe("file");
1582
+ expect((blocks[0].props as any).name).toBe("report.pdf");
1583
+ expect((blocks[0].props as any).caption).toBe("/images/file-type-icons/pdf.svg");
1584
+ expect((blocks[0].props as any).url).toBe("https://example.com/file.pdf");
1585
+ });
1586
+
1587
+ it("parses file markdown with empty name", () => {
1588
+ const markdown = "[![](https://example.com/url)](https://example.com/url)";
1589
+ const blocks = markdownToBlocks(markdown);
1590
+ expect(blocks).toHaveLength(1);
1591
+ expect(blocks[0].type).toBe("file");
1592
+ expect((blocks[0].props as any).name).toBe("");
1593
+ expect((blocks[0].props as any).url).toBe("https://example.com/url");
1594
+ });
1595
+
1596
+ it("does not confuse file blocks with image blocks", () => {
1597
+ const markdown = "![caption](https://example.com/image.png)";
1598
+ const blocks = markdownToBlocks(markdown);
1599
+ expect(blocks).toHaveLength(1);
1600
+ expect(blocks[0].type).toBe("image");
1601
+ });
1602
+
1603
+ it("round-trips file blocks through serialize and parse", () => {
1604
+ const blocks: CustomEditorBlock[] = [
1605
+ {
1606
+ id: "1",
1607
+ type: "file",
1608
+ props: {
1609
+ ...baseProps,
1610
+ url: "https://example.com/doc.xlsx",
1611
+ name: "doc.xlsx",
1612
+ caption: "/images/file-type-icons/xlsx.svg",
1613
+ },
1614
+ content: undefined as any,
1615
+ children: [],
1616
+ },
1617
+ ];
1618
+ const md = blocksToMarkdown(blocks);
1619
+ const parsed = markdownToBlocks(md);
1620
+ expect(parsed).toHaveLength(1);
1621
+ expect(parsed[0].type).toBe("file");
1622
+ expect((parsed[0].props as any).url).toBe("https://example.com/doc.xlsx");
1623
+ expect((parsed[0].props as any).name).toBe("doc.xlsx");
1624
+ expect((parsed[0].props as any).caption).toBe("/images/file-type-icons/xlsx.svg");
1625
+ });
1626
+ });
@@ -344,6 +344,16 @@ function serializeBlock(
344
344
  }
345
345
  return flattenWithBlankLine(lines, true);
346
346
  }
347
+ case "file": {
348
+ const url = (block.props as any).url || "";
349
+ const name = (block.props as any).name || "";
350
+ const caption = (block.props as any).caption || "";
351
+ if (url) {
352
+ const displayUrl = caption || url;
353
+ lines.push(`[![${name}](${displayUrl})](${url})`);
354
+ }
355
+ return flattenWithBlankLine(lines, true);
356
+ }
347
357
  case "testStep":
348
358
  case "snippet": {
349
359
  const isSnippet = block.type === "snippet";
@@ -1456,6 +1466,21 @@ export function markdownToBlocks(markdown: string): CustomPartialBlock[] {
1456
1466
  continue;
1457
1467
  }
1458
1468
 
1469
+ const fileMatch = line.trim().match(/^\[!\[([^\]]*)\]\(([^)]*)\)\]\(([^)]+)\)$/);
1470
+ if (fileMatch) {
1471
+ blocks.push({
1472
+ type: "file",
1473
+ props: {
1474
+ name: fileMatch[1] || "",
1475
+ caption: fileMatch[2] || "",
1476
+ url: fileMatch[3],
1477
+ },
1478
+ children: [],
1479
+ } as CustomPartialBlock);
1480
+ index += 1;
1481
+ continue;
1482
+ }
1483
+
1459
1484
  const imageMatch = line.trim().match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
1460
1485
  if (imageMatch) {
1461
1486
  blocks.push({