testomatio-editor-blocks 0.4.1 → 0.4.6

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.
@@ -12,6 +12,9 @@ export declare const stepBlock: {
12
12
  readonly expectedResult: {
13
13
  readonly default: "";
14
14
  };
15
+ readonly listStyle: {
16
+ readonly default: "bullet";
17
+ };
15
18
  };
16
19
  };
17
20
  implementation: import("@blocknote/core").TiptapBlockImplementation<{
@@ -27,6 +30,9 @@ export declare const stepBlock: {
27
30
  readonly expectedResult: {
28
31
  readonly default: "";
29
32
  };
33
+ readonly listStyle: {
34
+ readonly default: "bullet";
35
+ };
30
36
  };
31
37
  }, any, import("@blocknote/core").InlineContentSchema, import("@blocknote/core").StyleSchema>;
32
38
  };
@@ -56,6 +56,9 @@ export const stepBlock = createReactBlockSpec({
56
56
  expectedResult: {
57
57
  default: "",
58
58
  },
59
+ listStyle: {
60
+ default: "bullet",
61
+ },
59
62
  },
60
63
  }, {
61
64
  render: ({ block, editor }) => {
@@ -190,8 +190,8 @@ function flattenWithBlankLine(lines, appendBlank = false) {
190
190
  }
191
191
  return lines;
192
192
  }
193
- function serializeBlock(block, ctx, orderedIndex) {
194
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
193
+ function serializeBlock(block, ctx, orderedIndex, stepIndex) {
194
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
195
195
  const lines = [];
196
196
  const indent = ctx.listDepth > 0 ? " ".repeat(ctx.listDepth) : "";
197
197
  switch (block.type) {
@@ -292,7 +292,9 @@ function serializeBlock(block, ctx, orderedIndex) {
292
292
  .filter((segment) => segment.length > 0)
293
293
  .join(" ");
294
294
  if (normalizedTitle.length > 0) {
295
- lines.push(`* ${normalizedTitle}`);
295
+ const listStyle = (_m = block.props.listStyle) !== null && _m !== void 0 ? _m : "bullet";
296
+ const prefix = listStyle === "ordered" ? `${(stepIndex !== null && stepIndex !== void 0 ? stepIndex : 0) + 1}.` : "*";
297
+ lines.push(`${prefix} ${normalizedTitle}`);
296
298
  }
297
299
  }
298
300
  if (stepData.length > 0) {
@@ -348,7 +350,7 @@ function serializeBlock(block, ctx, orderedIndex) {
348
350
  return flattenWithBlankLine(lines, true);
349
351
  }
350
352
  const headerRowCount = rows.length
351
- ? Math.min(rows.length, Math.max((_m = tableContent.headerRows) !== null && _m !== void 0 ? _m : 1, 1))
353
+ ? Math.min(rows.length, Math.max((_o = tableContent.headerRows) !== null && _o !== void 0 ? _o : 1, 1))
352
354
  : 0;
353
355
  const columnAlignments = new Array(columnCount).fill("left");
354
356
  const getCellAlignment = (cell) => {
@@ -425,6 +427,7 @@ function serializeBlock(block, ctx, orderedIndex) {
425
427
  function serializeBlocks(blocks, ctx) {
426
428
  const lines = [];
427
429
  let orderedIndex = null;
430
+ let stepIndex = 0;
428
431
  for (const block of blocks) {
429
432
  if (block.type === "numberedListItem") {
430
433
  if (typeof block.props.start === "number") {
@@ -437,6 +440,13 @@ function serializeBlocks(blocks, ctx) {
437
440
  orderedIndex += 1;
438
441
  continue;
439
442
  }
443
+ if (block.type === "testStep") {
444
+ lines.push(...serializeBlock(block, ctx, undefined, stepIndex));
445
+ stepIndex += 1;
446
+ orderedIndex = null;
447
+ continue;
448
+ }
449
+ stepIndex = 0;
440
450
  orderedIndex = null;
441
451
  lines.push(...serializeBlock(block, ctx));
442
452
  }
@@ -616,7 +626,7 @@ function parseList(lines, startIndex, listType, indentLevel, allowEmptySteps = f
616
626
  // Only try to parse as testStep for top-level items (indentLevel === 0)
617
627
  // when we're under a Steps heading AND the list type is bullet
618
628
  // Numbered lists under Steps heading are only parsed as test steps if they look like test steps
619
- if (indentLevel === 0 && allowEmptySteps) {
629
+ if (indentLevel === 0 && (allowEmptySteps || listType === "bullet")) {
620
630
  // For bullet lists, always try to parse as test steps
621
631
  // For numbered lists, only try if they have step-like characteristics
622
632
  const looksLikeTestStep = listType === "bullet" ||
@@ -855,7 +865,8 @@ function parseTestStep(lines, index, allowEmpty = false, snippetId) {
855
865
  .map((line) => line.trimEnd())
856
866
  .join("\n")
857
867
  .trim();
858
- if (!isLikelyStep &&
868
+ if (!isBullet &&
869
+ !isLikelyStep &&
859
870
  !expectedResult &&
860
871
  stepDataLines.length === 0 &&
861
872
  !(allowEmpty && titleWithPlaceholders.length > 0)) {
@@ -875,6 +886,7 @@ function parseTestStep(lines, index, allowEmpty = false, snippetId) {
875
886
  stepTitle: titleWithPlaceholders,
876
887
  stepData: stepDataWithImages,
877
888
  expectedResult,
889
+ listStyle: isNumbered ? "ordered" : "bullet",
878
890
  };
879
891
  const parsedBlock = {
880
892
  type: blockType,
@@ -15,6 +15,9 @@ export declare const customSchema: BlockNoteSchema<import("@blocknote/core").Blo
15
15
  readonly expectedResult: {
16
16
  readonly default: "";
17
17
  };
18
+ readonly listStyle: {
19
+ readonly default: "bullet";
20
+ };
18
21
  };
19
22
  };
20
23
  implementation: import("@blocknote/core").TiptapBlockImplementation<{
@@ -30,6 +33,9 @@ export declare const customSchema: BlockNoteSchema<import("@blocknote/core").Blo
30
33
  readonly expectedResult: {
31
34
  readonly default: "";
32
35
  };
36
+ readonly listStyle: {
37
+ readonly default: "bullet";
38
+ };
33
39
  };
34
40
  }, any, import("@blocknote/core").InlineContentSchema, import("@blocknote/core").StyleSchema>;
35
41
  };
@@ -249,8 +249,8 @@
249
249
  padding-bottom: 8px;
250
250
  }
251
251
 
252
- .bn-teststep__header + .bn-step-field {
253
- margin-top: calc(8px - var(--step-section-gap));
252
+ .bn-teststep__content > .bn-teststep__header + .bn-step-field {
253
+ margin-top: calc(4px - var(--step-section-gap));
254
254
  }
255
255
 
256
256
  .bn-teststep__header {
@@ -579,7 +579,7 @@
579
579
  .bn-step-field {
580
580
  display: flex;
581
581
  flex-direction: column;
582
- gap: 8px;
582
+ gap: 4px;
583
583
  position: relative;
584
584
  }
585
585
 
@@ -801,6 +801,10 @@
801
801
  font-weight: 400 !important;
802
802
  }
803
803
 
804
+ .bn-step-editor .overtype-wrapper .overtype-placeholder {
805
+ display: none !important;
806
+ }
807
+
804
808
  .bn-step-editor .overtype-wrapper .overtype-preview {
805
809
  color: var(--text-primary) !important;
806
810
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testomatio-editor-blocks",
3
- "version": "0.4.1",
3
+ "version": "0.4.6",
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",
@@ -61,6 +61,9 @@ export const stepBlock = createReactBlockSpec(
61
61
  expectedResult: {
62
62
  default: "",
63
63
  },
64
+ listStyle: {
65
+ default: "bullet",
66
+ },
64
67
  },
65
68
  },
66
69
  {
@@ -75,6 +75,7 @@ describe("blocksToMarkdown", () => {
75
75
  stepTitle: "Open the Login page.",
76
76
  stepData: "",
77
77
  expectedResult: "The Login page loads successfully.",
78
+ listStyle: "bullet",
78
79
  },
79
80
  content: undefined,
80
81
  children: [],
@@ -86,6 +87,7 @@ describe("blocksToMarkdown", () => {
86
87
  stepTitle: "Enter a valid username.",
87
88
  stepData: "",
88
89
  expectedResult: "The username is accepted.",
90
+ listStyle: "bullet",
89
91
  },
90
92
  content: undefined,
91
93
  children: [],
@@ -182,6 +184,7 @@ describe("blocksToMarkdown", () => {
182
184
  stepTitle: "**Click** the _Login_ button",
183
185
  stepData: "",
184
186
  expectedResult: "**Success** is shown\nSecond line with <u>underline</u>",
187
+ listStyle: "bullet",
185
188
  },
186
189
  content: undefined,
187
190
  children: [],
@@ -206,6 +209,7 @@ describe("blocksToMarkdown", () => {
206
209
  stepTitle: "Navigate to login",
207
210
  stepData: "Open browser\nGo to login page",
208
211
  expectedResult: "Login form visible",
212
+ listStyle: "bullet",
209
213
  },
210
214
  content: undefined,
211
215
  children: [],
@@ -243,6 +247,7 @@ describe("blocksToMarkdown", () => {
243
247
  "![](/attachments/HMhkVtlDrO.png)",
244
248
  ].join("\n"),
245
249
  expectedResult: "The user receives a real-time notification for the order update.",
250
+ listStyle: "bullet",
246
251
  },
247
252
  content: undefined,
248
253
  children: [],
@@ -352,6 +357,7 @@ describe("markdownToBlocks", () => {
352
357
  stepTitle: "Open the Login page.",
353
358
  stepData: "",
354
359
  expectedResult: "The Login page loads successfully.",
360
+ listStyle: "bullet",
355
361
  },
356
362
  children: [],
357
363
  },
@@ -458,12 +464,43 @@ describe("markdownToBlocks", () => {
458
464
  const stepBlocks = blocks.filter((block) => block.type === "testStep");
459
465
 
460
466
  expect(stepBlocks).toEqual([
467
+ {
468
+ type: "testStep",
469
+ props: {
470
+ stepTitle: "The user is logged into the application.",
471
+ stepData: "",
472
+ expectedResult: "",
473
+ listStyle: "bullet",
474
+ },
475
+ children: [],
476
+ },
477
+ {
478
+ type: "testStep",
479
+ props: {
480
+ stepTitle: "The user has the necessary permissions to receive notifications.",
481
+ stepData: "",
482
+ expectedResult: "",
483
+ listStyle: "bullet",
484
+ },
485
+ children: [],
486
+ },
487
+ {
488
+ type: "testStep",
489
+ props: {
490
+ stepTitle: "The application is configured to send real-time notifications.",
491
+ stepData: "",
492
+ expectedResult: "",
493
+ listStyle: "bullet",
494
+ },
495
+ children: [],
496
+ },
461
497
  {
462
498
  type: "testStep",
463
499
  props: {
464
500
  stepTitle: "Step 1: Send a chat message to the user.",
465
501
  stepData: "",
466
502
  expectedResult: "The user receives a real-time notification for the chat message.",
503
+ listStyle: "bullet",
467
504
  },
468
505
  children: [],
469
506
  },
@@ -473,6 +510,7 @@ describe("markdownToBlocks", () => {
473
510
  stepTitle: "Step 2: Update an order status.",
474
511
  stepData: "",
475
512
  expectedResult: "The user receives a real-time notification for the order update.",
513
+ listStyle: "bullet",
476
514
  },
477
515
  children: [],
478
516
  },
@@ -482,6 +520,7 @@ describe("markdownToBlocks", () => {
482
520
  stepTitle: "Step 3: Send a file to the user.",
483
521
  stepData: "",
484
522
  expectedResult: "The user receives a real-time notification for the file received.",
523
+ listStyle: "bullet",
485
524
  },
486
525
  children: [],
487
526
  },
@@ -491,6 +530,27 @@ describe("markdownToBlocks", () => {
491
530
  stepTitle: "Step 4: Verify that the notifications are displayed correctly in the application's notification panel.",
492
531
  stepData: "",
493
532
  expectedResult: "All notifications (chat message, order update, file received) are listed in the notification panel with the correct information (e.g., timestamp, message content).\n",
533
+ listStyle: "bullet",
534
+ },
535
+ children: [],
536
+ },
537
+ {
538
+ type: "testStep",
539
+ props: {
540
+ stepTitle: "The user has received and viewed the notifications.",
541
+ stepData: "",
542
+ expectedResult: "",
543
+ listStyle: "bullet",
544
+ },
545
+ children: [],
546
+ },
547
+ {
548
+ type: "testStep",
549
+ props: {
550
+ stepTitle: "The application continues to function as expected after receiving and processing the notifications.",
551
+ stepData: "",
552
+ expectedResult: "",
553
+ listStyle: "bullet",
494
554
  },
495
555
  children: [],
496
556
  },
@@ -535,6 +595,7 @@ describe("markdownToBlocks", () => {
535
595
  stepTitle: "Step 2: Update an order status.",
536
596
  stepData: expectedData,
537
597
  expectedResult: "The user receives a real-time notification for the order update.",
598
+ listStyle: "bullet",
538
599
  },
539
600
  children: [],
540
601
  },
@@ -548,6 +609,7 @@ describe("markdownToBlocks", () => {
548
609
  stepTitle: "Step 2: Update an order status.",
549
610
  stepData: expectedData,
550
611
  expectedResult: "The user receives a real-time notification for the order update.",
612
+ listStyle: "bullet",
551
613
  },
552
614
  content: undefined,
553
615
  children: [],
@@ -590,39 +652,33 @@ describe("markdownToBlocks", () => {
590
652
  children: [],
591
653
  },
592
654
  {
593
- type: "bulletListItem",
594
- props: baseProps,
595
- content: [
596
- {
597
- type: "text",
598
- text: "The user is logged into the application.",
599
- styles: {},
600
- },
601
- ],
655
+ type: "testStep",
656
+ props: {
657
+ stepTitle: "The user is logged into the application.",
658
+ stepData: "",
659
+ expectedResult: "",
660
+ listStyle: "bullet",
661
+ },
602
662
  children: [],
603
663
  },
604
664
  {
605
- type: "bulletListItem",
606
- props: baseProps,
607
- content: [
608
- {
609
- type: "text",
610
- text: "The user has the necessary permissions to receive notifications.",
611
- styles: {},
612
- },
613
- ],
665
+ type: "testStep",
666
+ props: {
667
+ stepTitle: "The user has the necessary permissions to receive notifications.",
668
+ stepData: "",
669
+ expectedResult: "",
670
+ listStyle: "bullet",
671
+ },
614
672
  children: [],
615
673
  },
616
674
  {
617
- type: "bulletListItem",
618
- props: baseProps,
619
- content: [
620
- {
621
- type: "text",
622
- text: "The application is configured to send real-time notifications.",
623
- styles: {},
624
- },
625
- ],
675
+ type: "testStep",
676
+ props: {
677
+ stepTitle: "The application is configured to send real-time notifications.",
678
+ stepData: "",
679
+ expectedResult: "",
680
+ listStyle: "bullet",
681
+ },
626
682
  children: [],
627
683
  },
628
684
  ]);
@@ -664,6 +720,7 @@ describe("markdownToBlocks", () => {
664
720
  stepTitle: "Open the form.",
665
721
  stepData: "",
666
722
  expectedResult: "** The form opens.\nFields are empty.",
723
+ listStyle: "bullet",
667
724
  },
668
725
  children: [],
669
726
  },
@@ -685,6 +742,7 @@ describe("markdownToBlocks", () => {
685
742
  stepTitle: "Navigate to login",
686
743
  stepData: "Open browser\nGo to login page",
687
744
  expectedResult: "Login form visible",
745
+ listStyle: "bullet",
688
746
  },
689
747
  children: [],
690
748
  },
@@ -706,6 +764,7 @@ describe("markdownToBlocks", () => {
706
764
  stepTitle: "Prepare test fixtures",
707
765
  stepData: "Collect user accounts from staging.\nReset passwords for all test accounts.",
708
766
  expectedResult: "Test accounts are ready for execution.",
767
+ listStyle: "bullet",
709
768
  },
710
769
  children: [],
711
770
  },
@@ -725,6 +784,7 @@ describe("markdownToBlocks", () => {
725
784
  stepTitle: "Display the generated report.",
726
785
  stepData: "",
727
786
  expectedResult: "![](/attachments/report.png)",
787
+ listStyle: "bullet",
728
788
  },
729
789
  children: [],
730
790
  },
@@ -738,6 +798,7 @@ describe("markdownToBlocks", () => {
738
798
  stepTitle: "Display the generated report.",
739
799
  stepData: "",
740
800
  expectedResult: "![](/attachments/report.png)",
801
+ listStyle: "bullet",
741
802
  },
742
803
  content: undefined,
743
804
  children: [],
@@ -761,6 +822,7 @@ describe("markdownToBlocks", () => {
761
822
  stepTitle: "Should open login screen",
762
823
  stepData: "",
763
824
  expectedResult: "Login should look like this\n![](/login.png)",
825
+ listStyle: "bullet",
764
826
  },
765
827
  children: [],
766
828
  },
@@ -774,6 +836,7 @@ describe("markdownToBlocks", () => {
774
836
  stepTitle: "Should open login screen",
775
837
  stepData: "",
776
838
  expectedResult: "Login should look like this\n![](/login.png)",
839
+ listStyle: "bullet",
777
840
  },
778
841
  content: undefined,
779
842
  children: [],
@@ -810,6 +873,7 @@ describe("markdownToBlocks", () => {
810
873
  stepTitle: "Pass onboarding as mobile user",
811
874
  stepData: "",
812
875
  expectedResult: "",
876
+ listStyle: "bullet",
813
877
  },
814
878
  children: [],
815
879
  },
@@ -820,6 +884,7 @@ describe("markdownToBlocks", () => {
820
884
  "Navigate to More tab -≻ My Profile -≻ Log into the app with user from preconditions",
821
885
  stepData: "",
822
886
  expectedResult: "* Upsell SS screen is displayed",
887
+ listStyle: "bullet",
823
888
  },
824
889
  children: [],
825
890
  },
@@ -829,6 +894,7 @@ describe("markdownToBlocks", () => {
829
894
  stepTitle: "Close SS",
830
895
  stepData: "",
831
896
  expectedResult: "* My Course and More tab are displayed",
897
+ listStyle: "bullet",
832
898
  },
833
899
  children: [],
834
900
  },
@@ -860,6 +926,7 @@ describe("markdownToBlocks", () => {
860
926
  stepTitle: "Existing email + invalid password",
861
927
  stepData: "",
862
928
  expectedResult: "'Oops, wrong email or password' is displayed",
929
+ listStyle: "bullet",
863
930
  },
864
931
  children: [],
865
932
  });
@@ -870,6 +937,7 @@ describe("markdownToBlocks", () => {
870
937
  stepTitle: "Not existing email + valid password",
871
938
  stepData: "",
872
939
  expectedResult: "'Oops, wrong email or password' is displayed",
940
+ listStyle: "bullet",
873
941
  },
874
942
  children: [],
875
943
  });
@@ -910,7 +978,55 @@ describe("markdownToBlocks", () => {
910
978
 
911
979
  const markdown = blocksToMarkdown(blocks);
912
980
  const parsed = markdownToBlocks(markdown);
913
- expect(parsed).toEqual(blocks.map(toPartial));
981
+ expect(parsed).toEqual([
982
+ {
983
+ type: "paragraph",
984
+ props: baseProps,
985
+ content: [{ type: "text", text: "Paragraph", styles: {} }],
986
+ children: [],
987
+ },
988
+ {
989
+ type: "testStep",
990
+ props: {
991
+ stepTitle: "Bullet",
992
+ stepData: "",
993
+ expectedResult: "",
994
+ listStyle: "bullet",
995
+ },
996
+ children: [],
997
+ },
998
+ ]);
999
+ });
1000
+
1001
+ it("round-trips bullet steps preserving bullet style", () => {
1002
+ const markdown = [
1003
+ "* Open the page",
1004
+ " *Expected*: Page loads",
1005
+ "* Click button",
1006
+ ].join("\n");
1007
+
1008
+ const blocks = markdownToBlocks(markdown);
1009
+ const output = blocksToMarkdown(blocks as CustomEditorBlock[]);
1010
+
1011
+ expect(output).toContain("* Open the page");
1012
+ expect(output).toContain("* Click button");
1013
+ expect(output).not.toMatch(/^\d+\./m);
1014
+ });
1015
+
1016
+ it("round-trips ordered steps preserving ordered style", () => {
1017
+ const markdown = [
1018
+ "### Steps",
1019
+ "",
1020
+ "1. Open the page",
1021
+ " *Expected*: Page loads",
1022
+ "2. Click button",
1023
+ ].join("\n");
1024
+
1025
+ const blocks = markdownToBlocks(markdown);
1026
+ const output = blocksToMarkdown(blocks as CustomEditorBlock[]);
1027
+
1028
+ expect(output).toContain("1. Open the page");
1029
+ expect(output).toContain("2. Click button");
914
1030
  });
915
1031
 
916
1032
  it("parses markdown tables", () => {
@@ -977,6 +1093,7 @@ describe("markdownToBlocks", () => {
977
1093
  stepTitle: "Step 1: Send a chat message to the user.",
978
1094
  stepData: "",
979
1095
  expectedResult: "The user receives a real-time notification for the chat message.",
1096
+ listStyle: "bullet",
980
1097
  },
981
1098
  children: [],
982
1099
  },
@@ -1009,6 +1126,7 @@ describe("markdownToBlocks", () => {
1009
1126
  stepTitle: "Swipe Back",
1010
1127
  stepData: "",
1011
1128
  expectedResult: "",
1129
+ listStyle: "bullet",
1012
1130
  },
1013
1131
  children: [],
1014
1132
  });
@@ -1019,6 +1137,7 @@ describe("markdownToBlocks", () => {
1019
1137
  stepTitle: "Check UI of Sleep score info screen",
1020
1138
  stepData: "- Back button\nHeader: Sleep Score Info\nText: Ever wonder if 6, 8, or 9 hours of sleep are enough? Sleep score takes the guesswork out of your ZZZ's and shows you how well you slept last night based on duration, efficiency, and consistency.",
1021
1139
  expectedResult: "* - 1st block:\n* - 2nd block:\n* - 3d block:",
1140
+ listStyle: "bullet",
1022
1141
  },
1023
1142
  children: [],
1024
1143
  });
@@ -1029,6 +1148,7 @@ describe("markdownToBlocks", () => {
1029
1148
  stepTitle: "Tap 'Back' button",
1030
1149
  stepData: "",
1031
1150
  expectedResult: "",
1151
+ listStyle: "bullet",
1032
1152
  },
1033
1153
  children: [],
1034
1154
  });
@@ -1124,6 +1244,7 @@ describe("markdownToBlocks", () => {
1124
1244
  stepTitle: "Navigate to the product listing page.",
1125
1245
  stepData: "Expected open",
1126
1246
  expectedResult: "",
1247
+ listStyle: "ordered",
1127
1248
  },
1128
1249
  children: [],
1129
1250
  });
@@ -1135,6 +1256,7 @@ describe("markdownToBlocks", () => {
1135
1256
  stepTitle: "Select a product and click the \"Add to Cart\" button.",
1136
1257
  stepData: "Expected result close",
1137
1258
  expectedResult: "",
1259
+ listStyle: "ordered",
1138
1260
  },
1139
1261
  children: [],
1140
1262
  });
@@ -1146,6 +1268,7 @@ describe("markdownToBlocks", () => {
1146
1268
  stepTitle: "Open the shopping cart page.",
1147
1269
  stepData: "**Expected** edit",
1148
1270
  expectedResult: "",
1271
+ listStyle: "ordered",
1149
1272
  },
1150
1273
  children: [],
1151
1274
  });
@@ -1157,17 +1280,17 @@ describe("markdownToBlocks", () => {
1157
1280
  stepTitle: "Verify that the added item is displayed with the correct name, price, and quantity.",
1158
1281
  stepData: "_Expected_ close",
1159
1282
  expectedResult: "",
1283
+ listStyle: "ordered",
1160
1284
  },
1161
1285
  children: [],
1162
1286
  });
1163
1287
 
1164
- // Test round-trip conversion
1165
- // Note: Test steps are always serialized as bullet lists, not numbered lists
1288
+ // Test round-trip conversion — numbered steps preserve their ordered style
1166
1289
  const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
1167
- expect(roundTripMarkdown).toContain("* Navigate to the product listing page.");
1168
- expect(roundTripMarkdown).toContain("* Select a product and click the \"Add to Cart\" button.");
1169
- expect(roundTripMarkdown).toContain("* Open the shopping cart page.");
1170
- expect(roundTripMarkdown).toContain("* Verify that the added item is displayed with the correct name, price, and quantity.");
1290
+ expect(roundTripMarkdown).toContain("1. Navigate to the product listing page.");
1291
+ expect(roundTripMarkdown).toContain("2. Select a product and click the \"Add to Cart\" button.");
1292
+ expect(roundTripMarkdown).toContain("3. Open the shopping cart page.");
1293
+ expect(roundTripMarkdown).toContain("4. Verify that the added item is displayed with the correct name, price, and quantity.");
1171
1294
  // Check that step data is preserved
1172
1295
  expect(roundTripMarkdown).toContain(" Expected open");
1173
1296
  expect(roundTripMarkdown).toContain(" Expected result close");
@@ -266,6 +266,7 @@ function serializeBlock(
266
266
  block: CustomEditorBlock,
267
267
  ctx: MarkdownContext,
268
268
  orderedIndex?: number,
269
+ stepIndex?: number,
269
270
  ): string[] {
270
271
  const lines: string[] = [];
271
272
  const indent = ctx.listDepth > 0 ? " ".repeat(ctx.listDepth) : "";
@@ -375,7 +376,9 @@ function serializeBlock(
375
376
  .join(" ");
376
377
 
377
378
  if (normalizedTitle.length > 0) {
378
- lines.push(`* ${normalizedTitle}`);
379
+ const listStyle = (block.props as any).listStyle ?? "bullet";
380
+ const prefix = listStyle === "ordered" ? `${(stepIndex ?? 0) + 1}.` : "*";
381
+ lines.push(`${prefix} ${normalizedTitle}`);
379
382
  }
380
383
  }
381
384
 
@@ -533,6 +536,7 @@ function serializeBlock(
533
536
  function serializeBlocks(blocks: CustomEditorBlock[], ctx: MarkdownContext): string[] {
534
537
  const lines: string[] = [];
535
538
  let orderedIndex: number | null = null;
539
+ let stepIndex = 0;
536
540
 
537
541
  for (const block of blocks) {
538
542
  if (block.type === "numberedListItem") {
@@ -547,6 +551,14 @@ function serializeBlocks(blocks: CustomEditorBlock[], ctx: MarkdownContext): str
547
551
  continue;
548
552
  }
549
553
 
554
+ if (block.type === "testStep") {
555
+ lines.push(...serializeBlock(block, ctx, undefined, stepIndex));
556
+ stepIndex += 1;
557
+ orderedIndex = null;
558
+ continue;
559
+ }
560
+
561
+ stepIndex = 0;
550
562
  orderedIndex = null;
551
563
  lines.push(...serializeBlock(block, ctx));
552
564
  }
@@ -767,7 +779,7 @@ function parseList(
767
779
  // Only try to parse as testStep for top-level items (indentLevel === 0)
768
780
  // when we're under a Steps heading AND the list type is bullet
769
781
  // Numbered lists under Steps heading are only parsed as test steps if they look like test steps
770
- if (indentLevel === 0 && allowEmptySteps) {
782
+ if (indentLevel === 0 && (allowEmptySteps || listType === "bullet")) {
771
783
  // For bullet lists, always try to parse as test steps
772
784
  // For numbered lists, only try if they have step-like characteristics
773
785
  const looksLikeTestStep = listType === "bullet" ||
@@ -1036,6 +1048,7 @@ function parseTestStep(
1036
1048
  .trim();
1037
1049
 
1038
1050
  if (
1051
+ !isBullet &&
1039
1052
  !isLikelyStep &&
1040
1053
  !expectedResult &&
1041
1054
  stepDataLines.length === 0 &&
@@ -1060,6 +1073,7 @@ function parseTestStep(
1060
1073
  stepTitle: titleWithPlaceholders,
1061
1074
  stepData: stepDataWithImages,
1062
1075
  expectedResult,
1076
+ listStyle: isNumbered ? "ordered" : "bullet",
1063
1077
  };
1064
1078
 
1065
1079
  const parsedBlock: CustomPartialBlock = {
@@ -21,6 +21,7 @@ describe("markdownToBlocks", () => {
21
21
  stepTitle: "Open the Login page.",
22
22
  stepData: "",
23
23
  expectedResult: "The Login page loads successfully.",
24
+ listStyle: "bullet",
24
25
  },
25
26
  children: [],
26
27
  },
@@ -99,6 +100,7 @@ describe("markdownToBlocks", () => {
99
100
  stepTitle: "Step 1: Send a chat message to the user.",
100
101
  stepData: "",
101
102
  expectedResult: "The user receives a real-time notification for the chat message.",
103
+ listStyle: "bullet",
102
104
  },
103
105
  children: [],
104
106
  });
@@ -249,8 +249,8 @@
249
249
  padding-bottom: 8px;
250
250
  }
251
251
 
252
- .bn-teststep__header + .bn-step-field {
253
- margin-top: calc(8px - var(--step-section-gap));
252
+ .bn-teststep__content > .bn-teststep__header + .bn-step-field {
253
+ margin-top: calc(4px - var(--step-section-gap));
254
254
  }
255
255
 
256
256
  .bn-teststep__header {
@@ -579,7 +579,7 @@
579
579
  .bn-step-field {
580
580
  display: flex;
581
581
  flex-direction: column;
582
- gap: 8px;
582
+ gap: 4px;
583
583
  position: relative;
584
584
  }
585
585
 
@@ -801,6 +801,10 @@
801
801
  font-weight: 400 !important;
802
802
  }
803
803
 
804
+ .bn-step-editor .overtype-wrapper .overtype-placeholder {
805
+ display: none !important;
806
+ }
807
+
804
808
  .bn-step-editor .overtype-wrapper .overtype-preview {
805
809
  color: var(--text-primary) !important;
806
810
  }