testomatio-editor-blocks 0.4.52 → 0.4.54

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.
@@ -446,7 +446,7 @@ function serializeBlock(
446
446
  const normalizedExpected = stripExpectedPrefix(expectedResult).trim();
447
447
  if (normalizedExpected.length > 0) {
448
448
  const expectedLines = normalizedExpected.split(/\r?\n/);
449
- const label = "*Expected*";
449
+ const label = "*Expected result*";
450
450
  expectedLines.forEach((expectedLine: string, index: number) => {
451
451
  const trimmedLine = expectedLine.trim();
452
452
  if (trimmedLine.length === 0) {
@@ -630,7 +630,41 @@ export function blocksToMarkdown(blocks: CustomEditorBlock[]): string {
630
630
  }
631
631
 
632
632
  function parseInlineMarkdown(text: string): EditorInline[] {
633
- const cleaned = stripHtmlWrappers(text);
633
+ return parseInlineSegments(stripHtmlWrappers(text), {});
634
+ }
635
+
636
+ function findItalicClose(
637
+ text: string,
638
+ start: number,
639
+ marker: "*" | "_",
640
+ ): number {
641
+ let j = start;
642
+ while (j < text.length) {
643
+ const ch = text[j];
644
+ if (ch === "\\") {
645
+ j += 2;
646
+ continue;
647
+ }
648
+ if ((ch === "*" || ch === "_") && text[j + 1] === ch) {
649
+ const close = text.indexOf(ch + ch, j + 2);
650
+ if (close === -1) {
651
+ return -1;
652
+ }
653
+ j = close + 2;
654
+ continue;
655
+ }
656
+ if (ch === marker) {
657
+ return j;
658
+ }
659
+ j += 1;
660
+ }
661
+ return -1;
662
+ }
663
+
664
+ function parseInlineSegments(
665
+ cleaned: string,
666
+ outerStyles: Record<string, boolean>,
667
+ ): EditorInline[] {
634
668
  const result: EditorInline[] = [];
635
669
  let buffer = "";
636
670
 
@@ -638,22 +672,53 @@ function parseInlineMarkdown(text: string): EditorInline[] {
638
672
  if (buffer.length === 0) {
639
673
  return;
640
674
  }
641
- result.push({ type: "text", text: unescapeMarkdown(buffer), styles: {} });
675
+ result.push({
676
+ type: "text",
677
+ text: unescapeMarkdown(buffer),
678
+ styles: { ...outerStyles } as EditorStyles,
679
+ });
642
680
  buffer = "";
643
681
  };
644
682
 
683
+ const wrap = (inner: string, add: Record<string, boolean>) => {
684
+ pushPlain();
685
+ const nested = parseInlineSegments(inner, { ...outerStyles, ...add });
686
+ result.push(...nested);
687
+ };
688
+
645
689
  let i = 0;
646
690
  while (i < cleaned.length) {
691
+ if (cleaned.startsWith("***", i)) {
692
+ const end = cleaned.indexOf("***", i + 3);
693
+ if (end !== -1) {
694
+ wrap(cleaned.slice(i + 3, end), { bold: true, italic: true });
695
+ i = end + 3;
696
+ continue;
697
+ }
698
+ }
699
+
700
+ if (cleaned.startsWith("___", i)) {
701
+ const end = cleaned.indexOf("___", i + 3);
702
+ if (end !== -1) {
703
+ wrap(cleaned.slice(i + 3, end), { bold: true, italic: true });
704
+ i = end + 3;
705
+ continue;
706
+ }
707
+ }
708
+
647
709
  if (cleaned.startsWith("**", i)) {
648
710
  const end = cleaned.indexOf("**", i + 2);
649
711
  if (end !== -1) {
650
- pushPlain();
651
- const inner = cleaned.slice(i + 2, end);
652
- result.push({
653
- type: "text",
654
- text: unescapeMarkdown(inner),
655
- styles: { bold: true },
656
- });
712
+ wrap(cleaned.slice(i + 2, end), { bold: true });
713
+ i = end + 2;
714
+ continue;
715
+ }
716
+ }
717
+
718
+ if (cleaned.startsWith("__", i)) {
719
+ const end = cleaned.indexOf("__", i + 2);
720
+ if (end !== -1) {
721
+ wrap(cleaned.slice(i + 2, end), { bold: true });
657
722
  i = end + 2;
658
723
  continue;
659
724
  }
@@ -662,13 +727,7 @@ function parseInlineMarkdown(text: string): EditorInline[] {
662
727
  if (cleaned.startsWith("~~", i)) {
663
728
  const end = cleaned.indexOf("~~", i + 2);
664
729
  if (end !== -1) {
665
- pushPlain();
666
- const inner = cleaned.slice(i + 2, end);
667
- result.push({
668
- type: "text",
669
- text: unescapeMarkdown(inner),
670
- styles: { strike: true },
671
- });
730
+ wrap(cleaned.slice(i + 2, end), { strike: true });
672
731
  i = end + 2;
673
732
  continue;
674
733
  }
@@ -682,7 +741,7 @@ function parseInlineMarkdown(text: string): EditorInline[] {
682
741
  result.push({
683
742
  type: "text",
684
743
  text: unescapeMarkdown(inner),
685
- styles: { code: true },
744
+ styles: { ...outerStyles, code: true } as EditorStyles,
686
745
  });
687
746
  i = end + 1;
688
747
  continue;
@@ -697,7 +756,7 @@ function parseInlineMarkdown(text: string): EditorInline[] {
697
756
  pushPlain();
698
757
  const label = cleaned.slice(i + 1, endLabel);
699
758
  const href = cleaned.slice(startLink + 1, endLink);
700
- const parsedLabel = parseInlineMarkdown(label);
759
+ const parsedLabel = parseInlineSegments(label, {});
701
760
  // Ensure link content is never undefined - if empty, add empty text
702
761
  const linkContent = parsedLabel.length > 0 ? parsedLabel : [{ type: "text", text: "", styles: {} }];
703
762
  result.push({
@@ -711,16 +770,10 @@ function parseInlineMarkdown(text: string): EditorInline[] {
711
770
  }
712
771
 
713
772
  if (cleaned[i] === "*" || cleaned[i] === "_") {
714
- const marker = cleaned[i];
715
- const end = cleaned.indexOf(marker, i + 1);
773
+ const marker = cleaned[i] as "*" | "_";
774
+ const end = findItalicClose(cleaned, i + 1, marker);
716
775
  if (end !== -1) {
717
- pushPlain();
718
- const inner = cleaned.slice(i + 1, end);
719
- result.push({
720
- type: "text",
721
- text: unescapeMarkdown(inner),
722
- styles: { italic: true },
723
- });
776
+ wrap(cleaned.slice(i + 1, end), { italic: true });
724
777
  i = end + 1;
725
778
  continue;
726
779
  }
@@ -792,7 +845,27 @@ function parseList(
792
845
  const trimmed = rawLine.trim();
793
846
 
794
847
  if (!trimmed) {
795
- index += 1;
848
+ // Peek at the next non-blank line. If it's another item of this list
849
+ // (same indent level and list type), treat the blank lines as loose-list
850
+ // separators and consume them. Otherwise leave the blank line for the
851
+ // outer loop so it can become an empty paragraph block.
852
+ let lookahead = index + 1;
853
+ while (lookahead < lines.length && !lines[lookahead].trim()) {
854
+ lookahead += 1;
855
+ }
856
+ if (lookahead >= lines.length) {
857
+ break;
858
+ }
859
+ const nextLine = lines[lookahead];
860
+ const nextIndent = countIndent(nextLine);
861
+ if (nextIndent < indentLevel * 2) {
862
+ break;
863
+ }
864
+ const nextType = detectListType(nextLine.trim());
865
+ if (nextType !== listType) {
866
+ break;
867
+ }
868
+ index = lookahead;
796
869
  continue;
797
870
  }
798
871
 
@@ -130,4 +130,43 @@ describe("markdownToBlocks", () => {
130
130
  children: [],
131
131
  });
132
132
  });
133
+
134
+ it("parses combined bold+italic using nested delimiters", () => {
135
+ const blocks = markdownToBlocks(
136
+ "The _**Username**_ and **_Password_** fields and ***both*** and ___both___.",
137
+ );
138
+
139
+ expect(blocks).toHaveLength(1);
140
+ expect(blocks[0]).toEqual({
141
+ type: "paragraph",
142
+ props: baseProps,
143
+ content: [
144
+ { type: "text", text: "The ", styles: {} },
145
+ { type: "text", text: "Username", styles: { italic: true, bold: true } },
146
+ { type: "text", text: " and ", styles: {} },
147
+ { type: "text", text: "Password", styles: { bold: true, italic: true } },
148
+ { type: "text", text: " fields and ", styles: {} },
149
+ { type: "text", text: "both", styles: { bold: true, italic: true } },
150
+ { type: "text", text: " and ", styles: {} },
151
+ { type: "text", text: "both", styles: { bold: true, italic: true } },
152
+ { type: "text", text: ".", styles: {} },
153
+ ],
154
+ children: [],
155
+ });
156
+ });
157
+
158
+ it("parses bold with nested italic keeping both styles", () => {
159
+ const blocks = markdownToBlocks("**foo _bar_ baz**");
160
+
161
+ expect(blocks[0]).toEqual({
162
+ type: "paragraph",
163
+ props: baseProps,
164
+ content: [
165
+ { type: "text", text: "foo ", styles: { bold: true } },
166
+ { type: "text", text: "bar", styles: { bold: true, italic: true } },
167
+ { type: "text", text: " baz", styles: { bold: true } },
168
+ ],
169
+ children: [],
170
+ });
171
+ });
133
172
  });
@@ -1100,6 +1100,12 @@ html.dark .bn-step-image-preview__content {
1100
1100
  color: rgb(146, 64, 14) !important;
1101
1101
  }
1102
1102
 
1103
+ .bn-step-editor .overtype-wrapper .overtype-preview li.bullet-list .syntax-marker,
1104
+ .bn-step-editor .overtype-wrapper .overtype-preview li.ordered-list .syntax-marker {
1105
+ color: inherit !important;
1106
+ opacity: 1 !important;
1107
+ }
1108
+
1103
1109
  .bn-step-custom-caret {
1104
1110
  display: none;
1105
1111
  position: absolute;