testomatio-editor-blocks 0.4.62 → 0.4.64

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.
@@ -93,13 +93,8 @@ export function canInsertStepOrSnippet(editor, referenceBlockId) {
93
93
  * Returns the inserted step's block ID (for focusing), or null.
94
94
  */
95
95
  export function addStepsBlock(editor) {
96
- var _a, _b, _c, _d;
96
+ var _a, _b, _c, _d, _e, _f;
97
97
  const allBlocks = editor.document;
98
- const emptyStep = {
99
- type: "testStep",
100
- props: { stepTitle: "", stepData: "", expectedResult: "" },
101
- children: [],
102
- };
103
98
  let stepsHeadingIndex = -1;
104
99
  for (let i = 0; i < allBlocks.length; i++) {
105
100
  const b = allBlocks[i];
@@ -129,8 +124,17 @@ export function addStepsBlock(editor) {
129
124
  continue;
130
125
  break;
131
126
  }
132
- const inserted = editor.insertBlocks([emptyStep], allBlocks[lastIndex].id, "after");
133
- return (_b = (_a = inserted === null || inserted === void 0 ? void 0 : inserted[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
127
+ const previousStep = allBlocks[lastIndex];
128
+ const inheritedListStyle = (previousStep === null || previousStep === void 0 ? void 0 : previousStep.type) === "testStep"
129
+ ? ((_b = (_a = previousStep.props) === null || _a === void 0 ? void 0 : _a.listStyle) !== null && _b !== void 0 ? _b : "bullet")
130
+ : "bullet";
131
+ const emptyStep = {
132
+ type: "testStep",
133
+ props: { stepTitle: "", stepData: "", expectedResult: "", listStyle: inheritedListStyle },
134
+ children: [],
135
+ };
136
+ const inserted = editor.insertBlocks([emptyStep], previousStep.id, "after");
137
+ return (_d = (_c = inserted === null || inserted === void 0 ? void 0 : inserted[0]) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : null;
134
138
  }
135
139
  const lastBlock = allBlocks[allBlocks.length - 1];
136
140
  const stepsHeading = {
@@ -139,8 +143,13 @@ export function addStepsBlock(editor) {
139
143
  content: [{ type: "text", text: "Steps" }],
140
144
  children: [],
141
145
  };
146
+ const emptyStep = {
147
+ type: "testStep",
148
+ props: { stepTitle: "", stepData: "", expectedResult: "" },
149
+ children: [],
150
+ };
142
151
  const inserted = editor.insertBlocks([stepsHeading, emptyStep], lastBlock.id, "after");
143
- return (_d = (_c = inserted === null || inserted === void 0 ? void 0 : inserted[1]) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : null;
152
+ return (_f = (_e = inserted === null || inserted === void 0 ? void 0 : inserted[1]) === null || _e === void 0 ? void 0 : _e.id) !== null && _f !== void 0 ? _f : null;
144
153
  }
145
154
  /**
146
155
  * Programmatically add an empty snippet block to the editor.
@@ -346,12 +355,14 @@ export const stepBlock = createReactBlockSpec({
346
355
  });
347
356
  }, [editor, block.id, expectedResult]);
348
357
  const handleInsertNextStep = useCallback(() => {
358
+ var _a;
349
359
  const allBlocks = editor.document;
350
360
  const idx = allBlocks.findIndex((b) => b.id === block.id);
351
361
  const next = idx >= 0 ? allBlocks[idx + 1] : null;
352
362
  if (next && isEmptyParagraph(next)) {
353
363
  editor.removeBlocks([next.id]);
354
364
  }
365
+ const currentListStyle = (_a = block.props.listStyle) !== null && _a !== void 0 ? _a : "bullet";
355
366
  editor.insertBlocks([
356
367
  {
357
368
  type: "testStep",
@@ -359,11 +370,12 @@ export const stepBlock = createReactBlockSpec({
359
370
  stepTitle: "",
360
371
  stepData: "",
361
372
  expectedResult: "",
373
+ listStyle: currentListStyle,
362
374
  },
363
375
  children: [],
364
376
  },
365
377
  ], block.id, "after");
366
- }, [editor, block.id]);
378
+ }, [editor, block.id, block.props]);
367
379
  const handleFieldFocus = useCallback(() => {
368
380
  var _a, _b, _c;
369
381
  const selection = editor.getSelection();
@@ -262,7 +262,8 @@ function serializeBlock(block, ctx, orderedIndex, stepIndex) {
262
262
  return lines;
263
263
  }
264
264
  case "codeBlock": {
265
- const language = block.props.language || "";
265
+ const rawLanguage = block.props.language || "";
266
+ const language = /[\s`]/.test(rawLanguage) ? "" : rawLanguage;
266
267
  const fence = "```" + language;
267
268
  const body = inlineContentToPlainText(block.content);
268
269
  lines.push(fence);
@@ -1087,8 +1088,17 @@ function parseCodeBlock(lines, index) {
1087
1088
  nextIndex: index + 1,
1088
1089
  };
1089
1090
  }
1090
- const language = afterOpening.trim();
1091
+ const info = afterOpening.trim();
1092
+ let language = "";
1091
1093
  const body = [];
1094
+ if (info.length > 0) {
1095
+ if (/[\s`]/.test(info)) {
1096
+ body.push(afterOpening);
1097
+ }
1098
+ else {
1099
+ language = info;
1100
+ }
1101
+ }
1092
1102
  let next = index + 1;
1093
1103
  while (next < lines.length && !lines[next].startsWith("```")) {
1094
1104
  body.push(lines[next]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testomatio-editor-blocks",
3
- "version": "0.4.62",
3
+ "version": "0.4.64",
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",
@@ -108,11 +108,6 @@ export function addStepsBlock(editor: {
108
108
  insertBlocks: (blocks: any[], referenceId: string, placement: "before" | "after") => any[];
109
109
  }): string | null {
110
110
  const allBlocks = editor.document;
111
- const emptyStep = {
112
- type: "testStep" as const,
113
- props: { stepTitle: "", stepData: "", expectedResult: "" },
114
- children: [],
115
- };
116
111
 
117
112
  let stepsHeadingIndex = -1;
118
113
  for (let i = 0; i < allBlocks.length; i++) {
@@ -142,7 +137,17 @@ export function addStepsBlock(editor: {
142
137
  if (isEmptyParagraph(b)) continue;
143
138
  break;
144
139
  }
145
- const inserted = editor.insertBlocks([emptyStep], allBlocks[lastIndex].id, "after");
140
+ const previousStep = allBlocks[lastIndex];
141
+ const inheritedListStyle =
142
+ previousStep?.type === "testStep"
143
+ ? ((previousStep.props as any)?.listStyle ?? "bullet")
144
+ : "bullet";
145
+ const emptyStep = {
146
+ type: "testStep" as const,
147
+ props: { stepTitle: "", stepData: "", expectedResult: "", listStyle: inheritedListStyle },
148
+ children: [],
149
+ };
150
+ const inserted = editor.insertBlocks([emptyStep], previousStep.id, "after");
146
151
  return inserted?.[0]?.id ?? null;
147
152
  }
148
153
 
@@ -153,6 +158,11 @@ export function addStepsBlock(editor: {
153
158
  content: [{ type: "text" as const, text: "Steps" }],
154
159
  children: [],
155
160
  };
161
+ const emptyStep = {
162
+ type: "testStep" as const,
163
+ props: { stepTitle: "", stepData: "", expectedResult: "" },
164
+ children: [],
165
+ };
156
166
  const inserted = editor.insertBlocks([stepsHeading, emptyStep], lastBlock.id, "after");
157
167
  return inserted?.[1]?.id ?? null;
158
168
  }
@@ -399,6 +409,7 @@ export const stepBlock = createReactBlockSpec(
399
409
  if (next && isEmptyParagraph(next)) {
400
410
  editor.removeBlocks([next.id]);
401
411
  }
412
+ const currentListStyle = (block.props as any).listStyle ?? "bullet";
402
413
  editor.insertBlocks(
403
414
  [
404
415
  {
@@ -407,6 +418,7 @@ export const stepBlock = createReactBlockSpec(
407
418
  stepTitle: "",
408
419
  stepData: "",
409
420
  expectedResult: "",
421
+ listStyle: currentListStyle,
410
422
  },
411
423
  children: [],
412
424
  },
@@ -414,7 +426,7 @@ export const stepBlock = createReactBlockSpec(
414
426
  block.id,
415
427
  "after",
416
428
  );
417
- }, [editor, block.id]);
429
+ }, [editor, block.id, block.props]);
418
430
 
419
431
  const handleFieldFocus = useCallback(() => {
420
432
  const selection = editor.getSelection();
@@ -1696,6 +1696,36 @@ describe("markdownToBlocks", () => {
1696
1696
  expect(stepBlocks[1].props).toMatchObject({ stepTitle: "Second step", listStyle: "ordered" });
1697
1697
  });
1698
1698
 
1699
+ it("serializes a new ordered step appended to a numbered list as N.", () => {
1700
+ const orderedSteps: CustomEditorBlock[] = [
1701
+ {
1702
+ id: "s1",
1703
+ type: "testStep",
1704
+ props: { stepTitle: "First step", stepData: "", expectedResult: "", listStyle: "ordered" },
1705
+ content: undefined as any,
1706
+ children: [],
1707
+ } as any,
1708
+ {
1709
+ id: "s2",
1710
+ type: "testStep",
1711
+ props: { stepTitle: "Second step", stepData: "", expectedResult: "", listStyle: "ordered" },
1712
+ content: undefined as any,
1713
+ children: [],
1714
+ } as any,
1715
+ {
1716
+ id: "s3",
1717
+ type: "testStep",
1718
+ props: { stepTitle: "Newly added step", stepData: "", expectedResult: "", listStyle: "ordered" },
1719
+ content: undefined as any,
1720
+ children: [],
1721
+ } as any,
1722
+ ];
1723
+
1724
+ expect(blocksToMarkdown(orderedSteps)).toBe(
1725
+ ["1. First step", "2. Second step", "3. Newly added step"].join("\n"),
1726
+ );
1727
+ });
1728
+
1699
1729
  it("parses steps under an h4 'step' heading (lowercase)", () => {
1700
1730
  const markdown = ["#### step", "", "* Do something"].join("\n");
1701
1731
  const blocks = markdownToBlocks(markdown);
@@ -2655,6 +2685,90 @@ describe("markdownToBlocks", () => {
2655
2685
  },
2656
2686
  ]);
2657
2687
  });
2688
+
2689
+ it("preserves opening-fence content when it is not a clean language identifier", () => {
2690
+ const markdown = [
2691
+ "```curl `http://localhost:3000/projects/classic-project/test/e1c1b38c/edit` \\",
2692
+ "{{baseURL}}/endpoint?query_param_one=value_one&query_param_two=value_two",
2693
+ "```",
2694
+ ].join("\n");
2695
+ const blocks = markdownToBlocks(markdown);
2696
+ expect(blocks).toEqual([
2697
+ {
2698
+ type: "codeBlock",
2699
+ props: { language: "" },
2700
+ content: [
2701
+ {
2702
+ type: "text",
2703
+ text: [
2704
+ "curl `http://localhost:3000/projects/classic-project/test/e1c1b38c/edit` \\",
2705
+ "{{baseURL}}/endpoint?query_param_one=value_one&query_param_two=value_two",
2706
+ ].join("\n"),
2707
+ styles: {},
2708
+ },
2709
+ ],
2710
+ children: [],
2711
+ },
2712
+ ]);
2713
+ });
2714
+
2715
+ it("round-trips an opening-fence-with-content code block to stable markdown", () => {
2716
+ const markdown = [
2717
+ "```curl `http://localhost:3000/projects/classic-project/test/e1c1b38c/edit` \\",
2718
+ "{{baseURL}}/endpoint?query_param_one=value_one&query_param_two=value_two",
2719
+ "```",
2720
+ ].join("\n");
2721
+ const blocks = markdownToBlocks(markdown);
2722
+ const serialized = blocksToMarkdown(blocks as CustomEditorBlock[]);
2723
+ expect(serialized).toBe(
2724
+ [
2725
+ "```",
2726
+ "curl `http://localhost:3000/projects/classic-project/test/e1c1b38c/edit` \\",
2727
+ "{{baseURL}}/endpoint?query_param_one=value_one&query_param_two=value_two",
2728
+ "```",
2729
+ ].join("\n"),
2730
+ );
2731
+ expect(markdownToBlocks(serialized)).toEqual(blocks);
2732
+ });
2733
+
2734
+ it("preserves hyphenated language identifiers like shell-session", () => {
2735
+ const markdown = ["```shell-session", "$ ls", "```"].join("\n");
2736
+ const blocks = markdownToBlocks(markdown);
2737
+ expect(blocks).toEqual([
2738
+ {
2739
+ type: "codeBlock",
2740
+ props: { language: "shell-session" },
2741
+ content: [{ type: "text", text: "$ ls", styles: {} }],
2742
+ children: [],
2743
+ },
2744
+ ]);
2745
+ });
2746
+
2747
+ it("preserves digit-prefixed language identifiers like 1c-enterprise", () => {
2748
+ const markdown = ["```1c-enterprise", "code", "```"].join("\n");
2749
+ const blocks = markdownToBlocks(markdown);
2750
+ expect(blocks).toEqual([
2751
+ {
2752
+ type: "codeBlock",
2753
+ props: { language: "1c-enterprise" },
2754
+ content: [{ type: "text", text: "code", styles: {} }],
2755
+ children: [],
2756
+ },
2757
+ ]);
2758
+ });
2759
+
2760
+ it("sanitizes a malformed in-memory language prop on serialize", () => {
2761
+ const blocks: CustomEditorBlock[] = [
2762
+ {
2763
+ id: "1",
2764
+ type: "codeBlock",
2765
+ props: { ...baseProps, language: "curl http://x" } as any,
2766
+ content: [{ type: "text", text: "body", styles: {} }] as any,
2767
+ children: [],
2768
+ },
2769
+ ];
2770
+ expect(blocksToMarkdown(blocks)).toBe(["```", "body", "```"].join("\n"));
2771
+ });
2658
2772
  });
2659
2773
 
2660
2774
  describe("file block serialization", () => {
@@ -334,7 +334,8 @@ function serializeBlock(
334
334
  return lines;
335
335
  }
336
336
  case "codeBlock": {
337
- const language = (block.props as any).language || "";
337
+ const rawLanguage = (block.props as any).language || "";
338
+ const language = /[\s`]/.test(rawLanguage) ? "" : rawLanguage;
338
339
  const fence = "```" + language;
339
340
  const body = inlineContentToPlainText(block.content);
340
341
  lines.push(fence);
@@ -1290,8 +1291,16 @@ function parseCodeBlock(lines: string[], index: number): { block: CustomPartialB
1290
1291
  };
1291
1292
  }
1292
1293
 
1293
- const language = afterOpening.trim();
1294
+ const info = afterOpening.trim();
1295
+ let language = "";
1294
1296
  const body: string[] = [];
1297
+ if (info.length > 0) {
1298
+ if (/[\s`]/.test(info)) {
1299
+ body.push(afterOpening);
1300
+ } else {
1301
+ language = info;
1302
+ }
1303
+ }
1295
1304
  let next = index + 1;
1296
1305
  while (next < lines.length && !lines[next].startsWith("```") ) {
1297
1306
  body.push(lines[next]);