testomatio-editor-blocks 0.4.73 → 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,53 +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
|
-
|
|
391
|
-
dataLines.forEach((dataLine) => {
|
|
432
|
+
const escaped = escapeBody(stepData);
|
|
433
|
+
escaped.split(/\r?\n/).forEach((dataLine) => {
|
|
392
434
|
const trimmedLine = dataLine.trim();
|
|
393
|
-
|
|
394
|
-
lines.push(" ");
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
// Don't escape dots inside fenced code blocks (or on the fence lines
|
|
398
|
-
// themselves) — Markdown ignores backslash escapes there, so `\.`
|
|
399
|
-
// would render literally.
|
|
400
|
-
const isFence = trimmedLine.startsWith("```");
|
|
401
|
-
const content = insideCodeFence || isFence ? trimmedLine : escapeStepContent(trimmedLine);
|
|
402
|
-
lines.push(` ${content}`);
|
|
403
|
-
if (isFence) {
|
|
404
|
-
insideCodeFence = !insideCodeFence;
|
|
405
|
-
}
|
|
435
|
+
lines.push(trimmedLine.length === 0 ? " " : ` ${trimmedLine}`);
|
|
406
436
|
});
|
|
407
437
|
}
|
|
408
438
|
const normalizedExpected = stripExpectedPrefix(expectedResult).trim();
|
|
409
439
|
if (normalizedExpected.length > 0) {
|
|
410
|
-
const
|
|
440
|
+
const escaped = makeStepEscaper()(normalizedExpected);
|
|
411
441
|
const label = "*Expected result*";
|
|
412
|
-
let
|
|
413
|
-
|
|
442
|
+
let isFirst = true;
|
|
443
|
+
escaped.split(/\r?\n/).forEach((expectedLine) => {
|
|
414
444
|
const trimmedLine = expectedLine.trim();
|
|
415
445
|
if (trimmedLine.length === 0) {
|
|
416
446
|
return;
|
|
417
447
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const content = insideCodeFence || isFence ? trimmedLine : escapeStepContent(trimmedLine);
|
|
421
|
-
if (index === 0) {
|
|
422
|
-
lines.push(` ${label}: ${content}`);
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
lines.push(` ${content}`);
|
|
426
|
-
}
|
|
427
|
-
if (isFence) {
|
|
428
|
-
insideCodeFence = !insideCodeFence;
|
|
429
|
-
}
|
|
448
|
+
lines.push(isFirst ? ` ${label}: ${trimmedLine}` : ` ${trimmedLine}`);
|
|
449
|
+
isFirst = false;
|
|
430
450
|
});
|
|
431
451
|
}
|
|
432
452
|
return lines;
|
package/package.json
CHANGED
|
@@ -1296,6 +1296,65 @@ describe("markdownToBlocks", () => {
|
|
|
1296
1296
|
expect(roundTrip).toContain(" -b 'gclau=1.1.1681594017.1781077572;'");
|
|
1297
1297
|
});
|
|
1298
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
|
+
|
|
1299
1358
|
it("does not include content after a blank line in step data", () => {
|
|
1300
1359
|
const markdown = [
|
|
1301
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,57 +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
|
-
|
|
473
|
-
dataLines.forEach((dataLine: string) => {
|
|
513
|
+
const escaped = escapeBody(stepData);
|
|
514
|
+
escaped.split(/\r?\n/).forEach((dataLine: string) => {
|
|
474
515
|
const trimmedLine = dataLine.trim();
|
|
475
|
-
|
|
476
|
-
lines.push(" ");
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
// Don't escape dots inside fenced code blocks (or on the fence lines
|
|
480
|
-
// themselves) — Markdown ignores backslash escapes there, so `\.`
|
|
481
|
-
// would render literally.
|
|
482
|
-
const isFence = trimmedLine.startsWith("```");
|
|
483
|
-
const content =
|
|
484
|
-
insideCodeFence || isFence ? trimmedLine : escapeStepContent(trimmedLine);
|
|
485
|
-
lines.push(` ${content}`);
|
|
486
|
-
if (isFence) {
|
|
487
|
-
insideCodeFence = !insideCodeFence;
|
|
488
|
-
}
|
|
516
|
+
lines.push(trimmedLine.length === 0 ? " " : ` ${trimmedLine}`);
|
|
489
517
|
});
|
|
490
518
|
}
|
|
491
519
|
|
|
492
520
|
const normalizedExpected = stripExpectedPrefix(expectedResult).trim();
|
|
493
521
|
if (normalizedExpected.length > 0) {
|
|
494
|
-
const
|
|
522
|
+
const escaped = makeStepEscaper()(normalizedExpected);
|
|
495
523
|
const label = "*Expected result*";
|
|
496
|
-
let
|
|
497
|
-
|
|
524
|
+
let isFirst = true;
|
|
525
|
+
escaped.split(/\r?\n/).forEach((expectedLine: string) => {
|
|
498
526
|
const trimmedLine = expectedLine.trim();
|
|
499
527
|
if (trimmedLine.length === 0) {
|
|
500
528
|
return;
|
|
501
529
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const isFence = trimmedLine.startsWith("```");
|
|
505
|
-
const content =
|
|
506
|
-
insideCodeFence || isFence ? trimmedLine : escapeStepContent(trimmedLine);
|
|
507
|
-
if (index === 0) {
|
|
508
|
-
lines.push(` ${label}: ${content}`);
|
|
509
|
-
} else {
|
|
510
|
-
lines.push(` ${content}`);
|
|
511
|
-
}
|
|
512
|
-
if (isFence) {
|
|
513
|
-
insideCodeFence = !insideCodeFence;
|
|
514
|
-
}
|
|
530
|
+
lines.push(isFirst ? ` ${label}: ${trimmedLine}` : ` ${trimmedLine}`);
|
|
531
|
+
isFirst = false;
|
|
515
532
|
});
|
|
516
533
|
}
|
|
517
534
|
|