testomatio-editor-blocks 0.4.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 +32 -138
- package/package/editor/blocks/step.js +116 -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 +136 -36
- package/package/styles.css +565 -122
- package/package.json +5 -1
- package/src/editor/blocks/snippet.tsx +92 -212
- package/src/editor/blocks/step.tsx +265 -123
- package/src/editor/blocks/stepField.tsx +907 -95
- package/src/editor/blocks/stepHorizontalView.tsx +90 -0
- package/src/editor/customMarkdownConverter.test.ts +443 -1
- package/src/editor/customMarkdownConverter.ts +168 -41
- package/src/editor/styles.css +561 -133
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { StepField } from "./stepField";
|
|
3
|
+
import type { StepSuggestion } from "../stepAutocomplete";
|
|
4
|
+
|
|
5
|
+
const STEP_PLACEHOLDER = "Enter step name";
|
|
6
|
+
const EXPECTED_RESULT_PLACEHOLDER = "Enter expected result";
|
|
7
|
+
|
|
8
|
+
type StepHorizontalViewProps = {
|
|
9
|
+
blockId: string;
|
|
10
|
+
stepNumber: number;
|
|
11
|
+
stepValue: string;
|
|
12
|
+
expectedResult: string;
|
|
13
|
+
onStepChange: (next: string) => void;
|
|
14
|
+
onExpectedChange: (next: string) => void;
|
|
15
|
+
onInsertNextStep: () => void;
|
|
16
|
+
onFieldFocus: () => void;
|
|
17
|
+
viewToggle?: ReactNode;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function StepHorizontalView({
|
|
21
|
+
blockId,
|
|
22
|
+
stepNumber,
|
|
23
|
+
stepValue,
|
|
24
|
+
expectedResult,
|
|
25
|
+
onStepChange,
|
|
26
|
+
onExpectedChange,
|
|
27
|
+
onInsertNextStep,
|
|
28
|
+
onFieldFocus,
|
|
29
|
+
viewToggle,
|
|
30
|
+
}: StepHorizontalViewProps) {
|
|
31
|
+
return (
|
|
32
|
+
<div className="bn-teststep bn-teststep--horizontal" data-block-id={blockId}>
|
|
33
|
+
<div className="bn-teststep__timeline">
|
|
34
|
+
<span className="bn-teststep__number">{stepNumber}</span>
|
|
35
|
+
<div className="bn-teststep__line" />
|
|
36
|
+
</div>
|
|
37
|
+
<div className="bn-teststep__content">
|
|
38
|
+
<div className="bn-teststep__horizontal-fields">
|
|
39
|
+
<div className="bn-teststep__horizontal-col">
|
|
40
|
+
<div className="bn-teststep__header">
|
|
41
|
+
<span className="bn-teststep__title">Step</span>
|
|
42
|
+
</div>
|
|
43
|
+
<StepField
|
|
44
|
+
label="Step"
|
|
45
|
+
showLabel={false}
|
|
46
|
+
value={stepValue}
|
|
47
|
+
onChange={onStepChange}
|
|
48
|
+
placeholder={STEP_PLACEHOLDER}
|
|
49
|
+
enableAutocomplete
|
|
50
|
+
fieldName="title"
|
|
51
|
+
suggestionFilter={(suggestion) => (suggestion as StepSuggestion).isSnippet !== true}
|
|
52
|
+
onFieldFocus={onFieldFocus}
|
|
53
|
+
multiline
|
|
54
|
+
enableImageUpload
|
|
55
|
+
showFormattingButtons
|
|
56
|
+
showImageButton
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
<div className="bn-teststep__horizontal-col">
|
|
60
|
+
<div className="bn-teststep__header">
|
|
61
|
+
<span className="bn-teststep__title">Expected result</span>
|
|
62
|
+
{viewToggle}
|
|
63
|
+
</div>
|
|
64
|
+
<StepField
|
|
65
|
+
label="Expected result"
|
|
66
|
+
showLabel={false}
|
|
67
|
+
value={expectedResult}
|
|
68
|
+
onChange={onExpectedChange}
|
|
69
|
+
placeholder={EXPECTED_RESULT_PLACEHOLDER}
|
|
70
|
+
multiline
|
|
71
|
+
enableAutocomplete
|
|
72
|
+
enableImageUpload
|
|
73
|
+
showFormattingButtons
|
|
74
|
+
showImageButton
|
|
75
|
+
onFieldFocus={onFieldFocus}
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<div className="bn-step-actions">
|
|
80
|
+
<button type="button" className="bn-step-action-btn" onClick={onInsertNextStep}>
|
|
81
|
+
<svg className="bn-step-action-btn__icon" width="16" height="16" viewBox="0 0 13.334 13.334" fill="none" aria-hidden="true">
|
|
82
|
+
<path d="M6.667 0a6.667 6.667 0 1 1 0 13.334A6.667 6.667 0 0 1 6.667 0Zm0 1.334a5.333 5.333 0 1 0 0 10.666 5.333 5.333 0 0 0 0-10.666ZM7.334 3.334V6H10v1.334H7.334V10H6V7.334H3.334V6H6V3.334h1.334Z" fill="currentColor"/>
|
|
83
|
+
</svg>
|
|
84
|
+
Add new step
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -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";
|
|
@@ -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.",
|
|
@@ -834,6 +835,61 @@ describe("markdownToBlocks", () => {
|
|
|
834
835
|
]);
|
|
835
836
|
});
|
|
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
|
+
|
|
837
893
|
it("round-trips simple blocks", () => {
|
|
838
894
|
const blocks: CustomEditorBlock[] = [
|
|
839
895
|
{
|
|
@@ -977,4 +1033,390 @@ describe("markdownToBlocks", () => {
|
|
|
977
1033
|
children: [],
|
|
978
1034
|
});
|
|
979
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
|
+
});
|
|
980
1422
|
});
|