testomatio-editor-blocks 0.1.0
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/README.md +121 -0
- package/package/editor/customMarkdownConverter.d.ts +8 -0
- package/package/editor/customMarkdownConverter.js +1038 -0
- package/package/editor/customSchema.d.ts +636 -0
- package/package/editor/customSchema.js +379 -0
- package/package/index.d.ts +3 -0
- package/package/index.js +3 -0
- package/package/styles.css +238 -0
- package/package.json +64 -0
- package/src/App.css +162 -0
- package/src/App.tsx +351 -0
- package/src/assets/react.svg +1 -0
- package/src/editor/customMarkdownConverter.test.ts +749 -0
- package/src/editor/customMarkdownConverter.ts +1237 -0
- package/src/editor/customSchema.test.ts +39 -0
- package/src/editor/customSchema.tsx +594 -0
- package/src/editor/styles.css +238 -0
- package/src/index.css +33 -0
- package/src/index.ts +15 -0
- package/src/main.tsx +12 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
blocksToMarkdown,
|
|
4
|
+
markdownToBlocks,
|
|
5
|
+
type CustomEditorBlock,
|
|
6
|
+
type CustomPartialBlock,
|
|
7
|
+
} from "./customMarkdownConverter";
|
|
8
|
+
|
|
9
|
+
const baseProps = {
|
|
10
|
+
textAlignment: "left" as const,
|
|
11
|
+
textColor: "default" as const,
|
|
12
|
+
backgroundColor: "default" as const,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const cellProps = {
|
|
16
|
+
backgroundColor: "default" as const,
|
|
17
|
+
textColor: "default" as const,
|
|
18
|
+
textAlignment: "left" as const,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const toPartial = (block: CustomEditorBlock): CustomPartialBlock => ({
|
|
22
|
+
type: block.type,
|
|
23
|
+
props: block.props as any,
|
|
24
|
+
content: block.content as any,
|
|
25
|
+
children: block.children?.map(toPartial),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("blocksToMarkdown", () => {
|
|
29
|
+
it("converts styled paragraphs", () => {
|
|
30
|
+
const blocks: CustomEditorBlock[] = [
|
|
31
|
+
{
|
|
32
|
+
id: "p1",
|
|
33
|
+
type: "paragraph",
|
|
34
|
+
props: baseProps,
|
|
35
|
+
content: [
|
|
36
|
+
{ type: "text", text: "Hello ", styles: {} },
|
|
37
|
+
{ type: "text", text: "world", styles: { bold: true } },
|
|
38
|
+
{ type: "text", text: "!", styles: { italic: true } },
|
|
39
|
+
],
|
|
40
|
+
children: [],
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
expect(blocksToMarkdown(blocks)).toBe("Hello **world***!*");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("serializes a numbered list", () => {
|
|
48
|
+
const blocks: CustomEditorBlock[] = [
|
|
49
|
+
{
|
|
50
|
+
id: "n1",
|
|
51
|
+
type: "numberedListItem",
|
|
52
|
+
props: { ...baseProps, start: 1 },
|
|
53
|
+
content: [{ type: "text", text: "First", styles: {} }],
|
|
54
|
+
children: [],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "n2",
|
|
58
|
+
type: "numberedListItem",
|
|
59
|
+
props: { ...baseProps, start: 2 },
|
|
60
|
+
content: [{ type: "text", text: "Second", styles: {} }],
|
|
61
|
+
children: [],
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
expect(blocksToMarkdown(blocks)).toBe("1. First\n2. Second");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("serializes a custom test step block", () => {
|
|
69
|
+
const blocks: CustomEditorBlock[] = [
|
|
70
|
+
{
|
|
71
|
+
id: "s1",
|
|
72
|
+
type: "testStep",
|
|
73
|
+
props: {
|
|
74
|
+
stepTitle: "Open the Login page.",
|
|
75
|
+
stepData: "",
|
|
76
|
+
expectedResult: "The Login page loads successfully.",
|
|
77
|
+
},
|
|
78
|
+
content: undefined,
|
|
79
|
+
children: [],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: "s2",
|
|
83
|
+
type: "testStep",
|
|
84
|
+
props: {
|
|
85
|
+
stepTitle: "Enter a valid username.",
|
|
86
|
+
stepData: "",
|
|
87
|
+
expectedResult: "The username is accepted.",
|
|
88
|
+
},
|
|
89
|
+
content: undefined,
|
|
90
|
+
children: [],
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
expect(blocksToMarkdown(blocks)).toBe(
|
|
95
|
+
[
|
|
96
|
+
"* Open the Login page.",
|
|
97
|
+
" *Expected Result*: The Login page loads successfully.",
|
|
98
|
+
"* Enter a valid username.",
|
|
99
|
+
" *Expected Result*: The username is accepted.",
|
|
100
|
+
].join("\n"),
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("keeps inline formatting inside step fields", () => {
|
|
105
|
+
const blocks: CustomEditorBlock[] = [
|
|
106
|
+
{
|
|
107
|
+
id: "s3",
|
|
108
|
+
type: "testStep",
|
|
109
|
+
props: {
|
|
110
|
+
stepTitle: "**Click** the _Login_ button",
|
|
111
|
+
stepData: "",
|
|
112
|
+
expectedResult: "**Success** is shown\nSecond line with <u>underline</u>",
|
|
113
|
+
},
|
|
114
|
+
content: undefined,
|
|
115
|
+
children: [],
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
expect(blocksToMarkdown(blocks)).toBe(
|
|
120
|
+
[
|
|
121
|
+
"* **Click** the _Login_ button",
|
|
122
|
+
" *Expected Result*: **Success** is shown",
|
|
123
|
+
" Second line with <u>underline</u>",
|
|
124
|
+
].join("\n"),
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("serializes test step with data", () => {
|
|
129
|
+
const blocks: CustomEditorBlock[] = [
|
|
130
|
+
{
|
|
131
|
+
id: "s4",
|
|
132
|
+
type: "testStep",
|
|
133
|
+
props: {
|
|
134
|
+
stepTitle: "Navigate to login",
|
|
135
|
+
stepData: "Open browser\nGo to login page",
|
|
136
|
+
expectedResult: "Login form visible",
|
|
137
|
+
},
|
|
138
|
+
content: undefined,
|
|
139
|
+
children: [],
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
expect(blocksToMarkdown(blocks)).toBe(
|
|
144
|
+
[
|
|
145
|
+
"* Navigate to login",
|
|
146
|
+
" Open browser",
|
|
147
|
+
" Go to login page",
|
|
148
|
+
" *Expected Result*: Login form visible",
|
|
149
|
+
].join("\n"),
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("serializes step data containing code fences, blank lines, and images", () => {
|
|
154
|
+
const blocks: CustomEditorBlock[] = [
|
|
155
|
+
{
|
|
156
|
+
id: "s5",
|
|
157
|
+
type: "testStep",
|
|
158
|
+
props: {
|
|
159
|
+
stepTitle: "Update an order status.",
|
|
160
|
+
stepData: [
|
|
161
|
+
"```",
|
|
162
|
+
"SQL CREATE bnbmnbm mnbmb mm",
|
|
163
|
+
"mn,nm nm, m,nm,n,nn,m,",
|
|
164
|
+
",n,n,mnm,n asdsad",
|
|
165
|
+
"asdsadsa",
|
|
166
|
+
"",
|
|
167
|
+
"asdsadsadsadsad",
|
|
168
|
+
"",
|
|
169
|
+
"asdsadas",
|
|
170
|
+
"```",
|
|
171
|
+
"",
|
|
172
|
+
].join("\n"),
|
|
173
|
+
expectedResult: "The user receives a real-time notification for the order update.",
|
|
174
|
+
},
|
|
175
|
+
content: undefined,
|
|
176
|
+
children: [],
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
expect(blocksToMarkdown(blocks)).toBe(
|
|
181
|
+
[
|
|
182
|
+
"* Update an order status.",
|
|
183
|
+
" ```",
|
|
184
|
+
" SQL CREATE bnbmnbm mnbmb mm",
|
|
185
|
+
" mn,nm nm, m,nm,n,nn,m,",
|
|
186
|
+
" ,n,n,mnm,n asdsad",
|
|
187
|
+
" asdsadsa",
|
|
188
|
+
" ",
|
|
189
|
+
" asdsadsadsadsad",
|
|
190
|
+
" ",
|
|
191
|
+
" asdsadas",
|
|
192
|
+
" ```",
|
|
193
|
+
" ",
|
|
194
|
+
" *Expected Result*: The user receives a real-time notification for the order update.",
|
|
195
|
+
].join("\n"),
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("exports the custom test case block", () => {
|
|
200
|
+
const blocks: CustomEditorBlock[] = [
|
|
201
|
+
{
|
|
202
|
+
id: "tc1",
|
|
203
|
+
type: "testCase",
|
|
204
|
+
props: {
|
|
205
|
+
...baseProps,
|
|
206
|
+
status: "ready",
|
|
207
|
+
reference: "QA-7",
|
|
208
|
+
},
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: "text",
|
|
212
|
+
text: "Run the smoke tests.",
|
|
213
|
+
styles: {},
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
children: [],
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
expect(blocksToMarkdown(blocks)).toBe(
|
|
221
|
+
":::test-case status=\"ready\" reference=\"QA-7\"\nRun the smoke tests.\n:::",
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("serializes tables", () => {
|
|
226
|
+
const blocks: CustomEditorBlock[] = [
|
|
227
|
+
{
|
|
228
|
+
id: "tbl1",
|
|
229
|
+
type: "table",
|
|
230
|
+
props: { textColor: "default" },
|
|
231
|
+
content: {
|
|
232
|
+
type: "tableContent",
|
|
233
|
+
columnWidths: [undefined, undefined],
|
|
234
|
+
headerRows: 1,
|
|
235
|
+
rows: [
|
|
236
|
+
{
|
|
237
|
+
cells: [
|
|
238
|
+
{
|
|
239
|
+
type: "tableCell",
|
|
240
|
+
props: cellProps,
|
|
241
|
+
content: [{ type: "text", text: "Step", styles: {} }],
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
type: "tableCell",
|
|
245
|
+
props: cellProps,
|
|
246
|
+
content: [{ type: "text", text: "Expected", styles: {} }],
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
cells: [
|
|
252
|
+
{
|
|
253
|
+
type: "tableCell",
|
|
254
|
+
props: cellProps,
|
|
255
|
+
content: [{ type: "text", text: "Do thing", styles: {} }],
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
type: "tableCell",
|
|
259
|
+
props: cellProps,
|
|
260
|
+
content: [{ type: "text", text: "It works", styles: {} }],
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
children: [],
|
|
267
|
+
},
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
expect(blocksToMarkdown(blocks)).toBe(
|
|
271
|
+
[
|
|
272
|
+
"| Step | Expected |",
|
|
273
|
+
"| --- | --- |",
|
|
274
|
+
"| Do thing | It works |",
|
|
275
|
+
].join("\n"),
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe("markdownToBlocks", () => {
|
|
281
|
+
it("parses test steps and test cases", () => {
|
|
282
|
+
const markdown = [
|
|
283
|
+
"* Open the Login page.",
|
|
284
|
+
" *Expected Result*: The Login page loads successfully.",
|
|
285
|
+
"",
|
|
286
|
+
":::test-case status=\"ready\" reference=\"QA-7\"",
|
|
287
|
+
"Run the smoke tests.",
|
|
288
|
+
":::",
|
|
289
|
+
].join("\n");
|
|
290
|
+
|
|
291
|
+
expect(markdownToBlocks(markdown)).toEqual([
|
|
292
|
+
{
|
|
293
|
+
type: "testStep",
|
|
294
|
+
props: {
|
|
295
|
+
stepTitle: "Open the Login page.",
|
|
296
|
+
stepData: "",
|
|
297
|
+
expectedResult: "The Login page loads successfully.",
|
|
298
|
+
},
|
|
299
|
+
children: [],
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
type: "testCase",
|
|
303
|
+
props: {
|
|
304
|
+
...baseProps,
|
|
305
|
+
status: "ready",
|
|
306
|
+
reference: "QA-7",
|
|
307
|
+
},
|
|
308
|
+
content: [{ type: "text", text: "Run the smoke tests.", styles: {} }],
|
|
309
|
+
children: [],
|
|
310
|
+
},
|
|
311
|
+
]);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("parses step lists with inline expected results label", () => {
|
|
315
|
+
const markdown = [
|
|
316
|
+
"## Test Description: Real-time notifications (chat, order updates, file received)",
|
|
317
|
+
"",
|
|
318
|
+
"This test case verifies the functionality of real-time notifications for chat messages, order updates, and file receipts within the application.",
|
|
319
|
+
"",
|
|
320
|
+
"### Preconditions",
|
|
321
|
+
"* The user is logged into the application.",
|
|
322
|
+
"* The user has the necessary permissions to receive notifications.",
|
|
323
|
+
"* The application is configured to send real-time notifications.",
|
|
324
|
+
"",
|
|
325
|
+
"### Steps",
|
|
326
|
+
"",
|
|
327
|
+
"* Step 1: Send a chat message to the user.",
|
|
328
|
+
"**Expected Result**: The user receives a real-time notification for the chat message.",
|
|
329
|
+
"* Step 2: Update an order status.",
|
|
330
|
+
"**Expected Result**: The user receives a real-time notification for the order update.",
|
|
331
|
+
"* Step 3: Send a file to the user.",
|
|
332
|
+
"**Expected Result**: The user receives a real-time notification for the file received.",
|
|
333
|
+
"* Step 4: Verify that the notifications are displayed correctly in the application's notification panel.",
|
|
334
|
+
"**Expected Result**: All notifications (chat message, order update, file received) are listed in the notification panel with the correct information (e.g., timestamp, message content).",
|
|
335
|
+
"",
|
|
336
|
+
"### Postconditions",
|
|
337
|
+
"* The user has received and viewed the notifications.",
|
|
338
|
+
"* The application continues to function as expected after receiving and processing the notifications.",
|
|
339
|
+
].join("\n");
|
|
340
|
+
|
|
341
|
+
const blocks = markdownToBlocks(markdown);
|
|
342
|
+
const stepBlocks = blocks.filter((block) => block.type === "testStep");
|
|
343
|
+
|
|
344
|
+
expect(stepBlocks).toEqual([
|
|
345
|
+
{
|
|
346
|
+
type: "testStep",
|
|
347
|
+
props: {
|
|
348
|
+
stepTitle: "Step 1: Send a chat message to the user.",
|
|
349
|
+
stepData: "",
|
|
350
|
+
expectedResult: "The user receives a real-time notification for the chat message.",
|
|
351
|
+
},
|
|
352
|
+
children: [],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
type: "testStep",
|
|
356
|
+
props: {
|
|
357
|
+
stepTitle: "Step 2: Update an order status.",
|
|
358
|
+
stepData: "",
|
|
359
|
+
expectedResult: "The user receives a real-time notification for the order update.",
|
|
360
|
+
},
|
|
361
|
+
children: [],
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
type: "testStep",
|
|
365
|
+
props: {
|
|
366
|
+
stepTitle: "Step 3: Send a file to the user.",
|
|
367
|
+
stepData: "",
|
|
368
|
+
expectedResult: "The user receives a real-time notification for the file received.",
|
|
369
|
+
},
|
|
370
|
+
children: [],
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
type: "testStep",
|
|
374
|
+
props: {
|
|
375
|
+
stepTitle: "Step 4: Verify that the notifications are displayed correctly in the application's notification panel.",
|
|
376
|
+
stepData: "",
|
|
377
|
+
expectedResult: "All notifications (chat message, order update, file received) are listed in the notification panel with the correct information (e.g., timestamp, message content).",
|
|
378
|
+
},
|
|
379
|
+
children: [],
|
|
380
|
+
},
|
|
381
|
+
]);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it("parses step data containing code fences, blank lines, and images", () => {
|
|
385
|
+
const markdown = [
|
|
386
|
+
"* Step 2: Update an order status.",
|
|
387
|
+
" ```",
|
|
388
|
+
" SQL CREATE bnbmnbm mnbmb mm",
|
|
389
|
+
" mn,nm nm, m,nm,n,nn,m,",
|
|
390
|
+
" ,n,n,mnm,n asdsad",
|
|
391
|
+
" asdsadsa",
|
|
392
|
+
" ",
|
|
393
|
+
" asdsadsadsadsad",
|
|
394
|
+
" ",
|
|
395
|
+
" asdsadas",
|
|
396
|
+
" ```",
|
|
397
|
+
" ",
|
|
398
|
+
"**Expected Result**: The user receives a real-time notification for the order update.",
|
|
399
|
+
].join("\n");
|
|
400
|
+
|
|
401
|
+
const expectedData = [
|
|
402
|
+
"```",
|
|
403
|
+
"SQL CREATE bnbmnbm mnbmb mm",
|
|
404
|
+
"mn,nm nm, m,nm,n,nn,m,",
|
|
405
|
+
",n,n,mnm,n asdsad",
|
|
406
|
+
"asdsadsa",
|
|
407
|
+
"",
|
|
408
|
+
"asdsadsadsadsad",
|
|
409
|
+
"",
|
|
410
|
+
"asdsadas",
|
|
411
|
+
"```",
|
|
412
|
+
"",
|
|
413
|
+
].join("\n");
|
|
414
|
+
|
|
415
|
+
expect(markdownToBlocks(markdown)).toEqual([
|
|
416
|
+
{
|
|
417
|
+
type: "testStep",
|
|
418
|
+
props: {
|
|
419
|
+
stepTitle: "Step 2: Update an order status.",
|
|
420
|
+
stepData: expectedData,
|
|
421
|
+
expectedResult: "The user receives a real-time notification for the order update.",
|
|
422
|
+
},
|
|
423
|
+
children: [],
|
|
424
|
+
},
|
|
425
|
+
]);
|
|
426
|
+
|
|
427
|
+
const markdownRoundTrip = blocksToMarkdown([
|
|
428
|
+
{
|
|
429
|
+
id: "step2",
|
|
430
|
+
type: "testStep",
|
|
431
|
+
props: {
|
|
432
|
+
stepTitle: "Step 2: Update an order status.",
|
|
433
|
+
stepData: expectedData,
|
|
434
|
+
expectedResult: "The user receives a real-time notification for the order update.",
|
|
435
|
+
},
|
|
436
|
+
content: undefined,
|
|
437
|
+
children: [],
|
|
438
|
+
},
|
|
439
|
+
]);
|
|
440
|
+
|
|
441
|
+
expect(markdownRoundTrip).toBe(
|
|
442
|
+
[
|
|
443
|
+
"* Step 2: Update an order status.",
|
|
444
|
+
" ```",
|
|
445
|
+
" SQL CREATE bnbmnbm mnbmb mm",
|
|
446
|
+
" mn,nm nm, m,nm,n,nn,m,",
|
|
447
|
+
" ,n,n,mnm,n asdsad",
|
|
448
|
+
" asdsadsa",
|
|
449
|
+
" ",
|
|
450
|
+
" asdsadsadsadsad",
|
|
451
|
+
" ",
|
|
452
|
+
" asdsadas",
|
|
453
|
+
" ```",
|
|
454
|
+
" ",
|
|
455
|
+
" *Expected Result*: The user receives a real-time notification for the order update.",
|
|
456
|
+
].join("\n"),
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it("parses bullet lists written with asterisk markers", () => {
|
|
461
|
+
const markdown = [
|
|
462
|
+
"### Preconditions",
|
|
463
|
+
"",
|
|
464
|
+
"* The user is logged into the application.",
|
|
465
|
+
"* The user has the necessary permissions to receive notifications.",
|
|
466
|
+
"* The application is configured to send real-time notifications.",
|
|
467
|
+
].join("\n");
|
|
468
|
+
|
|
469
|
+
expect(markdownToBlocks(markdown)).toEqual([
|
|
470
|
+
{
|
|
471
|
+
type: "heading",
|
|
472
|
+
props: { ...baseProps, level: 3 },
|
|
473
|
+
content: [{ type: "text", text: "Preconditions", styles: {} }],
|
|
474
|
+
children: [],
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
type: "bulletListItem",
|
|
478
|
+
props: baseProps,
|
|
479
|
+
content: [
|
|
480
|
+
{
|
|
481
|
+
type: "text",
|
|
482
|
+
text: "The user is logged into the application.",
|
|
483
|
+
styles: {},
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
children: [],
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
type: "bulletListItem",
|
|
490
|
+
props: baseProps,
|
|
491
|
+
content: [
|
|
492
|
+
{
|
|
493
|
+
type: "text",
|
|
494
|
+
text: "The user has the necessary permissions to receive notifications.",
|
|
495
|
+
styles: {},
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
children: [],
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
type: "bulletListItem",
|
|
502
|
+
props: baseProps,
|
|
503
|
+
content: [
|
|
504
|
+
{
|
|
505
|
+
type: "text",
|
|
506
|
+
text: "The application is configured to send real-time notifications.",
|
|
507
|
+
styles: {},
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
children: [],
|
|
511
|
+
},
|
|
512
|
+
]);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it("handles nested lists with uneven indentation", () => {
|
|
516
|
+
const markdown = [
|
|
517
|
+
"### Requirements",
|
|
518
|
+
"",
|
|
519
|
+
"* Verify that each individual unit test completes in ≤ 50 ms (target) and never exceeds 200 ms (hard limit).",
|
|
520
|
+
"",
|
|
521
|
+
"### Steps",
|
|
522
|
+
"",
|
|
523
|
+
"1. Execute the full unit test suite with a timer wrapper.",
|
|
524
|
+
" * Each individual test case.",
|
|
525
|
+
" * Each test file.",
|
|
526
|
+
"2. Collect timing data for the security-critical modules.",
|
|
527
|
+
].join("\n");
|
|
528
|
+
|
|
529
|
+
const blocks = markdownToBlocks(markdown);
|
|
530
|
+
const numbered = blocks.filter((block) => block.type === "numberedListItem");
|
|
531
|
+
|
|
532
|
+
expect(numbered).not.toHaveLength(0);
|
|
533
|
+
const nestedChildren = numbered.flatMap((block) => block.children ?? []);
|
|
534
|
+
expect(nestedChildren.some((child) => child.type === "bulletListItem")).toBe(true);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("parses expected result prefixes with emphasis", () => {
|
|
538
|
+
const markdown = [
|
|
539
|
+
"* Open the form.",
|
|
540
|
+
" **Expected Result:** The form opens.",
|
|
541
|
+
" Expected: Fields are empty.",
|
|
542
|
+
].join("\n");
|
|
543
|
+
|
|
544
|
+
expect(markdownToBlocks(markdown)).toEqual([
|
|
545
|
+
{
|
|
546
|
+
type: "testStep",
|
|
547
|
+
props: {
|
|
548
|
+
stepTitle: "Open the form.",
|
|
549
|
+
stepData: "",
|
|
550
|
+
expectedResult: "Fields are empty.",
|
|
551
|
+
},
|
|
552
|
+
children: [],
|
|
553
|
+
},
|
|
554
|
+
]);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it("parses test step with data", () => {
|
|
558
|
+
const markdown = [
|
|
559
|
+
"* Navigate to login",
|
|
560
|
+
" Open browser",
|
|
561
|
+
" Go to login page",
|
|
562
|
+
" *Expected Result*: Login form visible",
|
|
563
|
+
].join("\n");
|
|
564
|
+
|
|
565
|
+
expect(markdownToBlocks(markdown)).toEqual([
|
|
566
|
+
{
|
|
567
|
+
type: "testStep",
|
|
568
|
+
props: {
|
|
569
|
+
stepTitle: "Navigate to login",
|
|
570
|
+
stepData: "Open browser\nGo to login page",
|
|
571
|
+
expectedResult: "Login form visible",
|
|
572
|
+
},
|
|
573
|
+
children: [],
|
|
574
|
+
},
|
|
575
|
+
]);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it("parses unindented step data between the title and expected result", () => {
|
|
579
|
+
const markdown = [
|
|
580
|
+
"* Prepare test fixtures",
|
|
581
|
+
"Collect user accounts from staging.",
|
|
582
|
+
"Reset passwords for all test accounts.",
|
|
583
|
+
"*Expected Result*: Test accounts are ready for execution.",
|
|
584
|
+
].join("\n");
|
|
585
|
+
|
|
586
|
+
expect(markdownToBlocks(markdown)).toEqual([
|
|
587
|
+
{
|
|
588
|
+
type: "testStep",
|
|
589
|
+
props: {
|
|
590
|
+
stepTitle: "Prepare test fixtures",
|
|
591
|
+
stepData: "Collect user accounts from staging.\nReset passwords for all test accounts.",
|
|
592
|
+
expectedResult: "Test accounts are ready for execution.",
|
|
593
|
+
},
|
|
594
|
+
children: [],
|
|
595
|
+
},
|
|
596
|
+
]);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it("parses expected result containing a markdown image", () => {
|
|
600
|
+
const markdown = [
|
|
601
|
+
"* Display the generated report.",
|
|
602
|
+
" *Expected Result*: ",
|
|
603
|
+
].join("\n");
|
|
604
|
+
|
|
605
|
+
expect(markdownToBlocks(markdown)).toEqual([
|
|
606
|
+
{
|
|
607
|
+
type: "testStep",
|
|
608
|
+
props: {
|
|
609
|
+
stepTitle: "Display the generated report.",
|
|
610
|
+
stepData: "",
|
|
611
|
+
expectedResult: "",
|
|
612
|
+
},
|
|
613
|
+
children: [],
|
|
614
|
+
},
|
|
615
|
+
]);
|
|
616
|
+
|
|
617
|
+
const markdownRoundTrip = blocksToMarkdown([
|
|
618
|
+
{
|
|
619
|
+
id: "step-image",
|
|
620
|
+
type: "testStep",
|
|
621
|
+
props: {
|
|
622
|
+
stepTitle: "Display the generated report.",
|
|
623
|
+
stepData: "",
|
|
624
|
+
expectedResult: "",
|
|
625
|
+
},
|
|
626
|
+
content: undefined,
|
|
627
|
+
children: [],
|
|
628
|
+
},
|
|
629
|
+
]);
|
|
630
|
+
|
|
631
|
+
expect(markdownRoundTrip).toBe(markdown);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
it("parses expected result with short expected label and image", () => {
|
|
635
|
+
const markdown = [
|
|
636
|
+
"* Should open login screen",
|
|
637
|
+
" *Expected*: Login should look like this",
|
|
638
|
+
" ",
|
|
639
|
+
].join("\n");
|
|
640
|
+
|
|
641
|
+
expect(markdownToBlocks(markdown)).toEqual([
|
|
642
|
+
{
|
|
643
|
+
type: "testStep",
|
|
644
|
+
props: {
|
|
645
|
+
stepTitle: "Should open login screen",
|
|
646
|
+
stepData: "",
|
|
647
|
+
expectedResult: "Login should look like this\n",
|
|
648
|
+
},
|
|
649
|
+
children: [],
|
|
650
|
+
},
|
|
651
|
+
]);
|
|
652
|
+
|
|
653
|
+
const roundTrip = blocksToMarkdown([
|
|
654
|
+
{
|
|
655
|
+
id: "step-login",
|
|
656
|
+
type: "testStep",
|
|
657
|
+
props: {
|
|
658
|
+
stepTitle: "Should open login screen",
|
|
659
|
+
stepData: "",
|
|
660
|
+
expectedResult: "Login should look like this\n",
|
|
661
|
+
},
|
|
662
|
+
content: undefined,
|
|
663
|
+
children: [],
|
|
664
|
+
},
|
|
665
|
+
]);
|
|
666
|
+
|
|
667
|
+
expect(roundTrip).toBe(
|
|
668
|
+
[
|
|
669
|
+
"* Should open login screen",
|
|
670
|
+
" *Expected Result*: Login should look like this",
|
|
671
|
+
" ",
|
|
672
|
+
].join("\n"),
|
|
673
|
+
);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it("round-trips simple blocks", () => {
|
|
677
|
+
const blocks: CustomEditorBlock[] = [
|
|
678
|
+
{
|
|
679
|
+
id: "p1",
|
|
680
|
+
type: "paragraph",
|
|
681
|
+
props: baseProps,
|
|
682
|
+
content: [{ type: "text", text: "Paragraph", styles: {} }],
|
|
683
|
+
children: [],
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
id: "b1",
|
|
687
|
+
type: "bulletListItem",
|
|
688
|
+
props: baseProps,
|
|
689
|
+
content: [{ type: "text", text: "Bullet", styles: {} }],
|
|
690
|
+
children: [],
|
|
691
|
+
},
|
|
692
|
+
];
|
|
693
|
+
|
|
694
|
+
const markdown = blocksToMarkdown(blocks);
|
|
695
|
+
const parsed = markdownToBlocks(markdown);
|
|
696
|
+
expect(parsed).toEqual(blocks.map(toPartial));
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
it("parses markdown tables", () => {
|
|
700
|
+
const markdown = [
|
|
701
|
+
"| Step | Expected |",
|
|
702
|
+
"| :--- | ---: |",
|
|
703
|
+
"| Do thing | It works |",
|
|
704
|
+
].join("\n");
|
|
705
|
+
|
|
706
|
+
expect(markdownToBlocks(markdown)).toEqual([
|
|
707
|
+
{
|
|
708
|
+
type: "table",
|
|
709
|
+
props: { textColor: "default" },
|
|
710
|
+
content: {
|
|
711
|
+
type: "tableContent",
|
|
712
|
+
columnWidths: [undefined, undefined],
|
|
713
|
+
headerRows: 1,
|
|
714
|
+
rows: [
|
|
715
|
+
{
|
|
716
|
+
cells: [
|
|
717
|
+
{
|
|
718
|
+
type: "tableCell",
|
|
719
|
+
props: { ...cellProps, textAlignment: "left" },
|
|
720
|
+
content: [{ type: "text", text: "Step", styles: {} }],
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
type: "tableCell",
|
|
724
|
+
props: { ...cellProps, textAlignment: "right" },
|
|
725
|
+
content: [{ type: "text", text: "Expected", styles: {} }],
|
|
726
|
+
},
|
|
727
|
+
],
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
cells: [
|
|
731
|
+
{
|
|
732
|
+
type: "tableCell",
|
|
733
|
+
props: { ...cellProps, textAlignment: "left" },
|
|
734
|
+
content: [{ type: "text", text: "Do thing", styles: {} }],
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
type: "tableCell",
|
|
738
|
+
props: { ...cellProps, textAlignment: "right" },
|
|
739
|
+
content: [{ type: "text", text: "It works", styles: {} }],
|
|
740
|
+
},
|
|
741
|
+
],
|
|
742
|
+
},
|
|
743
|
+
],
|
|
744
|
+
},
|
|
745
|
+
children: [],
|
|
746
|
+
},
|
|
747
|
+
]);
|
|
748
|
+
});
|
|
749
|
+
});
|