testomatio-editor-blocks 0.3.0 → 0.4.1
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 +42 -23
- package/package/editor/blocks/step.js +166 -35
- package/package/editor/blocks/stepField.d.ts +9 -1
- package/package/editor/blocks/stepField.js +664 -34
- package/package/editor/blocks/stepHorizontalView.d.ts +14 -0
- package/package/editor/blocks/stepHorizontalView.js +7 -0
- package/package/editor/blocks/useAutoResize.d.ts +8 -0
- package/package/editor/blocks/useAutoResize.js +31 -0
- package/package/editor/customMarkdownConverter.d.ts +1 -0
- package/package/editor/customMarkdownConverter.js +260 -31
- package/package/styles.css +706 -130
- package/package.json +9 -2
- package/src/App.tsx +1 -1
- package/src/editor/blocks/markdown.ts +27 -7
- package/src/editor/blocks/snippet.tsx +117 -61
- package/src/editor/blocks/step.tsx +325 -87
- package/src/editor/blocks/stepField.tsx +1396 -299
- package/src/editor/blocks/stepHorizontalView.tsx +90 -0
- package/src/editor/blocks/useAutoResize.ts +44 -0
- package/src/editor/customMarkdownConverter.test.ts +542 -3
- package/src/editor/customMarkdownConverter.ts +310 -36
- package/src/editor/customSchema.test.ts +35 -0
- package/src/editor/markdownToBlocks.test.ts +119 -0
- package/src/editor/styles.css +827 -128
|
@@ -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";
|
|
@@ -489,7 +490,7 @@ describe("markdownToBlocks", () => {
|
|
|
489
490
|
props: {
|
|
490
491
|
stepTitle: "Step 4: Verify that the notifications are displayed correctly in the application's notification panel.",
|
|
491
492
|
stepData: "",
|
|
492
|
-
expectedResult: "All notifications (chat message, order update, file received) are listed in the notification panel with the correct information (e.g., timestamp, message content)
|
|
493
|
+
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",
|
|
493
494
|
},
|
|
494
495
|
children: [],
|
|
495
496
|
},
|
|
@@ -633,7 +634,7 @@ describe("markdownToBlocks", () => {
|
|
|
633
634
|
"",
|
|
634
635
|
"* Verify that each individual unit test completes in ≤ 50 ms (target) and never exceeds 200 ms (hard limit).",
|
|
635
636
|
"",
|
|
636
|
-
"###
|
|
637
|
+
"### Instructions",
|
|
637
638
|
"",
|
|
638
639
|
"1. Execute the full unit test suite with a timer wrapper.",
|
|
639
640
|
" * Each individual test case.",
|
|
@@ -662,7 +663,7 @@ describe("markdownToBlocks", () => {
|
|
|
662
663
|
props: {
|
|
663
664
|
stepTitle: "Open the form.",
|
|
664
665
|
stepData: "",
|
|
665
|
-
expectedResult: "
|
|
666
|
+
expectedResult: "** The form opens.\nFields are empty.",
|
|
666
667
|
},
|
|
667
668
|
children: [],
|
|
668
669
|
},
|
|
@@ -788,6 +789,107 @@ describe("markdownToBlocks", () => {
|
|
|
788
789
|
);
|
|
789
790
|
});
|
|
790
791
|
|
|
792
|
+
it("parses steps under a Steps heading even when expected results are missing", () => {
|
|
793
|
+
const markdown = [
|
|
794
|
+
"### Steps",
|
|
795
|
+
"",
|
|
796
|
+
"* Pass onboarding as mobile user",
|
|
797
|
+
"* Navigate to More tab -≻ My Profile -≻ Log into the app with user from preconditions",
|
|
798
|
+
" *Expected:* Upsell SS screen is displayed",
|
|
799
|
+
"* Close SS",
|
|
800
|
+
" *Expected:* My Course and More tab are displayed",
|
|
801
|
+
].join("\n");
|
|
802
|
+
|
|
803
|
+
const blocks = markdownToBlocks(markdown);
|
|
804
|
+
const stepBlocks = blocks.filter((block) => block.type === "testStep");
|
|
805
|
+
|
|
806
|
+
expect(stepBlocks).toEqual([
|
|
807
|
+
{
|
|
808
|
+
type: "testStep",
|
|
809
|
+
props: {
|
|
810
|
+
stepTitle: "Pass onboarding as mobile user",
|
|
811
|
+
stepData: "",
|
|
812
|
+
expectedResult: "",
|
|
813
|
+
},
|
|
814
|
+
children: [],
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
type: "testStep",
|
|
818
|
+
props: {
|
|
819
|
+
stepTitle:
|
|
820
|
+
"Navigate to More tab -≻ My Profile -≻ Log into the app with user from preconditions",
|
|
821
|
+
stepData: "",
|
|
822
|
+
expectedResult: "* Upsell SS screen is displayed",
|
|
823
|
+
},
|
|
824
|
+
children: [],
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
type: "testStep",
|
|
828
|
+
props: {
|
|
829
|
+
stepTitle: "Close SS",
|
|
830
|
+
stepData: "",
|
|
831
|
+
expectedResult: "* My Course and More tab are displayed",
|
|
832
|
+
},
|
|
833
|
+
children: [],
|
|
834
|
+
},
|
|
835
|
+
]);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it("handles multiple steps with expected results without extra asterisks", () => {
|
|
839
|
+
const markdown = [
|
|
840
|
+
"### Preconditions",
|
|
841
|
+
"",
|
|
842
|
+
"User on the Sign In with Email Screen",
|
|
843
|
+
"",
|
|
844
|
+
"### Steps",
|
|
845
|
+
"",
|
|
846
|
+
"* Existing email + invalid password",
|
|
847
|
+
" *Expected*: 'Oops, wrong email or password' is displayed",
|
|
848
|
+
"* Not existing email + valid password",
|
|
849
|
+
" *Expected*: 'Oops, wrong email or password' is displayed",
|
|
850
|
+
].join("\n");
|
|
851
|
+
|
|
852
|
+
const blocks = markdownToBlocks(markdown);
|
|
853
|
+
const stepBlocks = blocks.filter((block) => block.type === "testStep");
|
|
854
|
+
|
|
855
|
+
expect(stepBlocks).toHaveLength(2);
|
|
856
|
+
|
|
857
|
+
expect(stepBlocks[0]).toEqual({
|
|
858
|
+
type: "testStep",
|
|
859
|
+
props: {
|
|
860
|
+
stepTitle: "Existing email + invalid password",
|
|
861
|
+
stepData: "",
|
|
862
|
+
expectedResult: "'Oops, wrong email or password' is displayed",
|
|
863
|
+
},
|
|
864
|
+
children: [],
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
expect(stepBlocks[1]).toEqual({
|
|
868
|
+
type: "testStep",
|
|
869
|
+
props: {
|
|
870
|
+
stepTitle: "Not existing email + valid password",
|
|
871
|
+
stepData: "",
|
|
872
|
+
expectedResult: "'Oops, wrong email or password' is displayed",
|
|
873
|
+
},
|
|
874
|
+
children: [],
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
// Verify round-trip doesn't add extra asterisks
|
|
878
|
+
const roundTrip = blocksToMarkdown(stepBlocks.map((block, index) => ({
|
|
879
|
+
...block,
|
|
880
|
+
id: `step${index}`,
|
|
881
|
+
})) as CustomEditorBlock[]);
|
|
882
|
+
|
|
883
|
+
expect(roundTrip).toBe(
|
|
884
|
+
[
|
|
885
|
+
"* Existing email + invalid password",
|
|
886
|
+
" *Expected*: 'Oops, wrong email or password' is displayed",
|
|
887
|
+
"* Not existing email + valid password",
|
|
888
|
+
" *Expected*: 'Oops, wrong email or password' is displayed",
|
|
889
|
+
].join("\n"),
|
|
890
|
+
);
|
|
891
|
+
});
|
|
892
|
+
|
|
791
893
|
it("round-trips simple blocks", () => {
|
|
792
894
|
const blocks: CustomEditorBlock[] = [
|
|
793
895
|
{
|
|
@@ -880,4 +982,441 @@ describe("markdownToBlocks", () => {
|
|
|
880
982
|
},
|
|
881
983
|
]);
|
|
882
984
|
});
|
|
985
|
+
|
|
986
|
+
it("parses multiple Expected blocks within a single test step", () => {
|
|
987
|
+
const markdown = [
|
|
988
|
+
"### Steps",
|
|
989
|
+
"",
|
|
990
|
+
"* Swipe Back",
|
|
991
|
+
"* Check UI of Sleep score info screen",
|
|
992
|
+
" - Back button",
|
|
993
|
+
" Header: Sleep Score Info",
|
|
994
|
+
" Text: 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.",
|
|
995
|
+
" *Expected:* - 1st block:",
|
|
996
|
+
" *Expected:* - 2nd block:",
|
|
997
|
+
" *Expected:* - 3d block:",
|
|
998
|
+
"* Tap 'Back' button",
|
|
999
|
+
].join("\n");
|
|
1000
|
+
|
|
1001
|
+
const blocks = markdownToBlocks(markdown);
|
|
1002
|
+
const stepBlocks = blocks.filter((block) => block.type === "testStep");
|
|
1003
|
+
|
|
1004
|
+
expect(stepBlocks).toHaveLength(3);
|
|
1005
|
+
|
|
1006
|
+
expect(stepBlocks[0]).toEqual({
|
|
1007
|
+
type: "testStep",
|
|
1008
|
+
props: {
|
|
1009
|
+
stepTitle: "Swipe Back",
|
|
1010
|
+
stepData: "",
|
|
1011
|
+
expectedResult: "",
|
|
1012
|
+
},
|
|
1013
|
+
children: [],
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
expect(stepBlocks[1]).toEqual({
|
|
1017
|
+
type: "testStep",
|
|
1018
|
+
props: {
|
|
1019
|
+
stepTitle: "Check UI of Sleep score info screen",
|
|
1020
|
+
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
|
+
expectedResult: "* - 1st block:\n* - 2nd block:\n* - 3d block:",
|
|
1022
|
+
},
|
|
1023
|
+
children: [],
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
expect(stepBlocks[2]).toEqual({
|
|
1027
|
+
type: "testStep",
|
|
1028
|
+
props: {
|
|
1029
|
+
stepTitle: "Tap 'Back' button",
|
|
1030
|
+
stepData: "",
|
|
1031
|
+
expectedResult: "",
|
|
1032
|
+
},
|
|
1033
|
+
children: [],
|
|
1034
|
+
});
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
it("correctly parses images at the end of the document", () => {
|
|
1038
|
+
const markdown = [
|
|
1039
|
+
"#### Steps:",
|
|
1040
|
+
"1. Navigate to the product listing page.",
|
|
1041
|
+
" Expected open",
|
|
1042
|
+
"3. Select a product and click the \"Add to Cart\" button.",
|
|
1043
|
+
" Expected result close",
|
|
1044
|
+
"5. Open the shopping cart page.",
|
|
1045
|
+
" **Expected** edit",
|
|
1046
|
+
"6. Verify that the added item is displayed with the correct name, price, and quantity.",
|
|
1047
|
+
" _Expected_ close",
|
|
1048
|
+
"",
|
|
1049
|
+
"### **Expected Result:** The item appears in the cart with correct details and price calculation.",
|
|
1050
|
+
"",
|
|
1051
|
+
"",
|
|
1052
|
+
"",
|
|
1053
|
+
"",
|
|
1054
|
+
].join("\n");
|
|
1055
|
+
|
|
1056
|
+
const blocks = markdownToBlocks(markdown);
|
|
1057
|
+
|
|
1058
|
+
// Find the paragraph blocks that contain the images (links)
|
|
1059
|
+
const imageBlocks = blocks.filter(block =>
|
|
1060
|
+
block.type === "paragraph" &&
|
|
1061
|
+
block.content &&
|
|
1062
|
+
Array.isArray(block.content) &&
|
|
1063
|
+
block.content.some((item: any) =>
|
|
1064
|
+
(item.type === "text" && item.text === "!") ||
|
|
1065
|
+
(item.type === "link" && item.href && item.href.includes("/attachments/"))
|
|
1066
|
+
)
|
|
1067
|
+
);
|
|
1068
|
+
|
|
1069
|
+
// Should have two paragraph blocks with images
|
|
1070
|
+
expect(imageBlocks.length).toBe(2);
|
|
1071
|
+
|
|
1072
|
+
// Check that both image links are properly parsed
|
|
1073
|
+
const imageLinks: any[] = [];
|
|
1074
|
+
imageBlocks.forEach(block => {
|
|
1075
|
+
if (block.content && Array.isArray(block.content)) {
|
|
1076
|
+
const link = (block.content as any[]).find(item => item.type === "link");
|
|
1077
|
+
if (link) {
|
|
1078
|
+
imageLinks.push(link);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
expect(imageLinks).toHaveLength(2);
|
|
1084
|
+
expect(imageLinks[0].href).toBe("/attachments/se2n8jaGon.png");
|
|
1085
|
+
expect(imageLinks[0].content).toEqual([{ type: "text", text: "logs", styles: {} }]);
|
|
1086
|
+
expect(imageLinks[1].href).toBe("/attachments/p5DgklVeMg.png");
|
|
1087
|
+
expect(imageLinks[1].content).toEqual([{ type: "text", text: "", styles: {} }]);
|
|
1088
|
+
|
|
1089
|
+
// Test round-trip conversion
|
|
1090
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1091
|
+
expect(roundTripMarkdown).toContain("");
|
|
1092
|
+
expect(roundTripMarkdown).toContain("");
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
it("parses numbered lists under a Steps heading as test steps", () => {
|
|
1096
|
+
const markdown = [
|
|
1097
|
+
"#### Steps:",
|
|
1098
|
+
"1. Navigate to the product listing page.",
|
|
1099
|
+
" Expected open",
|
|
1100
|
+
"3. Select a product and click the \"Add to Cart\" button.",
|
|
1101
|
+
" Expected result close",
|
|
1102
|
+
"5. Open the shopping cart page.",
|
|
1103
|
+
" **Expected** edit",
|
|
1104
|
+
"6. Verify that the added item is displayed with the correct name, price, and quantity.",
|
|
1105
|
+
" _Expected_ close",
|
|
1106
|
+
"",
|
|
1107
|
+
"### **Expected Result:** The item appears in the cart with correct details and price calculation.",
|
|
1108
|
+
"",
|
|
1109
|
+
"",
|
|
1110
|
+
"",
|
|
1111
|
+
"",
|
|
1112
|
+
].join("\n");
|
|
1113
|
+
|
|
1114
|
+
const blocks = markdownToBlocks(markdown);
|
|
1115
|
+
const testSteps = blocks.filter(block => block.type === "testStep");
|
|
1116
|
+
|
|
1117
|
+
// Should have 4 test steps
|
|
1118
|
+
expect(testSteps).toHaveLength(4);
|
|
1119
|
+
|
|
1120
|
+
// Check the first test step
|
|
1121
|
+
expect(testSteps[0]).toEqual({
|
|
1122
|
+
type: "testStep",
|
|
1123
|
+
props: {
|
|
1124
|
+
stepTitle: "Navigate to the product listing page.",
|
|
1125
|
+
stepData: "Expected open",
|
|
1126
|
+
expectedResult: "",
|
|
1127
|
+
},
|
|
1128
|
+
children: [],
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
// Check the second test step
|
|
1132
|
+
expect(testSteps[1]).toEqual({
|
|
1133
|
+
type: "testStep",
|
|
1134
|
+
props: {
|
|
1135
|
+
stepTitle: "Select a product and click the \"Add to Cart\" button.",
|
|
1136
|
+
stepData: "Expected result close",
|
|
1137
|
+
expectedResult: "",
|
|
1138
|
+
},
|
|
1139
|
+
children: [],
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
// Check the third test step
|
|
1143
|
+
expect(testSteps[2]).toEqual({
|
|
1144
|
+
type: "testStep",
|
|
1145
|
+
props: {
|
|
1146
|
+
stepTitle: "Open the shopping cart page.",
|
|
1147
|
+
stepData: "**Expected** edit",
|
|
1148
|
+
expectedResult: "",
|
|
1149
|
+
},
|
|
1150
|
+
children: [],
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
// Check the fourth test step
|
|
1154
|
+
expect(testSteps[3]).toEqual({
|
|
1155
|
+
type: "testStep",
|
|
1156
|
+
props: {
|
|
1157
|
+
stepTitle: "Verify that the added item is displayed with the correct name, price, and quantity.",
|
|
1158
|
+
stepData: "_Expected_ close",
|
|
1159
|
+
expectedResult: "",
|
|
1160
|
+
},
|
|
1161
|
+
children: [],
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
// Test round-trip conversion
|
|
1165
|
+
// Note: Test steps are always serialized as bullet lists, not numbered lists
|
|
1166
|
+
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.");
|
|
1171
|
+
// Check that step data is preserved
|
|
1172
|
+
expect(roundTripMarkdown).toContain(" Expected open");
|
|
1173
|
+
expect(roundTripMarkdown).toContain(" Expected result close");
|
|
1174
|
+
expect(roundTripMarkdown).toContain(" **Expected** edit");
|
|
1175
|
+
expect(roundTripMarkdown).toContain(" _Expected_ close");
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
it("handles standalone images without bullet list interference", () => {
|
|
1179
|
+
const markdown = [
|
|
1180
|
+
"### Steps:",
|
|
1181
|
+
"1. Navigate to the product listing page.",
|
|
1182
|
+
" Expected open",
|
|
1183
|
+
"",
|
|
1184
|
+
"### **Expected Result:** The item appears in the cart with correct details and price calculation.",
|
|
1185
|
+
"",
|
|
1186
|
+
"",
|
|
1187
|
+
"",
|
|
1188
|
+
"",
|
|
1189
|
+
].join("\n");
|
|
1190
|
+
|
|
1191
|
+
const blocks = markdownToBlocks(markdown);
|
|
1192
|
+
|
|
1193
|
+
// Find image paragraphs
|
|
1194
|
+
const imageParagraphs = blocks.filter(block =>
|
|
1195
|
+
block.type === "paragraph" &&
|
|
1196
|
+
block.content &&
|
|
1197
|
+
Array.isArray(block.content) &&
|
|
1198
|
+
block.content.some((item: any) => item.type === "link")
|
|
1199
|
+
);
|
|
1200
|
+
|
|
1201
|
+
// Should have exactly 2 image paragraphs
|
|
1202
|
+
expect(imageParagraphs).toHaveLength(2);
|
|
1203
|
+
|
|
1204
|
+
// First image with alt text
|
|
1205
|
+
expect(imageParagraphs[0].content).toContainEqual({
|
|
1206
|
+
type: "text",
|
|
1207
|
+
text: "!",
|
|
1208
|
+
styles: {}
|
|
1209
|
+
});
|
|
1210
|
+
expect(imageParagraphs[0].content).toContainEqual({
|
|
1211
|
+
type: "link",
|
|
1212
|
+
href: "/attachments/se2n8jaGon.png",
|
|
1213
|
+
content: [{ type: "text", text: "logs", styles: {} }]
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
// Second image without alt text
|
|
1217
|
+
expect(imageParagraphs[1].content).toContainEqual({
|
|
1218
|
+
type: "text",
|
|
1219
|
+
text: "!",
|
|
1220
|
+
styles: {}
|
|
1221
|
+
});
|
|
1222
|
+
expect(imageParagraphs[1].content).toContainEqual({
|
|
1223
|
+
type: "link",
|
|
1224
|
+
href: "/attachments/p5DgklVeMg.png",
|
|
1225
|
+
content: [{ type: "text", text: "", styles: {} }]
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
// No extra empty paragraphs
|
|
1229
|
+
const emptyParagraphs = blocks.filter(block =>
|
|
1230
|
+
block.type === "paragraph" &&
|
|
1231
|
+
(!block.content || block.content.length === 0)
|
|
1232
|
+
);
|
|
1233
|
+
expect(emptyParagraphs).toHaveLength(0);
|
|
1234
|
+
|
|
1235
|
+
// Test round-trip conversion
|
|
1236
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1237
|
+
expect(roundTripMarkdown).toContain("");
|
|
1238
|
+
expect(roundTripMarkdown).toContain("");
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
it("handles images with multiple blank lines between them", () => {
|
|
1242
|
+
const markdown = `
|
|
1243
|
+
|
|
1244
|
+

|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+

|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
`;
|
|
1253
|
+
|
|
1254
|
+
const blocks = markdownToBlocks(markdown);
|
|
1255
|
+
|
|
1256
|
+
// Should have exactly 2 image paragraphs, no empty paragraphs
|
|
1257
|
+
const imageParagraphs = blocks.filter(block =>
|
|
1258
|
+
block.type === "paragraph" &&
|
|
1259
|
+
block.content &&
|
|
1260
|
+
Array.isArray(block.content) &&
|
|
1261
|
+
block.content.some((item: any) => item.type === "link")
|
|
1262
|
+
);
|
|
1263
|
+
|
|
1264
|
+
const emptyParagraphs = blocks.filter(block =>
|
|
1265
|
+
block.type === "paragraph" &&
|
|
1266
|
+
(!block.content || block.content.length === 0)
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
// Check for malformed image blocks (paragraphs with just "!" but no link)
|
|
1270
|
+
const malformedBlocks = blocks.filter(block =>
|
|
1271
|
+
block.type === "paragraph" &&
|
|
1272
|
+
block.content &&
|
|
1273
|
+
Array.isArray(block.content) &&
|
|
1274
|
+
block.content.some((item: any) => item.type === "text" && item.text === "!") &&
|
|
1275
|
+
!block.content.some((item: any) => item.type === "link")
|
|
1276
|
+
);
|
|
1277
|
+
|
|
1278
|
+
expect(imageParagraphs).toHaveLength(2);
|
|
1279
|
+
expect(emptyParagraphs).toHaveLength(0);
|
|
1280
|
+
expect(malformedBlocks).toHaveLength(0);
|
|
1281
|
+
|
|
1282
|
+
// Test round-trip conversion
|
|
1283
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1284
|
+
expect(roundTripMarkdown).toContain("");
|
|
1285
|
+
expect(roundTripMarkdown).toContain("");
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
it("reproduces the exact issue from user's example", () => {
|
|
1289
|
+
const markdown = `#### Steps:
|
|
1290
|
+
1. Navigate to the product listing page.
|
|
1291
|
+
Expected open
|
|
1292
|
+
3. Select a product and click the "Add to Cart" button.
|
|
1293
|
+
Expected result close
|
|
1294
|
+
5. Open the shopping cart page.
|
|
1295
|
+
**Expected** edit
|
|
1296
|
+
6. Verify that the added item is displayed with the correct name, price, and quantity.
|
|
1297
|
+
_Expected_ close
|
|
1298
|
+
|
|
1299
|
+
### **Expected Result:** The item appears in the cart with correct details and price calculation.
|
|
1300
|
+
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+

|
|
1304
|
+
|
|
1305
|
+
|
|
1306
|
+
|
|
1307
|
+

|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
`;
|
|
1312
|
+
|
|
1313
|
+
const blocks = markdownToBlocks(markdown);
|
|
1314
|
+
|
|
1315
|
+
// Test round-trip conversion
|
|
1316
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1317
|
+
|
|
1318
|
+
// Check that both images are preserved
|
|
1319
|
+
expect(roundTripMarkdown).toContain("");
|
|
1320
|
+
expect(roundTripMarkdown).toContain("");
|
|
1321
|
+
|
|
1322
|
+
// Make sure we don't have a standalone "!" without the rest of the image
|
|
1323
|
+
const lines = roundTripMarkdown.split('\n');
|
|
1324
|
+
const exclamationLines = lines.filter(line => line.trim() === '!' || line.trim() === '! ');
|
|
1325
|
+
expect(exclamationLines.length).toBe(0);
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
it("handles empty alt text images correctly", () => {
|
|
1329
|
+
const markdown = "";
|
|
1330
|
+
const blocks = markdownToBlocks(markdown);
|
|
1331
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1332
|
+
|
|
1333
|
+
expect(roundTripMarkdown).toContain("");
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
it("removes malformed image blocks through post-processing", () => {
|
|
1337
|
+
// Simulate the malformed blocks you're seeing
|
|
1338
|
+
const malformedBlocks: any[] = [
|
|
1339
|
+
{
|
|
1340
|
+
type: "heading",
|
|
1341
|
+
props: { level: 3 },
|
|
1342
|
+
content: [{ type: "text", text: "Expected Result:", styles: {} }],
|
|
1343
|
+
children: []
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
type: "paragraph",
|
|
1347
|
+
props: {},
|
|
1348
|
+
content: [
|
|
1349
|
+
{ type: "text", text: "!", styles: {} },
|
|
1350
|
+
{ type: "link", href: "/attachments/se2n8jaGon.png", content: [{ type: "text", text: "logs", styles: {} }] }
|
|
1351
|
+
],
|
|
1352
|
+
children: []
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
type: "paragraph",
|
|
1356
|
+
props: {},
|
|
1357
|
+
content: [{ type: "text", text: "!", styles: {} }],
|
|
1358
|
+
children: []
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
type: "paragraph",
|
|
1362
|
+
props: {},
|
|
1363
|
+
content: [],
|
|
1364
|
+
children: []
|
|
1365
|
+
}
|
|
1366
|
+
];
|
|
1367
|
+
|
|
1368
|
+
// Apply the fixMalformedImageBlocks function
|
|
1369
|
+
const fixedBlocks = fixMalformedImageBlocks(malformedBlocks);
|
|
1370
|
+
|
|
1371
|
+
// Should have removed the malformed image blocks (both the "!" only block and the empty block)
|
|
1372
|
+
expect(fixedBlocks.length).toBe(2);
|
|
1373
|
+
expect(fixedBlocks[0].type).toBe("heading");
|
|
1374
|
+
expect(fixedBlocks[1].type).toBe("paragraph");
|
|
1375
|
+
expect(fixedBlocks[1].content).toContainEqual(
|
|
1376
|
+
{ type: "text", text: "!", styles: {} }
|
|
1377
|
+
);
|
|
1378
|
+
expect(fixedBlocks[1].content).toContainEqual(
|
|
1379
|
+
{ type: "link", href: "/attachments/se2n8jaGon.png", content: [{ type: "text", text: "logs", styles: {} }] }
|
|
1380
|
+
);
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
it("reproduces the exact Unsplash URL issue", () => {
|
|
1384
|
+
const markdown = `
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
### **Expected Result:** The item appears in the cart with correct details and price calculation.
|
|
1389
|
+
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+

|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+

|
|
1397
|
+
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
`;
|
|
1401
|
+
|
|
1402
|
+
const blocks = markdownToBlocks(markdown);
|
|
1403
|
+
|
|
1404
|
+
// Should have at least 3 blocks
|
|
1405
|
+
expect(blocks.length).toBeGreaterThanOrEqual(3);
|
|
1406
|
+
|
|
1407
|
+
// Should have at least one paragraph with content (images)
|
|
1408
|
+
const imageBlocks = blocks.filter(b =>
|
|
1409
|
+
b.type === "paragraph" &&
|
|
1410
|
+
b.content &&
|
|
1411
|
+
Array.isArray(b.content) &&
|
|
1412
|
+
b.content.some((item: any) => item.type === "link")
|
|
1413
|
+
);
|
|
1414
|
+
expect(imageBlocks.length).toBeGreaterThan(0);
|
|
1415
|
+
|
|
1416
|
+
// Test round-trip conversion - check that we get the images back
|
|
1417
|
+
const roundTripMarkdown = blocksToMarkdown(blocks as CustomEditorBlock[]);
|
|
1418
|
+
|
|
1419
|
+
// Most importantly: should not have a standalone "!" at the end
|
|
1420
|
+
expect(roundTripMarkdown).not.toMatch(/\n!\s*$/);
|
|
1421
|
+
});
|
|
883
1422
|
});
|