testomatio-editor-blocks 0.4.0 → 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.
- package/package/editor/blocks/snippet.js +32 -138
- package/package/editor/blocks/step.d.ts +6 -0
- package/package/editor/blocks/step.js +119 -52
- package/package/editor/blocks/stepField.d.ts +4 -1
- package/package/editor/blocks/stepField.js +651 -39
- package/package/editor/blocks/stepHorizontalView.d.ts +14 -0
- package/package/editor/blocks/stepHorizontalView.js +7 -0
- package/package/editor/customMarkdownConverter.d.ts +1 -0
- package/package/editor/customMarkdownConverter.js +153 -41
- package/package/editor/customSchema.d.ts +6 -0
- package/package/styles.css +569 -122
- package/package.json +5 -1
- package/src/editor/blocks/snippet.tsx +92 -212
- package/src/editor/blocks/step.tsx +268 -123
- package/src/editor/blocks/stepField.tsx +907 -95
- package/src/editor/blocks/stepHorizontalView.tsx +90 -0
- package/src/editor/customMarkdownConverter.test.ts +594 -29
- package/src/editor/customMarkdownConverter.ts +183 -42
- package/src/editor/markdownToBlocks.test.ts +2 -0
- package/src/editor/styles.css +565 -133
|
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
blocksToMarkdown,
|
|
4
4
|
markdownToBlocks,
|
|
5
|
+
fixMalformedImageBlocks,
|
|
5
6
|
type CustomEditorBlock,
|
|
6
7
|
type CustomPartialBlock,
|
|
7
8
|
} from "./customMarkdownConverter";
|
|
@@ -74,6 +75,7 @@ describe("blocksToMarkdown", () => {
|
|
|
74
75
|
stepTitle: "Open the Login page.",
|
|
75
76
|
stepData: "",
|
|
76
77
|
expectedResult: "The Login page loads successfully.",
|
|
78
|
+
listStyle: "bullet",
|
|
77
79
|
},
|
|
78
80
|
content: undefined,
|
|
79
81
|
children: [],
|
|
@@ -85,6 +87,7 @@ describe("blocksToMarkdown", () => {
|
|
|
85
87
|
stepTitle: "Enter a valid username.",
|
|
86
88
|
stepData: "",
|
|
87
89
|
expectedResult: "The username is accepted.",
|
|
90
|
+
listStyle: "bullet",
|
|
88
91
|
},
|
|
89
92
|
content: undefined,
|
|
90
93
|
children: [],
|
|
@@ -181,6 +184,7 @@ describe("blocksToMarkdown", () => {
|
|
|
181
184
|
stepTitle: "**Click** the _Login_ button",
|
|
182
185
|
stepData: "",
|
|
183
186
|
expectedResult: "**Success** is shown\nSecond line with <u>underline</u>",
|
|
187
|
+
listStyle: "bullet",
|
|
184
188
|
},
|
|
185
189
|
content: undefined,
|
|
186
190
|
children: [],
|
|
@@ -205,6 +209,7 @@ describe("blocksToMarkdown", () => {
|
|
|
205
209
|
stepTitle: "Navigate to login",
|
|
206
210
|
stepData: "Open browser\nGo to login page",
|
|
207
211
|
expectedResult: "Login form visible",
|
|
212
|
+
listStyle: "bullet",
|
|
208
213
|
},
|
|
209
214
|
content: undefined,
|
|
210
215
|
children: [],
|
|
@@ -242,6 +247,7 @@ describe("blocksToMarkdown", () => {
|
|
|
242
247
|
"",
|
|
243
248
|
].join("\n"),
|
|
244
249
|
expectedResult: "The user receives a real-time notification for the order update.",
|
|
250
|
+
listStyle: "bullet",
|
|
245
251
|
},
|
|
246
252
|
content: undefined,
|
|
247
253
|
children: [],
|
|
@@ -351,6 +357,7 @@ describe("markdownToBlocks", () => {
|
|
|
351
357
|
stepTitle: "Open the Login page.",
|
|
352
358
|
stepData: "",
|
|
353
359
|
expectedResult: "The Login page loads successfully.",
|
|
360
|
+
listStyle: "bullet",
|
|
354
361
|
},
|
|
355
362
|
children: [],
|
|
356
363
|
},
|
|
@@ -457,12 +464,43 @@ describe("markdownToBlocks", () => {
|
|
|
457
464
|
const stepBlocks = blocks.filter((block) => block.type === "testStep");
|
|
458
465
|
|
|
459
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
|
+
},
|
|
460
497
|
{
|
|
461
498
|
type: "testStep",
|
|
462
499
|
props: {
|
|
463
500
|
stepTitle: "Step 1: Send a chat message to the user.",
|
|
464
501
|
stepData: "",
|
|
465
502
|
expectedResult: "The user receives a real-time notification for the chat message.",
|
|
503
|
+
listStyle: "bullet",
|
|
466
504
|
},
|
|
467
505
|
children: [],
|
|
468
506
|
},
|
|
@@ -472,6 +510,7 @@ describe("markdownToBlocks", () => {
|
|
|
472
510
|
stepTitle: "Step 2: Update an order status.",
|
|
473
511
|
stepData: "",
|
|
474
512
|
expectedResult: "The user receives a real-time notification for the order update.",
|
|
513
|
+
listStyle: "bullet",
|
|
475
514
|
},
|
|
476
515
|
children: [],
|
|
477
516
|
},
|
|
@@ -481,6 +520,7 @@ describe("markdownToBlocks", () => {
|
|
|
481
520
|
stepTitle: "Step 3: Send a file to the user.",
|
|
482
521
|
stepData: "",
|
|
483
522
|
expectedResult: "The user receives a real-time notification for the file received.",
|
|
523
|
+
listStyle: "bullet",
|
|
484
524
|
},
|
|
485
525
|
children: [],
|
|
486
526
|
},
|
|
@@ -490,6 +530,27 @@ describe("markdownToBlocks", () => {
|
|
|
490
530
|
stepTitle: "Step 4: Verify that the notifications are displayed correctly in the application's notification panel.",
|
|
491
531
|
stepData: "",
|
|
492
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",
|
|
493
554
|
},
|
|
494
555
|
children: [],
|
|
495
556
|
},
|
|
@@ -534,6 +595,7 @@ describe("markdownToBlocks", () => {
|
|
|
534
595
|
stepTitle: "Step 2: Update an order status.",
|
|
535
596
|
stepData: expectedData,
|
|
536
597
|
expectedResult: "The user receives a real-time notification for the order update.",
|
|
598
|
+
listStyle: "bullet",
|
|
537
599
|
},
|
|
538
600
|
children: [],
|
|
539
601
|
},
|
|
@@ -547,6 +609,7 @@ describe("markdownToBlocks", () => {
|
|
|
547
609
|
stepTitle: "Step 2: Update an order status.",
|
|
548
610
|
stepData: expectedData,
|
|
549
611
|
expectedResult: "The user receives a real-time notification for the order update.",
|
|
612
|
+
listStyle: "bullet",
|
|
550
613
|
},
|
|
551
614
|
content: undefined,
|
|
552
615
|
children: [],
|
|
@@ -589,39 +652,33 @@ describe("markdownToBlocks", () => {
|
|
|
589
652
|
children: [],
|
|
590
653
|
},
|
|
591
654
|
{
|
|
592
|
-
type: "
|
|
593
|
-
props:
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
},
|
|
600
|
-
],
|
|
655
|
+
type: "testStep",
|
|
656
|
+
props: {
|
|
657
|
+
stepTitle: "The user is logged into the application.",
|
|
658
|
+
stepData: "",
|
|
659
|
+
expectedResult: "",
|
|
660
|
+
listStyle: "bullet",
|
|
661
|
+
},
|
|
601
662
|
children: [],
|
|
602
663
|
},
|
|
603
664
|
{
|
|
604
|
-
type: "
|
|
605
|
-
props:
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
},
|
|
612
|
-
],
|
|
665
|
+
type: "testStep",
|
|
666
|
+
props: {
|
|
667
|
+
stepTitle: "The user has the necessary permissions to receive notifications.",
|
|
668
|
+
stepData: "",
|
|
669
|
+
expectedResult: "",
|
|
670
|
+
listStyle: "bullet",
|
|
671
|
+
},
|
|
613
672
|
children: [],
|
|
614
673
|
},
|
|
615
674
|
{
|
|
616
|
-
type: "
|
|
617
|
-
props:
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
},
|
|
624
|
-
],
|
|
675
|
+
type: "testStep",
|
|
676
|
+
props: {
|
|
677
|
+
stepTitle: "The application is configured to send real-time notifications.",
|
|
678
|
+
stepData: "",
|
|
679
|
+
expectedResult: "",
|
|
680
|
+
listStyle: "bullet",
|
|
681
|
+
},
|
|
625
682
|
children: [],
|
|
626
683
|
},
|
|
627
684
|
]);
|
|
@@ -633,7 +690,7 @@ describe("markdownToBlocks", () => {
|
|
|
633
690
|
"",
|
|
634
691
|
"* Verify that each individual unit test completes in ≤ 50 ms (target) and never exceeds 200 ms (hard limit).",
|
|
635
692
|
"",
|
|
636
|
-
"###
|
|
693
|
+
"### Instructions",
|
|
637
694
|
"",
|
|
638
695
|
"1. Execute the full unit test suite with a timer wrapper.",
|
|
639
696
|
" * Each individual test case.",
|
|
@@ -663,6 +720,7 @@ describe("markdownToBlocks", () => {
|
|
|
663
720
|
stepTitle: "Open the form.",
|
|
664
721
|
stepData: "",
|
|
665
722
|
expectedResult: "** The form opens.\nFields are empty.",
|
|
723
|
+
listStyle: "bullet",
|
|
666
724
|
},
|
|
667
725
|
children: [],
|
|
668
726
|
},
|
|
@@ -684,6 +742,7 @@ describe("markdownToBlocks", () => {
|
|
|
684
742
|
stepTitle: "Navigate to login",
|
|
685
743
|
stepData: "Open browser\nGo to login page",
|
|
686
744
|
expectedResult: "Login form visible",
|
|
745
|
+
listStyle: "bullet",
|
|
687
746
|
},
|
|
688
747
|
children: [],
|
|
689
748
|
},
|
|
@@ -705,6 +764,7 @@ describe("markdownToBlocks", () => {
|
|
|
705
764
|
stepTitle: "Prepare test fixtures",
|
|
706
765
|
stepData: "Collect user accounts from staging.\nReset passwords for all test accounts.",
|
|
707
766
|
expectedResult: "Test accounts are ready for execution.",
|
|
767
|
+
listStyle: "bullet",
|
|
708
768
|
},
|
|
709
769
|
children: [],
|
|
710
770
|
},
|
|
@@ -724,6 +784,7 @@ describe("markdownToBlocks", () => {
|
|
|
724
784
|
stepTitle: "Display the generated report.",
|
|
725
785
|
stepData: "",
|
|
726
786
|
expectedResult: "",
|
|
787
|
+
listStyle: "bullet",
|
|
727
788
|
},
|
|
728
789
|
children: [],
|
|
729
790
|
},
|
|
@@ -737,6 +798,7 @@ describe("markdownToBlocks", () => {
|
|
|
737
798
|
stepTitle: "Display the generated report.",
|
|
738
799
|
stepData: "",
|
|
739
800
|
expectedResult: "",
|
|
801
|
+
listStyle: "bullet",
|
|
740
802
|
},
|
|
741
803
|
content: undefined,
|
|
742
804
|
children: [],
|
|
@@ -760,6 +822,7 @@ describe("markdownToBlocks", () => {
|
|
|
760
822
|
stepTitle: "Should open login screen",
|
|
761
823
|
stepData: "",
|
|
762
824
|
expectedResult: "Login should look like this\n",
|
|
825
|
+
listStyle: "bullet",
|
|
763
826
|
},
|
|
764
827
|
children: [],
|
|
765
828
|
},
|
|
@@ -773,6 +836,7 @@ describe("markdownToBlocks", () => {
|
|
|
773
836
|
stepTitle: "Should open login screen",
|
|
774
837
|
stepData: "",
|
|
775
838
|
expectedResult: "Login should look like this\n",
|
|
839
|
+
listStyle: "bullet",
|
|
776
840
|
},
|
|
777
841
|
content: undefined,
|
|
778
842
|
children: [],
|
|
@@ -809,6 +873,7 @@ describe("markdownToBlocks", () => {
|
|
|
809
873
|
stepTitle: "Pass onboarding as mobile user",
|
|
810
874
|
stepData: "",
|
|
811
875
|
expectedResult: "",
|
|
876
|
+
listStyle: "bullet",
|
|
812
877
|
},
|
|
813
878
|
children: [],
|
|
814
879
|
},
|
|
@@ -819,6 +884,7 @@ describe("markdownToBlocks", () => {
|
|
|
819
884
|
"Navigate to More tab -≻ My Profile -≻ Log into the app with user from preconditions",
|
|
820
885
|
stepData: "",
|
|
821
886
|
expectedResult: "* Upsell SS screen is displayed",
|
|
887
|
+
listStyle: "bullet",
|
|
822
888
|
},
|
|
823
889
|
children: [],
|
|
824
890
|
},
|
|
@@ -828,12 +894,70 @@ describe("markdownToBlocks", () => {
|
|
|
828
894
|
stepTitle: "Close SS",
|
|
829
895
|
stepData: "",
|
|
830
896
|
expectedResult: "* My Course and More tab are displayed",
|
|
897
|
+
listStyle: "bullet",
|
|
831
898
|
},
|
|
832
899
|
children: [],
|
|
833
900
|
},
|
|
834
901
|
]);
|
|
835
902
|
});
|
|
836
903
|
|
|
904
|
+
it("handles multiple steps with expected results without extra asterisks", () => {
|
|
905
|
+
const markdown = [
|
|
906
|
+
"### Preconditions",
|
|
907
|
+
"",
|
|
908
|
+
"User on the Sign In with Email Screen",
|
|
909
|
+
"",
|
|
910
|
+
"### Steps",
|
|
911
|
+
"",
|
|
912
|
+
"* Existing email + invalid password",
|
|
913
|
+
" *Expected*: 'Oops, wrong email or password' is displayed",
|
|
914
|
+
"* Not existing email + valid password",
|
|
915
|
+
" *Expected*: 'Oops, wrong email or password' is displayed",
|
|
916
|
+
].join("\n");
|
|
917
|
+
|
|
918
|
+
const blocks = markdownToBlocks(markdown);
|
|
919
|
+
const stepBlocks = blocks.filter((block) => block.type === "testStep");
|
|
920
|
+
|
|
921
|
+
expect(stepBlocks).toHaveLength(2);
|
|
922
|
+
|
|
923
|
+
expect(stepBlocks[0]).toEqual({
|
|
924
|
+
type: "testStep",
|
|
925
|
+
props: {
|
|
926
|
+
stepTitle: "Existing email + invalid password",
|
|
927
|
+
stepData: "",
|
|
928
|
+
expectedResult: "'Oops, wrong email or password' is displayed",
|
|
929
|
+
listStyle: "bullet",
|
|
930
|
+
},
|
|
931
|
+
children: [],
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
expect(stepBlocks[1]).toEqual({
|
|
935
|
+
type: "testStep",
|
|
936
|
+
props: {
|
|
937
|
+
stepTitle: "Not existing email + valid password",
|
|
938
|
+
stepData: "",
|
|
939
|
+
expectedResult: "'Oops, wrong email or password' is displayed",
|
|
940
|
+
listStyle: "bullet",
|
|
941
|
+
},
|
|
942
|
+
children: [],
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
// Verify round-trip doesn't add extra asterisks
|
|
946
|
+
const roundTrip = blocksToMarkdown(stepBlocks.map((block, index) => ({
|
|
947
|
+
...block,
|
|
948
|
+
id: `step${index}`,
|
|
949
|
+
})) as CustomEditorBlock[]);
|
|
950
|
+
|
|
951
|
+
expect(roundTrip).toBe(
|
|
952
|
+
[
|
|
953
|
+
"* Existing email + invalid password",
|
|
954
|
+
" *Expected*: 'Oops, wrong email or password' is displayed",
|
|
955
|
+
"* Not existing email + valid password",
|
|
956
|
+
" *Expected*: 'Oops, wrong email or password' is displayed",
|
|
957
|
+
].join("\n"),
|
|
958
|
+
);
|
|
959
|
+
});
|
|
960
|
+
|
|
837
961
|
it("round-trips simple blocks", () => {
|
|
838
962
|
const blocks: CustomEditorBlock[] = [
|
|
839
963
|
{
|
|
@@ -854,7 +978,55 @@ describe("markdownToBlocks", () => {
|
|
|
854
978
|
|
|
855
979
|
const markdown = blocksToMarkdown(blocks);
|
|
856
980
|
const parsed = markdownToBlocks(markdown);
|
|
857
|
-
expect(parsed).toEqual(
|
|
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");
|
|
858
1030
|
});
|
|
859
1031
|
|
|
860
1032
|
it("parses markdown tables", () => {
|
|
@@ -921,6 +1093,7 @@ describe("markdownToBlocks", () => {
|
|
|
921
1093
|
stepTitle: "Step 1: Send a chat message to the user.",
|
|
922
1094
|
stepData: "",
|
|
923
1095
|
expectedResult: "The user receives a real-time notification for the chat message.",
|
|
1096
|
+
listStyle: "bullet",
|
|
924
1097
|
},
|
|
925
1098
|
children: [],
|
|
926
1099
|
},
|
|
@@ -953,6 +1126,7 @@ describe("markdownToBlocks", () => {
|
|
|
953
1126
|
stepTitle: "Swipe Back",
|
|
954
1127
|
stepData: "",
|
|
955
1128
|
expectedResult: "",
|
|
1129
|
+
listStyle: "bullet",
|
|
956
1130
|
},
|
|
957
1131
|
children: [],
|
|
958
1132
|
});
|
|
@@ -963,6 +1137,7 @@ describe("markdownToBlocks", () => {
|
|
|
963
1137
|
stepTitle: "Check UI of Sleep score info screen",
|
|
964
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.",
|
|
965
1139
|
expectedResult: "* - 1st block:\n* - 2nd block:\n* - 3d block:",
|
|
1140
|
+
listStyle: "bullet",
|
|
966
1141
|
},
|
|
967
1142
|
children: [],
|
|
968
1143
|
});
|
|
@@ -973,8 +1148,398 @@ describe("markdownToBlocks", () => {
|
|
|
973
1148
|
stepTitle: "Tap 'Back' button",
|
|
974
1149
|
stepData: "",
|
|
975
1150
|
expectedResult: "",
|
|
1151
|
+
listStyle: "bullet",
|
|
1152
|
+
},
|
|
1153
|
+
children: [],
|
|
1154
|
+
});
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
it("correctly parses images at the end of the document", () => {
|
|
1158
|
+
const markdown = [
|
|
1159
|
+
"#### Steps:",
|
|
1160
|
+
"1. Navigate to the product listing page.",
|
|
1161
|
+
" Expected open",
|
|
1162
|
+
"3. Select a product and click the \"Add to Cart\" button.",
|
|
1163
|
+
" Expected result close",
|
|
1164
|
+
"5. Open the shopping cart page.",
|
|
1165
|
+
" **Expected** edit",
|
|
1166
|
+
"6. Verify that the added item is displayed with the correct name, price, and quantity.",
|
|
1167
|
+
" _Expected_ close",
|
|
1168
|
+
"",
|
|
1169
|
+
"### **Expected Result:** The item appears in the cart with correct details and price calculation.",
|
|
1170
|
+
"",
|
|
1171
|
+
"",
|
|
1172
|
+
"",
|
|
1173
|
+
"",
|
|
1174
|
+
].join("\n");
|
|
1175
|
+
|
|
1176
|
+
const blocks = markdownToBlocks(markdown);
|
|
1177
|
+
|
|
1178
|
+
// Find the paragraph blocks that contain the images (links)
|
|
1179
|
+
const imageBlocks = blocks.filter(block =>
|
|
1180
|
+
block.type === "paragraph" &&
|
|
1181
|
+
block.content &&
|
|
1182
|
+
Array.isArray(block.content) &&
|
|
1183
|
+
block.content.some((item: any) =>
|
|
1184
|
+
(item.type === "text" && item.text === "!") ||
|
|
1185
|
+
(item.type === "link" && item.href && item.href.includes("/attachments/"))
|
|
1186
|
+
)
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
// Should have two paragraph blocks with images
|
|
1190
|
+
expect(imageBlocks.length).toBe(2);
|
|
1191
|
+
|
|
1192
|
+
// Check that both image links are properly parsed
|
|
1193
|
+
const imageLinks: any[] = [];
|
|
1194
|
+
imageBlocks.forEach(block => {
|
|
1195
|
+
if (block.content && Array.isArray(block.content)) {
|
|
1196
|
+
const link = (block.content as any[]).find(item => item.type === "link");
|
|
1197
|
+
if (link) {
|
|
1198
|
+
imageLinks.push(link);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
expect(imageLinks).toHaveLength(2);
|
|
1204
|
+
expect(imageLinks[0].href).toBe("/attachments/se2n8jaGon.png");
|
|
1205
|
+
expect(imageLinks[0].content).toEqual([{ type: "text", text: "logs", styles: {} }]);
|
|
1206
|
+
expect(imageLinks[1].href).toBe("/attachments/p5DgklVeMg.png");
|
|
1207
|
+
expect(imageLinks[1].content).toEqual([{ type: "text", text: "", styles: {} }]);
|
|
1208
|
+
|
|
1209
|
+
// Test round-trip conversion
|
|
1210
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1211
|
+
expect(roundTripMarkdown).toContain("");
|
|
1212
|
+
expect(roundTripMarkdown).toContain("");
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
it("parses numbered lists under a Steps heading as test steps", () => {
|
|
1216
|
+
const markdown = [
|
|
1217
|
+
"#### Steps:",
|
|
1218
|
+
"1. Navigate to the product listing page.",
|
|
1219
|
+
" Expected open",
|
|
1220
|
+
"3. Select a product and click the \"Add to Cart\" button.",
|
|
1221
|
+
" Expected result close",
|
|
1222
|
+
"5. Open the shopping cart page.",
|
|
1223
|
+
" **Expected** edit",
|
|
1224
|
+
"6. Verify that the added item is displayed with the correct name, price, and quantity.",
|
|
1225
|
+
" _Expected_ close",
|
|
1226
|
+
"",
|
|
1227
|
+
"### **Expected Result:** The item appears in the cart with correct details and price calculation.",
|
|
1228
|
+
"",
|
|
1229
|
+
"",
|
|
1230
|
+
"",
|
|
1231
|
+
"",
|
|
1232
|
+
].join("\n");
|
|
1233
|
+
|
|
1234
|
+
const blocks = markdownToBlocks(markdown);
|
|
1235
|
+
const testSteps = blocks.filter(block => block.type === "testStep");
|
|
1236
|
+
|
|
1237
|
+
// Should have 4 test steps
|
|
1238
|
+
expect(testSteps).toHaveLength(4);
|
|
1239
|
+
|
|
1240
|
+
// Check the first test step
|
|
1241
|
+
expect(testSteps[0]).toEqual({
|
|
1242
|
+
type: "testStep",
|
|
1243
|
+
props: {
|
|
1244
|
+
stepTitle: "Navigate to the product listing page.",
|
|
1245
|
+
stepData: "Expected open",
|
|
1246
|
+
expectedResult: "",
|
|
1247
|
+
listStyle: "ordered",
|
|
1248
|
+
},
|
|
1249
|
+
children: [],
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
// Check the second test step
|
|
1253
|
+
expect(testSteps[1]).toEqual({
|
|
1254
|
+
type: "testStep",
|
|
1255
|
+
props: {
|
|
1256
|
+
stepTitle: "Select a product and click the \"Add to Cart\" button.",
|
|
1257
|
+
stepData: "Expected result close",
|
|
1258
|
+
expectedResult: "",
|
|
1259
|
+
listStyle: "ordered",
|
|
976
1260
|
},
|
|
977
1261
|
children: [],
|
|
978
1262
|
});
|
|
1263
|
+
|
|
1264
|
+
// Check the third test step
|
|
1265
|
+
expect(testSteps[2]).toEqual({
|
|
1266
|
+
type: "testStep",
|
|
1267
|
+
props: {
|
|
1268
|
+
stepTitle: "Open the shopping cart page.",
|
|
1269
|
+
stepData: "**Expected** edit",
|
|
1270
|
+
expectedResult: "",
|
|
1271
|
+
listStyle: "ordered",
|
|
1272
|
+
},
|
|
1273
|
+
children: [],
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
// Check the fourth test step
|
|
1277
|
+
expect(testSteps[3]).toEqual({
|
|
1278
|
+
type: "testStep",
|
|
1279
|
+
props: {
|
|
1280
|
+
stepTitle: "Verify that the added item is displayed with the correct name, price, and quantity.",
|
|
1281
|
+
stepData: "_Expected_ close",
|
|
1282
|
+
expectedResult: "",
|
|
1283
|
+
listStyle: "ordered",
|
|
1284
|
+
},
|
|
1285
|
+
children: [],
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
// Test round-trip conversion — numbered steps preserve their ordered style
|
|
1289
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
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.");
|
|
1294
|
+
// Check that step data is preserved
|
|
1295
|
+
expect(roundTripMarkdown).toContain(" Expected open");
|
|
1296
|
+
expect(roundTripMarkdown).toContain(" Expected result close");
|
|
1297
|
+
expect(roundTripMarkdown).toContain(" **Expected** edit");
|
|
1298
|
+
expect(roundTripMarkdown).toContain(" _Expected_ close");
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
it("handles standalone images without bullet list interference", () => {
|
|
1302
|
+
const markdown = [
|
|
1303
|
+
"### Steps:",
|
|
1304
|
+
"1. Navigate to the product listing page.",
|
|
1305
|
+
" Expected open",
|
|
1306
|
+
"",
|
|
1307
|
+
"### **Expected Result:** The item appears in the cart with correct details and price calculation.",
|
|
1308
|
+
"",
|
|
1309
|
+
"",
|
|
1310
|
+
"",
|
|
1311
|
+
"",
|
|
1312
|
+
].join("\n");
|
|
1313
|
+
|
|
1314
|
+
const blocks = markdownToBlocks(markdown);
|
|
1315
|
+
|
|
1316
|
+
// Find image paragraphs
|
|
1317
|
+
const imageParagraphs = blocks.filter(block =>
|
|
1318
|
+
block.type === "paragraph" &&
|
|
1319
|
+
block.content &&
|
|
1320
|
+
Array.isArray(block.content) &&
|
|
1321
|
+
block.content.some((item: any) => item.type === "link")
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
// Should have exactly 2 image paragraphs
|
|
1325
|
+
expect(imageParagraphs).toHaveLength(2);
|
|
1326
|
+
|
|
1327
|
+
// First image with alt text
|
|
1328
|
+
expect(imageParagraphs[0].content).toContainEqual({
|
|
1329
|
+
type: "text",
|
|
1330
|
+
text: "!",
|
|
1331
|
+
styles: {}
|
|
1332
|
+
});
|
|
1333
|
+
expect(imageParagraphs[0].content).toContainEqual({
|
|
1334
|
+
type: "link",
|
|
1335
|
+
href: "/attachments/se2n8jaGon.png",
|
|
1336
|
+
content: [{ type: "text", text: "logs", styles: {} }]
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
// Second image without alt text
|
|
1340
|
+
expect(imageParagraphs[1].content).toContainEqual({
|
|
1341
|
+
type: "text",
|
|
1342
|
+
text: "!",
|
|
1343
|
+
styles: {}
|
|
1344
|
+
});
|
|
1345
|
+
expect(imageParagraphs[1].content).toContainEqual({
|
|
1346
|
+
type: "link",
|
|
1347
|
+
href: "/attachments/p5DgklVeMg.png",
|
|
1348
|
+
content: [{ type: "text", text: "", styles: {} }]
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
// No extra empty paragraphs
|
|
1352
|
+
const emptyParagraphs = blocks.filter(block =>
|
|
1353
|
+
block.type === "paragraph" &&
|
|
1354
|
+
(!block.content || block.content.length === 0)
|
|
1355
|
+
);
|
|
1356
|
+
expect(emptyParagraphs).toHaveLength(0);
|
|
1357
|
+
|
|
1358
|
+
// Test round-trip conversion
|
|
1359
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1360
|
+
expect(roundTripMarkdown).toContain("");
|
|
1361
|
+
expect(roundTripMarkdown).toContain("");
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
it("handles images with multiple blank lines between them", () => {
|
|
1365
|
+
const markdown = `
|
|
1366
|
+
|
|
1367
|
+

|
|
1368
|
+
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+

|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
|
|
1375
|
+
`;
|
|
1376
|
+
|
|
1377
|
+
const blocks = markdownToBlocks(markdown);
|
|
1378
|
+
|
|
1379
|
+
// Should have exactly 2 image paragraphs, no empty paragraphs
|
|
1380
|
+
const imageParagraphs = blocks.filter(block =>
|
|
1381
|
+
block.type === "paragraph" &&
|
|
1382
|
+
block.content &&
|
|
1383
|
+
Array.isArray(block.content) &&
|
|
1384
|
+
block.content.some((item: any) => item.type === "link")
|
|
1385
|
+
);
|
|
1386
|
+
|
|
1387
|
+
const emptyParagraphs = blocks.filter(block =>
|
|
1388
|
+
block.type === "paragraph" &&
|
|
1389
|
+
(!block.content || block.content.length === 0)
|
|
1390
|
+
);
|
|
1391
|
+
|
|
1392
|
+
// Check for malformed image blocks (paragraphs with just "!" but no link)
|
|
1393
|
+
const malformedBlocks = blocks.filter(block =>
|
|
1394
|
+
block.type === "paragraph" &&
|
|
1395
|
+
block.content &&
|
|
1396
|
+
Array.isArray(block.content) &&
|
|
1397
|
+
block.content.some((item: any) => item.type === "text" && item.text === "!") &&
|
|
1398
|
+
!block.content.some((item: any) => item.type === "link")
|
|
1399
|
+
);
|
|
1400
|
+
|
|
1401
|
+
expect(imageParagraphs).toHaveLength(2);
|
|
1402
|
+
expect(emptyParagraphs).toHaveLength(0);
|
|
1403
|
+
expect(malformedBlocks).toHaveLength(0);
|
|
1404
|
+
|
|
1405
|
+
// Test round-trip conversion
|
|
1406
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1407
|
+
expect(roundTripMarkdown).toContain("");
|
|
1408
|
+
expect(roundTripMarkdown).toContain("");
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
it("reproduces the exact issue from user's example", () => {
|
|
1412
|
+
const markdown = `#### Steps:
|
|
1413
|
+
1. Navigate to the product listing page.
|
|
1414
|
+
Expected open
|
|
1415
|
+
3. Select a product and click the "Add to Cart" button.
|
|
1416
|
+
Expected result close
|
|
1417
|
+
5. Open the shopping cart page.
|
|
1418
|
+
**Expected** edit
|
|
1419
|
+
6. Verify that the added item is displayed with the correct name, price, and quantity.
|
|
1420
|
+
_Expected_ close
|
|
1421
|
+
|
|
1422
|
+
### **Expected Result:** The item appears in the cart with correct details and price calculation.
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
|
|
1426
|
+

|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+

|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
|
|
1434
|
+
`;
|
|
1435
|
+
|
|
1436
|
+
const blocks = markdownToBlocks(markdown);
|
|
1437
|
+
|
|
1438
|
+
// Test round-trip conversion
|
|
1439
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1440
|
+
|
|
1441
|
+
// Check that both images are preserved
|
|
1442
|
+
expect(roundTripMarkdown).toContain("");
|
|
1443
|
+
expect(roundTripMarkdown).toContain("");
|
|
1444
|
+
|
|
1445
|
+
// Make sure we don't have a standalone "!" without the rest of the image
|
|
1446
|
+
const lines = roundTripMarkdown.split('\n');
|
|
1447
|
+
const exclamationLines = lines.filter(line => line.trim() === '!' || line.trim() === '! ');
|
|
1448
|
+
expect(exclamationLines.length).toBe(0);
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
it("handles empty alt text images correctly", () => {
|
|
1452
|
+
const markdown = "";
|
|
1453
|
+
const blocks = markdownToBlocks(markdown);
|
|
1454
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1455
|
+
|
|
1456
|
+
expect(roundTripMarkdown).toContain("");
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
it("removes malformed image blocks through post-processing", () => {
|
|
1460
|
+
// Simulate the malformed blocks you're seeing
|
|
1461
|
+
const malformedBlocks: any[] = [
|
|
1462
|
+
{
|
|
1463
|
+
type: "heading",
|
|
1464
|
+
props: { level: 3 },
|
|
1465
|
+
content: [{ type: "text", text: "Expected Result:", styles: {} }],
|
|
1466
|
+
children: []
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
type: "paragraph",
|
|
1470
|
+
props: {},
|
|
1471
|
+
content: [
|
|
1472
|
+
{ type: "text", text: "!", styles: {} },
|
|
1473
|
+
{ type: "link", href: "/attachments/se2n8jaGon.png", content: [{ type: "text", text: "logs", styles: {} }] }
|
|
1474
|
+
],
|
|
1475
|
+
children: []
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
type: "paragraph",
|
|
1479
|
+
props: {},
|
|
1480
|
+
content: [{ type: "text", text: "!", styles: {} }],
|
|
1481
|
+
children: []
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
type: "paragraph",
|
|
1485
|
+
props: {},
|
|
1486
|
+
content: [],
|
|
1487
|
+
children: []
|
|
1488
|
+
}
|
|
1489
|
+
];
|
|
1490
|
+
|
|
1491
|
+
// Apply the fixMalformedImageBlocks function
|
|
1492
|
+
const fixedBlocks = fixMalformedImageBlocks(malformedBlocks);
|
|
1493
|
+
|
|
1494
|
+
// Should have removed the malformed image blocks (both the "!" only block and the empty block)
|
|
1495
|
+
expect(fixedBlocks.length).toBe(2);
|
|
1496
|
+
expect(fixedBlocks[0].type).toBe("heading");
|
|
1497
|
+
expect(fixedBlocks[1].type).toBe("paragraph");
|
|
1498
|
+
expect(fixedBlocks[1].content).toContainEqual(
|
|
1499
|
+
{ type: "text", text: "!", styles: {} }
|
|
1500
|
+
);
|
|
1501
|
+
expect(fixedBlocks[1].content).toContainEqual(
|
|
1502
|
+
{ type: "link", href: "/attachments/se2n8jaGon.png", content: [{ type: "text", text: "logs", styles: {} }] }
|
|
1503
|
+
);
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
it("reproduces the exact Unsplash URL issue", () => {
|
|
1507
|
+
const markdown = `
|
|
1508
|
+
|
|
1509
|
+
|
|
1510
|
+
|
|
1511
|
+
### **Expected Result:** The item appears in the cart with correct details and price calculation.
|
|
1512
|
+
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+

|
|
1516
|
+
|
|
1517
|
+
|
|
1518
|
+
|
|
1519
|
+

|
|
1520
|
+
|
|
1521
|
+
|
|
1522
|
+
|
|
1523
|
+
`;
|
|
1524
|
+
|
|
1525
|
+
const blocks = markdownToBlocks(markdown);
|
|
1526
|
+
|
|
1527
|
+
// Should have at least 3 blocks
|
|
1528
|
+
expect(blocks.length).toBeGreaterThanOrEqual(3);
|
|
1529
|
+
|
|
1530
|
+
// Should have at least one paragraph with content (images)
|
|
1531
|
+
const imageBlocks = blocks.filter(b =>
|
|
1532
|
+
b.type === "paragraph" &&
|
|
1533
|
+
b.content &&
|
|
1534
|
+
Array.isArray(b.content) &&
|
|
1535
|
+
b.content.some((item: any) => item.type === "link")
|
|
1536
|
+
);
|
|
1537
|
+
expect(imageBlocks.length).toBeGreaterThan(0);
|
|
1538
|
+
|
|
1539
|
+
// Test round-trip conversion - check that we get the images back
|
|
1540
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1541
|
+
|
|
1542
|
+
// Most importantly: should not have a standalone "!" at the end
|
|
1543
|
+
expect(roundTripMarkdown).not.toMatch(/\n!\s*$/);
|
|
979
1544
|
});
|
|
980
1545
|
});
|