testomatio-editor-blocks 0.4.72 → 0.4.74
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.
|
@@ -43,8 +43,46 @@ function escapeMarkdown(text) {
|
|
|
43
43
|
result += text.slice(lastIndex).replace(SPECIAL_CHAR_REGEX, "\\$1");
|
|
44
44
|
return result;
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
// Creates a stateful escaper that escapes dots outside Markdown code while
|
|
47
|
+
// leaving code spans (inline `…`, fenced ``` … ```) verbatim — backslash
|
|
48
|
+
// escapes are ignored inside code, so `\.` would render literally.
|
|
49
|
+
//
|
|
50
|
+
// The state (an open, not-yet-closed backtick run) carries across calls so a
|
|
51
|
+
// fence can be opened in one segment and closed in another. This matters
|
|
52
|
+
// because the step editor splits a multi-line code block across props: the
|
|
53
|
+
// opening ``` lands in `stepTitle` while the body and closing ``` land in
|
|
54
|
+
// `stepData`.
|
|
55
|
+
function makeStepEscaper() {
|
|
56
|
+
let fence = null;
|
|
57
|
+
return (text) => {
|
|
58
|
+
let result = "";
|
|
59
|
+
let i = 0;
|
|
60
|
+
while (i < text.length) {
|
|
61
|
+
if (text[i] === "`") {
|
|
62
|
+
let ticks = 0;
|
|
63
|
+
while (text[i + ticks] === "`")
|
|
64
|
+
ticks++;
|
|
65
|
+
const run = "`".repeat(ticks);
|
|
66
|
+
result += run;
|
|
67
|
+
i += ticks;
|
|
68
|
+
if (fence === null) {
|
|
69
|
+
fence = run; // open a code span/fence
|
|
70
|
+
}
|
|
71
|
+
else if (fence === run) {
|
|
72
|
+
fence = null; // matching run closes it
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (fence !== null) {
|
|
77
|
+
result += text[i]; // inside code — copy verbatim
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
result += text[i] === "." ? "\\." : text[i];
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
48
86
|
}
|
|
49
87
|
function stripHtmlWrappers(text) {
|
|
50
88
|
return text
|
|
@@ -380,38 +418,35 @@ function serializeBlock(block, ctx, orderedIndex, stepIndex) {
|
|
|
380
418
|
.join(" ");
|
|
381
419
|
const normalizedExpectedForCheck = stripExpectedPrefix(expectedResult).trim();
|
|
382
420
|
const hasContent = stepData.length > 0 || normalizedExpectedForCheck.length > 0;
|
|
421
|
+
// One escaper threads code-fence state from the title into the data: the
|
|
422
|
+
// editor splits a multi-line code block so the opening ``` sits in the
|
|
423
|
+
// title and the body + closing ``` sit in the data. Both must be treated
|
|
424
|
+
// as a single code region so their dots stay literal.
|
|
425
|
+
const escapeBody = makeStepEscaper();
|
|
383
426
|
if (normalizedTitle.length > 0 || hasContent) {
|
|
384
427
|
const listStyle = (_o = block.props.listStyle) !== null && _o !== void 0 ? _o : "bullet";
|
|
385
428
|
const prefix = listStyle === "ordered" ? `${(stepIndex !== null && stepIndex !== void 0 ? stepIndex : 0) + 1}.` : "*";
|
|
386
|
-
lines.push(normalizedTitle.length > 0 ? `${prefix} ${
|
|
429
|
+
lines.push(normalizedTitle.length > 0 ? `${prefix} ${escapeBody(normalizedTitle)}` : `${prefix} `);
|
|
387
430
|
}
|
|
388
431
|
if (stepData.length > 0) {
|
|
389
|
-
const
|
|
390
|
-
|
|
432
|
+
const escaped = escapeBody(stepData);
|
|
433
|
+
escaped.split(/\r?\n/).forEach((dataLine) => {
|
|
391
434
|
const trimmedLine = dataLine.trim();
|
|
392
|
-
|
|
393
|
-
lines.push(` ${escapeStepContent(trimmedLine)}`);
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
lines.push(" ");
|
|
397
|
-
}
|
|
435
|
+
lines.push(trimmedLine.length === 0 ? " " : ` ${trimmedLine}`);
|
|
398
436
|
});
|
|
399
437
|
}
|
|
400
438
|
const normalizedExpected = stripExpectedPrefix(expectedResult).trim();
|
|
401
439
|
if (normalizedExpected.length > 0) {
|
|
402
|
-
const
|
|
440
|
+
const escaped = makeStepEscaper()(normalizedExpected);
|
|
403
441
|
const label = "*Expected result*";
|
|
404
|
-
|
|
442
|
+
let isFirst = true;
|
|
443
|
+
escaped.split(/\r?\n/).forEach((expectedLine) => {
|
|
405
444
|
const trimmedLine = expectedLine.trim();
|
|
406
445
|
if (trimmedLine.length === 0) {
|
|
407
446
|
return;
|
|
408
447
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
lines.push(` ${escapeStepContent(trimmedLine)}`);
|
|
414
|
-
}
|
|
448
|
+
lines.push(isFirst ? ` ${label}: ${trimmedLine}` : ` ${trimmedLine}`);
|
|
449
|
+
isFirst = false;
|
|
415
450
|
});
|
|
416
451
|
}
|
|
417
452
|
return lines;
|
package/package.json
CHANGED
|
@@ -1249,6 +1249,112 @@ describe("markdownToBlocks", () => {
|
|
|
1249
1249
|
);
|
|
1250
1250
|
});
|
|
1251
1251
|
|
|
1252
|
+
it("does not escape dots inside fenced code blocks in step data", () => {
|
|
1253
|
+
const stepData = [
|
|
1254
|
+
"```",
|
|
1255
|
+
"curl 'https://stable.testomat.io/api/runs/54?page=1&entry=' \\",
|
|
1256
|
+
" -H 'accept-language: en-US,en;q=0.9' \\",
|
|
1257
|
+
" -b 'gclau=1.1.1681594017.1781077572;'",
|
|
1258
|
+
"```",
|
|
1259
|
+
].join("\n");
|
|
1260
|
+
|
|
1261
|
+
const markdown = blocksToMarkdown([
|
|
1262
|
+
{
|
|
1263
|
+
id: "step1",
|
|
1264
|
+
type: "testStep",
|
|
1265
|
+
props: {
|
|
1266
|
+
stepTitle: "Run the request.",
|
|
1267
|
+
stepData,
|
|
1268
|
+
expectedResult: "",
|
|
1269
|
+
listStyle: "bullet",
|
|
1270
|
+
},
|
|
1271
|
+
content: undefined,
|
|
1272
|
+
children: [],
|
|
1273
|
+
},
|
|
1274
|
+
]);
|
|
1275
|
+
|
|
1276
|
+
expect(markdown).toBe(
|
|
1277
|
+
[
|
|
1278
|
+
// Title outside the fence is still escaped.
|
|
1279
|
+
"* Run the request\\.",
|
|
1280
|
+
// Dots inside the fence stay literal.
|
|
1281
|
+
" ```",
|
|
1282
|
+
" curl 'https://stable.testomat.io/api/runs/54?page=1&entry=' \\",
|
|
1283
|
+
" -H 'accept-language: en-US,en;q=0.9' \\",
|
|
1284
|
+
" -b 'gclau=1.1.1681594017.1781077572;'",
|
|
1285
|
+
" ```",
|
|
1286
|
+
].join("\n"),
|
|
1287
|
+
);
|
|
1288
|
+
|
|
1289
|
+
// Round-trip stays stable.
|
|
1290
|
+
const roundTrip = blocksToMarkdown(
|
|
1291
|
+
markdownToBlocks(["### Steps", "", markdown].join("\n")) as CustomEditorBlock[],
|
|
1292
|
+
);
|
|
1293
|
+
expect(roundTrip).toContain(
|
|
1294
|
+
" curl 'https://stable.testomat.io/api/runs/54?page=1&entry=' \\",
|
|
1295
|
+
);
|
|
1296
|
+
expect(roundTrip).toContain(" -b 'gclau=1.1.1681594017.1781077572;'");
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
it("does not escape dots inside inline code in step data", () => {
|
|
1300
|
+
const markdown = blocksToMarkdown([
|
|
1301
|
+
{
|
|
1302
|
+
id: "step1",
|
|
1303
|
+
type: "testStep",
|
|
1304
|
+
props: {
|
|
1305
|
+
stepTitle: "Run the request.",
|
|
1306
|
+
stepData: "`curl https://example.com/api`",
|
|
1307
|
+
expectedResult: "",
|
|
1308
|
+
listStyle: "bullet",
|
|
1309
|
+
},
|
|
1310
|
+
content: undefined,
|
|
1311
|
+
children: [],
|
|
1312
|
+
},
|
|
1313
|
+
]);
|
|
1314
|
+
|
|
1315
|
+
expect(markdown).toBe(
|
|
1316
|
+
[
|
|
1317
|
+
// Title outside code is still escaped.
|
|
1318
|
+
"* Run the request\\.",
|
|
1319
|
+
// Dots inside the inline code span stay literal.
|
|
1320
|
+
" `curl https://example.com/api`",
|
|
1321
|
+
].join("\n"),
|
|
1322
|
+
);
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
it("does not escape dots when a fenced block opens in the title and the body lands in step data", () => {
|
|
1326
|
+
// The step editor splits a multi-line code block: the opening ``` becomes
|
|
1327
|
+
// the title and the body + closing ``` become the step data.
|
|
1328
|
+
const markdown = blocksToMarkdown([
|
|
1329
|
+
{
|
|
1330
|
+
id: "step1",
|
|
1331
|
+
type: "testStep",
|
|
1332
|
+
props: {
|
|
1333
|
+
stepTitle: "```",
|
|
1334
|
+
stepData: [
|
|
1335
|
+
"curl --location 'https://example.com.ua' \\",
|
|
1336
|
+
"--data-raw '{ \"method\": \"url.method\" }'",
|
|
1337
|
+
"```",
|
|
1338
|
+
].join("\n"),
|
|
1339
|
+
expectedResult: "",
|
|
1340
|
+
listStyle: "bullet",
|
|
1341
|
+
},
|
|
1342
|
+
content: undefined,
|
|
1343
|
+
children: [],
|
|
1344
|
+
},
|
|
1345
|
+
]);
|
|
1346
|
+
|
|
1347
|
+
expect(markdown).toBe(
|
|
1348
|
+
[
|
|
1349
|
+
"* ```",
|
|
1350
|
+
// Dots inside the fence stay literal even though it opened in the title.
|
|
1351
|
+
" curl --location 'https://example.com.ua' \\",
|
|
1352
|
+
" --data-raw '{ \"method\": \"url.method\" }'",
|
|
1353
|
+
" ```",
|
|
1354
|
+
].join("\n"),
|
|
1355
|
+
);
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1252
1358
|
it("does not include content after a blank line in step data", () => {
|
|
1253
1359
|
const markdown = [
|
|
1254
1360
|
"### Steps",
|
|
@@ -84,8 +84,44 @@ function escapeMarkdown(text: string): string {
|
|
|
84
84
|
return result;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
// Creates a stateful escaper that escapes dots outside Markdown code while
|
|
88
|
+
// leaving code spans (inline `…`, fenced ``` … ```) verbatim — backslash
|
|
89
|
+
// escapes are ignored inside code, so `\.` would render literally.
|
|
90
|
+
//
|
|
91
|
+
// The state (an open, not-yet-closed backtick run) carries across calls so a
|
|
92
|
+
// fence can be opened in one segment and closed in another. This matters
|
|
93
|
+
// because the step editor splits a multi-line code block across props: the
|
|
94
|
+
// opening ``` lands in `stepTitle` while the body and closing ``` land in
|
|
95
|
+
// `stepData`.
|
|
96
|
+
function makeStepEscaper(): (text: string) => string {
|
|
97
|
+
let fence: string | null = null;
|
|
98
|
+
return (text: string): string => {
|
|
99
|
+
let result = "";
|
|
100
|
+
let i = 0;
|
|
101
|
+
while (i < text.length) {
|
|
102
|
+
if (text[i] === "`") {
|
|
103
|
+
let ticks = 0;
|
|
104
|
+
while (text[i + ticks] === "`") ticks++;
|
|
105
|
+
const run = "`".repeat(ticks);
|
|
106
|
+
result += run;
|
|
107
|
+
i += ticks;
|
|
108
|
+
if (fence === null) {
|
|
109
|
+
fence = run; // open a code span/fence
|
|
110
|
+
} else if (fence === run) {
|
|
111
|
+
fence = null; // matching run closes it
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (fence !== null) {
|
|
116
|
+
result += text[i]; // inside code — copy verbatim
|
|
117
|
+
i++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
result += text[i] === "." ? "\\." : text[i];
|
|
121
|
+
i++;
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
};
|
|
89
125
|
}
|
|
90
126
|
|
|
91
127
|
function stripHtmlWrappers(text: string): string {
|
|
@@ -461,39 +497,38 @@ function serializeBlock(
|
|
|
461
497
|
const normalizedExpectedForCheck = stripExpectedPrefix(expectedResult).trim();
|
|
462
498
|
const hasContent = stepData.length > 0 || normalizedExpectedForCheck.length > 0;
|
|
463
499
|
|
|
500
|
+
// One escaper threads code-fence state from the title into the data: the
|
|
501
|
+
// editor splits a multi-line code block so the opening ``` sits in the
|
|
502
|
+
// title and the body + closing ``` sit in the data. Both must be treated
|
|
503
|
+
// as a single code region so their dots stay literal.
|
|
504
|
+
const escapeBody = makeStepEscaper();
|
|
505
|
+
|
|
464
506
|
if (normalizedTitle.length > 0 || hasContent) {
|
|
465
507
|
const listStyle = (block.props as any).listStyle ?? "bullet";
|
|
466
508
|
const prefix = listStyle === "ordered" ? `${(stepIndex ?? 0) + 1}.` : "*";
|
|
467
|
-
lines.push(normalizedTitle.length > 0 ? `${prefix} ${
|
|
509
|
+
lines.push(normalizedTitle.length > 0 ? `${prefix} ${escapeBody(normalizedTitle)}` : `${prefix} `);
|
|
468
510
|
}
|
|
469
511
|
|
|
470
512
|
if (stepData.length > 0) {
|
|
471
|
-
const
|
|
472
|
-
|
|
513
|
+
const escaped = escapeBody(stepData);
|
|
514
|
+
escaped.split(/\r?\n/).forEach((dataLine: string) => {
|
|
473
515
|
const trimmedLine = dataLine.trim();
|
|
474
|
-
|
|
475
|
-
lines.push(` ${escapeStepContent(trimmedLine)}`);
|
|
476
|
-
} else {
|
|
477
|
-
lines.push(" ");
|
|
478
|
-
}
|
|
516
|
+
lines.push(trimmedLine.length === 0 ? " " : ` ${trimmedLine}`);
|
|
479
517
|
});
|
|
480
518
|
}
|
|
481
519
|
|
|
482
520
|
const normalizedExpected = stripExpectedPrefix(expectedResult).trim();
|
|
483
521
|
if (normalizedExpected.length > 0) {
|
|
484
|
-
const
|
|
522
|
+
const escaped = makeStepEscaper()(normalizedExpected);
|
|
485
523
|
const label = "*Expected result*";
|
|
486
|
-
|
|
524
|
+
let isFirst = true;
|
|
525
|
+
escaped.split(/\r?\n/).forEach((expectedLine: string) => {
|
|
487
526
|
const trimmedLine = expectedLine.trim();
|
|
488
527
|
if (trimmedLine.length === 0) {
|
|
489
528
|
return;
|
|
490
529
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
lines.push(` ${label}: ${escapeStepContent(trimmedLine)}`);
|
|
494
|
-
} else {
|
|
495
|
-
lines.push(` ${escapeStepContent(trimmedLine)}`);
|
|
496
|
-
}
|
|
530
|
+
lines.push(isFirst ? ` ${label}: ${trimmedLine}` : ` ${trimmedLine}`);
|
|
531
|
+
isFirst = false;
|
|
497
532
|
});
|
|
498
533
|
}
|
|
499
534
|
|