testomatio-editor-blocks 0.4.44 → 0.4.46
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/step.js +22 -4
- package/package/editor/blocks/stepHorizontalView.d.ts +2 -2
- package/package/editor/blocks/stepHorizontalView.js +4 -3
- package/package/editor/customMarkdownConverter.js +26 -9
- package/package/styles.css +5 -5
- package/package.json +1 -1
- package/src/editor/blocks/step.tsx +43 -37
- package/src/editor/blocks/stepHorizontalView.tsx +5 -5
- package/src/editor/customMarkdownConverter.test.ts +71 -4
- package/src/editor/customMarkdownConverter.ts +20 -7
- package/src/editor/styles.css +5 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createReactBlockSpec, useEditorChange } from "@blocknote/react";
|
|
3
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { StepField } from "./stepField";
|
|
5
5
|
import { StepHorizontalView } from "./stepHorizontalView";
|
|
6
6
|
import { useStepImageUpload } from "../stepImageUpload";
|
|
@@ -9,6 +9,7 @@ const VIEW_MODE_KEY = "bn-step-view-mode";
|
|
|
9
9
|
const STEP_TITLE_PLACEHOLDER = "Enter step title...";
|
|
10
10
|
const STEP_DATA_PLACEHOLDER = "Enter step data...";
|
|
11
11
|
const EXPECTED_RESULT_PLACEHOLDER = "Enter expected result...";
|
|
12
|
+
const FORCE_VERTICAL_WIDTH = 550;
|
|
12
13
|
/* readExpectedCollapsedPreference removed — currently unused */
|
|
13
14
|
const writeExpectedCollapsedPreference = (collapsed) => {
|
|
14
15
|
if (typeof window === "undefined") {
|
|
@@ -172,6 +173,22 @@ export const stepBlock = createReactBlockSpec({
|
|
|
172
173
|
const [documentVersion, setDocumentVersion] = useState(0);
|
|
173
174
|
const uploadImage = useStepImageUpload();
|
|
174
175
|
const [viewMode, setViewMode] = useState(() => readStepViewMode());
|
|
176
|
+
const containerRef = useRef(null);
|
|
177
|
+
const [forceVertical, setForceVertical] = useState(false);
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
var _a;
|
|
180
|
+
const el = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.parentElement;
|
|
181
|
+
if (!el)
|
|
182
|
+
return;
|
|
183
|
+
const observer = new ResizeObserver((entries) => {
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
setForceVertical(entry.contentRect.width < FORCE_VERTICAL_WIDTH);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
observer.observe(el);
|
|
189
|
+
return () => observer.disconnect();
|
|
190
|
+
}, []);
|
|
191
|
+
const effectiveVertical = forceVertical || viewMode === "vertical";
|
|
175
192
|
// Calculate step number based on position in document
|
|
176
193
|
const stepNumber = useMemo(() => {
|
|
177
194
|
const allBlocks = editor.document;
|
|
@@ -337,10 +354,11 @@ export const stepBlock = createReactBlockSpec({
|
|
|
337
354
|
}, [expectedHasContent, isExpectedVisible]);
|
|
338
355
|
const canToggleData = !dataHasContent;
|
|
339
356
|
const canToggleExpected = !expectedHasContent;
|
|
340
|
-
|
|
341
|
-
|
|
357
|
+
const viewToggleButton = (_jsx("button", { type: "button", className: `bn-teststep__view-toggle${!effectiveVertical ? " bn-teststep__view-toggle--horizontal" : ""}${forceVertical ? " bn-teststep__view-toggle--disabled" : ""}`, "data-tooltip": forceVertical ? "Not enough space for horizontal view" : "Switch step view", "aria-label": forceVertical ? "Not enough space for horizontal view" : "Switch step view", onClick: forceVertical ? undefined : handleToggleView, "aria-disabled": forceVertical, children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [_jsx("mask", { id: "mask-toggle", style: { maskType: "alpha" }, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "16", height: "16", children: _jsx("rect", { width: "16", height: "16", fill: "#D9D9D9" }) }), _jsx("g", { mask: "url(#mask-toggle)", children: _jsx("path", { d: "M12.6667 2C13.0333 2 13.3472 2.13056 13.6083 2.39167C13.8694 2.65278 14 2.96667 14 3.33333L14 12.6667C14 13.0333 13.8694 13.3472 13.6083 13.6083C13.3472 13.8694 13.0333 14 12.6667 14L10 14C9.63333 14 9.31944 13.8694 9.05833 13.6083C8.79722 13.3472 8.66667 13.0333 8.66667 12.6667L8.66667 3.33333C8.66667 2.96667 8.79722 2.65278 9.05833 2.39167C9.31945 2.13055 9.63333 2 10 2L12.6667 2ZM6 2C6.36667 2 6.68056 2.13055 6.94167 2.39167C7.20278 2.65278 7.33333 2.96667 7.33333 3.33333L7.33333 12.6667C7.33333 13.0333 7.20278 13.3472 6.94167 13.6083C6.68055 13.8694 6.36667 14 6 14L3.33333 14C2.96667 14 2.65278 13.8694 2.39167 13.6083C2.13056 13.3472 2 13.0333 2 12.6667L2 3.33333C2 2.96667 2.13056 2.65278 2.39167 2.39167C2.65278 2.13055 2.96667 2 3.33333 2L6 2ZM3.33333 12.6667L6 12.6667L6 3.33333L3.33333 3.33333L3.33333 12.6667Z", fill: "currentColor" }) })] }) }));
|
|
358
|
+
if (!effectiveVertical) {
|
|
359
|
+
return (_jsx(StepHorizontalView, { ref: containerRef, blockId: block.id, stepNumber: stepNumber, stepValue: combinedStepValue, expectedResult: expectedResult, onStepChange: handleCombinedStepChange, onExpectedChange: handleExpectedChange, onInsertNextStep: handleInsertNextStep, onFieldFocus: handleFieldFocus, viewToggle: viewToggleButton }));
|
|
342
360
|
}
|
|
343
|
-
return (_jsxs("div", { className: "bn-teststep", "data-block-id": block.id, children: [_jsxs("div", { className: "bn-teststep__timeline", children: [_jsx("span", { className: "bn-teststep__number", children: stepNumber }), _jsx("div", { className: "bn-teststep__line" })] }), _jsxs("div", { className: "bn-teststep__content", children: [_jsxs("div", { className: "bn-teststep__header", children: [_jsx("span", { className: "bn-teststep__title", children: "Step" }),
|
|
361
|
+
return (_jsxs("div", { className: "bn-teststep", "data-block-id": block.id, ref: containerRef, children: [_jsxs("div", { className: "bn-teststep__timeline", children: [_jsx("span", { className: "bn-teststep__number", children: stepNumber }), _jsx("div", { className: "bn-teststep__line" })] }), _jsxs("div", { className: "bn-teststep__content", children: [_jsxs("div", { className: "bn-teststep__header", children: [_jsx("span", { className: "bn-teststep__title", children: "Step" }), viewToggleButton] }), _jsx(StepField, { label: "Step", showLabel: false, value: stepTitle, placeholder: STEP_TITLE_PLACEHOLDER, onChange: handleStepTitleChange, autoFocus: stepTitle.length === 0, enableAutocomplete: true, fieldName: "title", suggestionFilter: (suggestion) => suggestion.isSnippet !== true, onFieldFocus: handleFieldFocus, enableImageUpload: false, showFormattingButtons: true, onImageFile: async (file) => {
|
|
344
362
|
if (!uploadImage) {
|
|
345
363
|
return;
|
|
346
364
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
2
|
type StepHorizontalViewProps = {
|
|
3
3
|
blockId: string;
|
|
4
4
|
stepNumber: number;
|
|
@@ -10,5 +10,5 @@ type StepHorizontalViewProps = {
|
|
|
10
10
|
onFieldFocus: () => void;
|
|
11
11
|
viewToggle?: ReactNode;
|
|
12
12
|
};
|
|
13
|
-
export declare
|
|
13
|
+
export declare const StepHorizontalView: import("react").ForwardRefExoticComponent<StepHorizontalViewProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
14
14
|
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
2
3
|
import { StepField } from "./stepField";
|
|
3
4
|
const STEP_PLACEHOLDER = "Enter step name";
|
|
4
5
|
const EXPECTED_RESULT_PLACEHOLDER = "Enter expected result";
|
|
5
|
-
export function StepHorizontalView({ blockId, stepNumber, stepValue, expectedResult, onStepChange, onExpectedChange, onInsertNextStep, onFieldFocus, viewToggle, }) {
|
|
6
|
-
return (_jsxs("div", { className: "bn-teststep bn-teststep--horizontal", "data-block-id": blockId, children: [_jsxs("div", { className: "bn-teststep__timeline", children: [_jsx("span", { className: "bn-teststep__number", children: stepNumber }), _jsx("div", { className: "bn-teststep__line" })] }), _jsxs("div", { className: "bn-teststep__content", children: [_jsxs("div", { className: "bn-teststep__horizontal-fields", children: [_jsx("div", { className: "bn-teststep__horizontal-col", children: _jsx(StepField, { label: "Step", value: stepValue, onChange: onStepChange, placeholder: STEP_PLACEHOLDER, enableAutocomplete: true, fieldName: "title", suggestionFilter: (suggestion) => suggestion.isSnippet !== true, onFieldFocus: onFieldFocus, multiline: true, enableImageUpload: true, showFormattingButtons: true, showImageButton: true }) }), _jsx("div", { className: "bn-teststep__horizontal-col", children: _jsx(StepField, { label: "Expected result", labelAction: viewToggle, value: expectedResult, onChange: onExpectedChange, placeholder: EXPECTED_RESULT_PLACEHOLDER, multiline: true, enableAutocomplete: true, enableImageUpload: true, showFormattingButtons: true, showImageButton: true, onFieldFocus: onFieldFocus }) })] }), _jsx("div", { className: "bn-step-actions", children: _jsxs("button", { type: "button", className: "bn-step-action-btn", onClick: onInsertNextStep, children: [_jsx("svg", { className: "bn-step-action-btn__icon", width: "16", height: "16", viewBox: "0 0 13.334 13.334", fill: "none", "aria-hidden": "true", children: _jsx("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" }) }), "Add new step"] }) })] })] }));
|
|
7
|
-
}
|
|
6
|
+
export const StepHorizontalView = forwardRef(function StepHorizontalView({ blockId, stepNumber, stepValue, expectedResult, onStepChange, onExpectedChange, onInsertNextStep, onFieldFocus, viewToggle, }, ref) {
|
|
7
|
+
return (_jsxs("div", { className: "bn-teststep bn-teststep--horizontal", "data-block-id": blockId, ref: ref, children: [_jsxs("div", { className: "bn-teststep__timeline", children: [_jsx("span", { className: "bn-teststep__number", children: stepNumber }), _jsx("div", { className: "bn-teststep__line" })] }), _jsxs("div", { className: "bn-teststep__content", children: [_jsxs("div", { className: "bn-teststep__horizontal-fields", children: [_jsx("div", { className: "bn-teststep__horizontal-col", children: _jsx(StepField, { label: "Step", value: stepValue, onChange: onStepChange, placeholder: STEP_PLACEHOLDER, enableAutocomplete: true, fieldName: "title", suggestionFilter: (suggestion) => suggestion.isSnippet !== true, onFieldFocus: onFieldFocus, multiline: true, enableImageUpload: true, showFormattingButtons: true, showImageButton: true }) }), _jsx("div", { className: "bn-teststep__horizontal-col", children: _jsx(StepField, { label: "Expected result", labelAction: viewToggle, value: expectedResult, onChange: onExpectedChange, placeholder: EXPECTED_RESULT_PLACEHOLDER, multiline: true, enableAutocomplete: true, enableImageUpload: true, showFormattingButtons: true, showImageButton: true, onFieldFocus: onFieldFocus }) })] }), _jsx("div", { className: "bn-step-actions", children: _jsxs("button", { type: "button", className: "bn-step-action-btn", onClick: onInsertNextStep, children: [_jsx("svg", { className: "bn-step-action-btn__icon", width: "16", height: "16", viewBox: "0 0 13.334 13.334", fill: "none", "aria-hidden": "true", children: _jsx("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" }) }), "Add new step"] }) })] })] }));
|
|
8
|
+
});
|
|
@@ -80,15 +80,20 @@ function unescapeMarkdown(text) {
|
|
|
80
80
|
return stripHtmlWrappers(text).replace(/\\([*_`~\[\]()<>\\])/g, "$1").replace(/\\>/g, ">");
|
|
81
81
|
}
|
|
82
82
|
function applyTextStyles(text, styles) {
|
|
83
|
+
var _a, _b, _c, _d;
|
|
83
84
|
if (!styles) {
|
|
84
85
|
return text;
|
|
85
86
|
}
|
|
86
87
|
const hasCode = styles.code === true;
|
|
87
88
|
let result = text;
|
|
88
89
|
if (hasCode) {
|
|
89
|
-
|
|
90
|
+
const leadingWs = (_b = (_a = result.match(/^(\s*)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : "";
|
|
91
|
+
const trailingWs = (_d = (_c = result.match(/(\s*)$/)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : "";
|
|
92
|
+
const trimmed = result.slice(leadingWs.length, result.length - trailingWs.length || undefined);
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
return result;
|
|
90
95
|
// Code style supersedes other styles in Markdown.
|
|
91
|
-
return
|
|
96
|
+
return leadingWs + "`" + trimmed.replace(/`/g, "\\`") + "`" + trailingWs;
|
|
92
97
|
}
|
|
93
98
|
const wrappers = [];
|
|
94
99
|
if (styles.bold) {
|
|
@@ -120,15 +125,20 @@ function applyTextStyles(text, styles) {
|
|
|
120
125
|
// trapped inside markers like **bold<br/>text**.
|
|
121
126
|
const segments = result.split("\n");
|
|
122
127
|
const wrapped = segments.map((segment) => {
|
|
123
|
-
var _a;
|
|
128
|
+
var _a, _b, _c, _d, _e;
|
|
124
129
|
if (!segment)
|
|
125
130
|
return segment;
|
|
126
|
-
|
|
131
|
+
const leadingWs = (_b = (_a = segment.match(/^(\s*)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : "";
|
|
132
|
+
const trailingWs = (_d = (_c = segment.match(/(\s*)$/)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : "";
|
|
133
|
+
const trimmed = segment.slice(leadingWs.length, segment.length - trailingWs.length || undefined);
|
|
134
|
+
if (!trimmed)
|
|
135
|
+
return segment;
|
|
136
|
+
let s = trimmed;
|
|
127
137
|
for (const wrapper of wrappers) {
|
|
128
|
-
const suffix = (
|
|
138
|
+
const suffix = (_e = wrapper.suffix) !== null && _e !== void 0 ? _e : wrapper.prefix;
|
|
129
139
|
s = `${wrapper.prefix}${s}${suffix}`;
|
|
130
140
|
}
|
|
131
|
-
return s;
|
|
141
|
+
return leadingWs + s + trailingWs;
|
|
132
142
|
});
|
|
133
143
|
return wrapped.join("\n");
|
|
134
144
|
}
|
|
@@ -828,11 +838,18 @@ function parseTestStep(lines, index, allowEmpty = false, snippetId) {
|
|
|
828
838
|
// Check for expected result labels with different formatting
|
|
829
839
|
const expectedMatch = rawTrimmed.match(EXPECTED_LABEL_REGEX);
|
|
830
840
|
const expectedStarMatch = rawTrimmed.match(/^\*expected\s*\*:\s*(.*)$/i) ||
|
|
831
|
-
rawTrimmed.match(/^\*expected\*:\s*(.*)$/i)
|
|
841
|
+
rawTrimmed.match(/^\*expected\*:\s*(.*)$/i) ||
|
|
842
|
+
rawTrimmed.match(/^\*{1,2}expected\s*:\*{1,2}\s*(.*)$/i);
|
|
832
843
|
if (expectedMatch || expectedStarMatch) {
|
|
833
844
|
inExpectedResult = true;
|
|
834
|
-
|
|
835
|
-
let content
|
|
845
|
+
// Prefer the star match (more specific about formatting) to avoid leaking markers
|
|
846
|
+
let content;
|
|
847
|
+
if (expectedStarMatch) {
|
|
848
|
+
content = (expectedStarMatch[1] || '').trim();
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
content = rawTrimmed.slice(expectedMatch[0].length).trim();
|
|
852
|
+
}
|
|
836
853
|
// Add the content (if any) from this line
|
|
837
854
|
if (content) {
|
|
838
855
|
const expectedContent = unescapeMarkdown(content);
|
package/package/styles.css
CHANGED
|
@@ -504,6 +504,11 @@ html.dark .bn-step-image-preview__content {
|
|
|
504
504
|
transform: rotate(90deg);
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
+
.bn-teststep__view-toggle--disabled {
|
|
508
|
+
cursor: not-allowed;
|
|
509
|
+
opacity: 0.3 !important;
|
|
510
|
+
}
|
|
511
|
+
|
|
507
512
|
.bn-teststep__horizontal-fields {
|
|
508
513
|
display: flex;
|
|
509
514
|
gap: 16px;
|
|
@@ -521,11 +526,6 @@ html.dark .bn-step-image-preview__content {
|
|
|
521
526
|
min-height: 28px;
|
|
522
527
|
}
|
|
523
528
|
|
|
524
|
-
@media (max-width: 860px) {
|
|
525
|
-
.bn-teststep__horizontal-fields {
|
|
526
|
-
flex-direction: column;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
529
|
|
|
530
530
|
.bn-snippet .bn-step-field__input {
|
|
531
531
|
border-color: var(--snippet-border-light);
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createReactBlockSpec, useEditorChange } from "@blocknote/react";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { StepField } from "./stepField";
|
|
4
4
|
import { StepHorizontalView } from "./stepHorizontalView";
|
|
5
5
|
import { useStepImageUpload } from "../stepImageUpload";
|
|
@@ -11,6 +11,7 @@ const STEP_TITLE_PLACEHOLDER = "Enter step title...";
|
|
|
11
11
|
const STEP_DATA_PLACEHOLDER = "Enter step data...";
|
|
12
12
|
const EXPECTED_RESULT_PLACEHOLDER = "Enter expected result...";
|
|
13
13
|
type StepViewMode = "vertical" | "horizontal";
|
|
14
|
+
const FORCE_VERTICAL_WIDTH = 550;
|
|
14
15
|
|
|
15
16
|
/* readExpectedCollapsedPreference removed — currently unused */
|
|
16
17
|
|
|
@@ -191,6 +192,22 @@ export const stepBlock = createReactBlockSpec(
|
|
|
191
192
|
const [documentVersion, setDocumentVersion] = useState(0);
|
|
192
193
|
const uploadImage = useStepImageUpload();
|
|
193
194
|
const [viewMode, setViewMode] = useState<StepViewMode>(() => readStepViewMode());
|
|
195
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
196
|
+
const [forceVertical, setForceVertical] = useState(false);
|
|
197
|
+
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
const el = containerRef.current?.parentElement;
|
|
200
|
+
if (!el) return;
|
|
201
|
+
const observer = new ResizeObserver((entries) => {
|
|
202
|
+
for (const entry of entries) {
|
|
203
|
+
setForceVertical(entry.contentRect.width < FORCE_VERTICAL_WIDTH);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
observer.observe(el);
|
|
207
|
+
return () => observer.disconnect();
|
|
208
|
+
}, []);
|
|
209
|
+
|
|
210
|
+
const effectiveVertical = forceVertical || viewMode === "vertical";
|
|
194
211
|
|
|
195
212
|
// Calculate step number based on position in document
|
|
196
213
|
const stepNumber = useMemo(() => {
|
|
@@ -391,9 +408,30 @@ export const stepBlock = createReactBlockSpec(
|
|
|
391
408
|
const canToggleData = !dataHasContent;
|
|
392
409
|
const canToggleExpected = !expectedHasContent;
|
|
393
410
|
|
|
394
|
-
|
|
411
|
+
const viewToggleButton = (
|
|
412
|
+
<button
|
|
413
|
+
type="button"
|
|
414
|
+
className={`bn-teststep__view-toggle${!effectiveVertical ? " bn-teststep__view-toggle--horizontal" : ""}${forceVertical ? " bn-teststep__view-toggle--disabled" : ""}`}
|
|
415
|
+
data-tooltip={forceVertical ? "Not enough space for horizontal view" : "Switch step view"}
|
|
416
|
+
aria-label={forceVertical ? "Not enough space for horizontal view" : "Switch step view"}
|
|
417
|
+
onClick={forceVertical ? undefined : handleToggleView}
|
|
418
|
+
aria-disabled={forceVertical}
|
|
419
|
+
>
|
|
420
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
421
|
+
<mask id="mask-toggle" style={{maskType: "alpha"}} maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
|
422
|
+
<rect width="16" height="16" fill="#D9D9D9"/>
|
|
423
|
+
</mask>
|
|
424
|
+
<g mask="url(#mask-toggle)">
|
|
425
|
+
<path d="M12.6667 2C13.0333 2 13.3472 2.13056 13.6083 2.39167C13.8694 2.65278 14 2.96667 14 3.33333L14 12.6667C14 13.0333 13.8694 13.3472 13.6083 13.6083C13.3472 13.8694 13.0333 14 12.6667 14L10 14C9.63333 14 9.31944 13.8694 9.05833 13.6083C8.79722 13.3472 8.66667 13.0333 8.66667 12.6667L8.66667 3.33333C8.66667 2.96667 8.79722 2.65278 9.05833 2.39167C9.31945 2.13055 9.63333 2 10 2L12.6667 2ZM6 2C6.36667 2 6.68056 2.13055 6.94167 2.39167C7.20278 2.65278 7.33333 2.96667 7.33333 3.33333L7.33333 12.6667C7.33333 13.0333 7.20278 13.3472 6.94167 13.6083C6.68055 13.8694 6.36667 14 6 14L3.33333 14C2.96667 14 2.65278 13.8694 2.39167 13.6083C2.13056 13.3472 2 13.0333 2 12.6667L2 3.33333C2 2.96667 2.13056 2.65278 2.39167 2.39167C2.65278 2.13055 2.96667 2 3.33333 2L6 2ZM3.33333 12.6667L6 12.6667L6 3.33333L3.33333 3.33333L3.33333 12.6667Z" fill="currentColor"/>
|
|
426
|
+
</g>
|
|
427
|
+
</svg>
|
|
428
|
+
</button>
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
if (!effectiveVertical) {
|
|
395
432
|
return (
|
|
396
433
|
<StepHorizontalView
|
|
434
|
+
ref={containerRef}
|
|
397
435
|
blockId={block.id}
|
|
398
436
|
stepNumber={stepNumber}
|
|
399
437
|
stepValue={combinedStepValue}
|
|
@@ -402,30 +440,13 @@ export const stepBlock = createReactBlockSpec(
|
|
|
402
440
|
onExpectedChange={handleExpectedChange}
|
|
403
441
|
onInsertNextStep={handleInsertNextStep}
|
|
404
442
|
onFieldFocus={handleFieldFocus}
|
|
405
|
-
viewToggle={
|
|
406
|
-
<button
|
|
407
|
-
type="button"
|
|
408
|
-
className="bn-teststep__view-toggle bn-teststep__view-toggle--horizontal"
|
|
409
|
-
data-tooltip="Switch step view"
|
|
410
|
-
aria-label="Switch step view"
|
|
411
|
-
onClick={handleToggleView}
|
|
412
|
-
>
|
|
413
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
414
|
-
<mask id="mask-toggle" style={{maskType: "alpha"}} maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
|
415
|
-
<rect width="16" height="16" fill="#D9D9D9"/>
|
|
416
|
-
</mask>
|
|
417
|
-
<g mask="url(#mask-toggle)">
|
|
418
|
-
<path d="M12.6667 2C13.0333 2 13.3472 2.13056 13.6083 2.39167C13.8694 2.65278 14 2.96667 14 3.33333L14 12.6667C14 13.0333 13.8694 13.3472 13.6083 13.6083C13.3472 13.8694 13.0333 14 12.6667 14L10 14C9.63333 14 9.31944 13.8694 9.05833 13.6083C8.79722 13.3472 8.66667 13.0333 8.66667 12.6667L8.66667 3.33333C8.66667 2.96667 8.79722 2.65278 9.05833 2.39167C9.31945 2.13055 9.63333 2 10 2L12.6667 2ZM6 2C6.36667 2 6.68056 2.13055 6.94167 2.39167C7.20278 2.65278 7.33333 2.96667 7.33333 3.33333L7.33333 12.6667C7.33333 13.0333 7.20278 13.3472 6.94167 13.6083C6.68055 13.8694 6.36667 14 6 14L3.33333 14C2.96667 14 2.65278 13.8694 2.39167 13.6083C2.13056 13.3472 2 13.0333 2 12.6667L2 3.33333C2 2.96667 2.13056 2.65278 2.39167 2.39167C2.65278 2.13055 2.96667 2 3.33333 2L6 2ZM3.33333 12.6667L6 12.6667L6 3.33333L3.33333 3.33333L3.33333 12.6667Z" fill="currentColor"/>
|
|
419
|
-
</g>
|
|
420
|
-
</svg>
|
|
421
|
-
</button>
|
|
422
|
-
}
|
|
443
|
+
viewToggle={viewToggleButton}
|
|
423
444
|
/>
|
|
424
445
|
);
|
|
425
446
|
}
|
|
426
447
|
|
|
427
448
|
return (
|
|
428
|
-
<div className="bn-teststep" data-block-id={block.id}>
|
|
449
|
+
<div className="bn-teststep" data-block-id={block.id} ref={containerRef}>
|
|
429
450
|
<div className="bn-teststep__timeline">
|
|
430
451
|
<span className="bn-teststep__number">{stepNumber}</span>
|
|
431
452
|
<div className="bn-teststep__line" />
|
|
@@ -433,22 +454,7 @@ export const stepBlock = createReactBlockSpec(
|
|
|
433
454
|
<div className="bn-teststep__content">
|
|
434
455
|
<div className="bn-teststep__header">
|
|
435
456
|
<span className="bn-teststep__title">Step</span>
|
|
436
|
-
|
|
437
|
-
type="button"
|
|
438
|
-
className="bn-teststep__view-toggle"
|
|
439
|
-
data-tooltip="Switch step view"
|
|
440
|
-
aria-label="Switch step view"
|
|
441
|
-
onClick={handleToggleView}
|
|
442
|
-
>
|
|
443
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
444
|
-
<mask id="mask-toggle" style={{maskType: "alpha"}} maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
|
445
|
-
<rect width="16" height="16" fill="#D9D9D9"/>
|
|
446
|
-
</mask>
|
|
447
|
-
<g mask="url(#mask-toggle)">
|
|
448
|
-
<path d="M12.6667 2C13.0333 2 13.3472 2.13056 13.6083 2.39167C13.8694 2.65278 14 2.96667 14 3.33333L14 12.6667C14 13.0333 13.8694 13.3472 13.6083 13.6083C13.3472 13.8694 13.0333 14 12.6667 14L10 14C9.63333 14 9.31944 13.8694 9.05833 13.6083C8.79722 13.3472 8.66667 13.0333 8.66667 12.6667L8.66667 3.33333C8.66667 2.96667 8.79722 2.65278 9.05833 2.39167C9.31945 2.13055 9.63333 2 10 2L12.6667 2ZM6 2C6.36667 2 6.68056 2.13055 6.94167 2.39167C7.20278 2.65278 7.33333 2.96667 7.33333 3.33333L7.33333 12.6667C7.33333 13.0333 7.20278 13.3472 6.94167 13.6083C6.68055 13.8694 6.36667 14 6 14L3.33333 14C2.96667 14 2.65278 13.8694 2.39167 13.6083C2.13056 13.3472 2 13.0333 2 12.6667L2 3.33333C2 2.96667 2.13056 2.65278 2.39167 2.39167C2.65278 2.13055 2.96667 2 3.33333 2L6 2ZM3.33333 12.6667L6 12.6667L6 3.33333L3.33333 3.33333L3.33333 12.6667Z" fill="currentColor"/>
|
|
449
|
-
</g>
|
|
450
|
-
</svg>
|
|
451
|
-
</button>
|
|
457
|
+
{viewToggleButton}
|
|
452
458
|
</div>
|
|
453
459
|
<StepField
|
|
454
460
|
label="Step"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { forwardRef, type ReactNode } from "react";
|
|
2
2
|
import { StepField } from "./stepField";
|
|
3
3
|
import type { StepSuggestion } from "../stepAutocomplete";
|
|
4
4
|
|
|
@@ -17,7 +17,7 @@ type StepHorizontalViewProps = {
|
|
|
17
17
|
viewToggle?: ReactNode;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
export function StepHorizontalView({
|
|
20
|
+
export const StepHorizontalView = forwardRef<HTMLDivElement, StepHorizontalViewProps>(function StepHorizontalView({
|
|
21
21
|
blockId,
|
|
22
22
|
stepNumber,
|
|
23
23
|
stepValue,
|
|
@@ -27,9 +27,9 @@ export function StepHorizontalView({
|
|
|
27
27
|
onInsertNextStep,
|
|
28
28
|
onFieldFocus,
|
|
29
29
|
viewToggle,
|
|
30
|
-
}
|
|
30
|
+
}, ref) {
|
|
31
31
|
return (
|
|
32
|
-
<div className="bn-teststep bn-teststep--horizontal" data-block-id={blockId}>
|
|
32
|
+
<div className="bn-teststep bn-teststep--horizontal" data-block-id={blockId} ref={ref}>
|
|
33
33
|
<div className="bn-teststep__timeline">
|
|
34
34
|
<span className="bn-teststep__number">{stepNumber}</span>
|
|
35
35
|
<div className="bn-teststep__line" />
|
|
@@ -79,4 +79,4 @@ export function StepHorizontalView({
|
|
|
79
79
|
</div>
|
|
80
80
|
</div>
|
|
81
81
|
);
|
|
82
|
-
}
|
|
82
|
+
});
|
|
@@ -46,6 +46,73 @@ describe("blocksToMarkdown", () => {
|
|
|
46
46
|
expect(blocksToMarkdown(blocks)).toBe("Hello **world***!*");
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
it("places bold markers outside leading/trailing spaces", () => {
|
|
50
|
+
const blocks: CustomEditorBlock[] = [
|
|
51
|
+
{
|
|
52
|
+
id: "1",
|
|
53
|
+
type: "paragraph",
|
|
54
|
+
props: baseProps,
|
|
55
|
+
content: [
|
|
56
|
+
{ type: "text", text: "some ", styles: {} },
|
|
57
|
+
{ type: "text", text: " bold ", styles: { bold: true } },
|
|
58
|
+
{ type: "text", text: " text", styles: {} },
|
|
59
|
+
],
|
|
60
|
+
children: [],
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
expect(blocksToMarkdown(blocks)).toBe("some **bold** text");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("places italic markers outside trailing space", () => {
|
|
67
|
+
const blocks: CustomEditorBlock[] = [
|
|
68
|
+
{
|
|
69
|
+
id: "1",
|
|
70
|
+
type: "paragraph",
|
|
71
|
+
props: baseProps,
|
|
72
|
+
content: [
|
|
73
|
+
{ type: "text", text: "word ", styles: { italic: true } },
|
|
74
|
+
{ type: "text", text: "next", styles: {} },
|
|
75
|
+
],
|
|
76
|
+
children: [],
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
expect(blocksToMarkdown(blocks)).toBe("*word* next");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("places code backticks outside leading/trailing spaces", () => {
|
|
83
|
+
const blocks: CustomEditorBlock[] = [
|
|
84
|
+
{
|
|
85
|
+
id: "1",
|
|
86
|
+
type: "paragraph",
|
|
87
|
+
props: baseProps,
|
|
88
|
+
content: [
|
|
89
|
+
{ type: "text", text: "see ", styles: {} },
|
|
90
|
+
{ type: "text", text: " code ", styles: { code: true } },
|
|
91
|
+
{ type: "text", text: " here", styles: {} },
|
|
92
|
+
],
|
|
93
|
+
children: [],
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
expect(blocksToMarkdown(blocks)).toBe("see `code` here");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns all-whitespace text unformatted", () => {
|
|
100
|
+
const blocks: CustomEditorBlock[] = [
|
|
101
|
+
{
|
|
102
|
+
id: "1",
|
|
103
|
+
type: "paragraph",
|
|
104
|
+
props: baseProps,
|
|
105
|
+
content: [
|
|
106
|
+
{ type: "text", text: "a", styles: {} },
|
|
107
|
+
{ type: "text", text: " ", styles: { bold: true } },
|
|
108
|
+
{ type: "text", text: "b", styles: {} },
|
|
109
|
+
],
|
|
110
|
+
children: [],
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
expect(blocksToMarkdown(blocks)).toBe("a b");
|
|
114
|
+
});
|
|
115
|
+
|
|
49
116
|
it("serializes a numbered list", () => {
|
|
50
117
|
const blocks: CustomEditorBlock[] = [
|
|
51
118
|
{
|
|
@@ -1048,7 +1115,7 @@ describe("markdownToBlocks", () => {
|
|
|
1048
1115
|
props: {
|
|
1049
1116
|
stepTitle: "Open the form.",
|
|
1050
1117
|
stepData: "",
|
|
1051
|
-
expectedResult: "
|
|
1118
|
+
expectedResult: "The form opens.\nFields are empty.",
|
|
1052
1119
|
listStyle: "bullet",
|
|
1053
1120
|
},
|
|
1054
1121
|
children: [],
|
|
@@ -1212,7 +1279,7 @@ describe("markdownToBlocks", () => {
|
|
|
1212
1279
|
stepTitle:
|
|
1213
1280
|
"Navigate to More tab -≻ My Profile -≻ Log into the app with user from preconditions",
|
|
1214
1281
|
stepData: "",
|
|
1215
|
-
expectedResult: "
|
|
1282
|
+
expectedResult: "Upsell SS screen is displayed",
|
|
1216
1283
|
listStyle: "bullet",
|
|
1217
1284
|
},
|
|
1218
1285
|
children: [],
|
|
@@ -1222,7 +1289,7 @@ describe("markdownToBlocks", () => {
|
|
|
1222
1289
|
props: {
|
|
1223
1290
|
stepTitle: "Close SS",
|
|
1224
1291
|
stepData: "",
|
|
1225
|
-
expectedResult: "
|
|
1292
|
+
expectedResult: "My Course and More tab are displayed",
|
|
1226
1293
|
listStyle: "bullet",
|
|
1227
1294
|
},
|
|
1228
1295
|
children: [],
|
|
@@ -1742,7 +1809,7 @@ describe("markdownToBlocks", () => {
|
|
|
1742
1809
|
props: {
|
|
1743
1810
|
stepTitle: "Check UI of Sleep score info screen",
|
|
1744
1811
|
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.",
|
|
1745
|
-
expectedResult: "
|
|
1812
|
+
expectedResult: "- 1st block:\n- 2nd block:\n- 3d block:",
|
|
1746
1813
|
listStyle: "bullet",
|
|
1747
1814
|
},
|
|
1748
1815
|
children: [],
|
|
@@ -137,9 +137,12 @@ function applyTextStyles(text: string, styles: EditorStyles | undefined): string
|
|
|
137
137
|
let result = text;
|
|
138
138
|
|
|
139
139
|
if (hasCode) {
|
|
140
|
-
|
|
140
|
+
const leadingWs = result.match(/^(\s*)/)?.[1] ?? "";
|
|
141
|
+
const trailingWs = result.match(/(\s*)$/)?.[1] ?? "";
|
|
142
|
+
const trimmed = result.slice(leadingWs.length, result.length - trailingWs.length || undefined);
|
|
143
|
+
if (!trimmed) return result;
|
|
141
144
|
// Code style supersedes other styles in Markdown.
|
|
142
|
-
return
|
|
145
|
+
return leadingWs + "`" + trimmed.replace(/`/g, "\\`") + "`" + trailingWs;
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
const wrappers: Array<{ prefix: string; suffix?: string }> = [];
|
|
@@ -180,12 +183,16 @@ function applyTextStyles(text: string, styles: EditorStyles | undefined): string
|
|
|
180
183
|
const segments = result.split("\n");
|
|
181
184
|
const wrapped = segments.map((segment) => {
|
|
182
185
|
if (!segment) return segment;
|
|
183
|
-
|
|
186
|
+
const leadingWs = segment.match(/^(\s*)/)?.[1] ?? "";
|
|
187
|
+
const trailingWs = segment.match(/(\s*)$/)?.[1] ?? "";
|
|
188
|
+
const trimmed = segment.slice(leadingWs.length, segment.length - trailingWs.length || undefined);
|
|
189
|
+
if (!trimmed) return segment;
|
|
190
|
+
let s = trimmed;
|
|
184
191
|
for (const wrapper of wrappers) {
|
|
185
192
|
const suffix = wrapper.suffix ?? wrapper.prefix;
|
|
186
193
|
s = `${wrapper.prefix}${s}${suffix}`;
|
|
187
194
|
}
|
|
188
|
-
return s;
|
|
195
|
+
return leadingWs + s + trailingWs;
|
|
189
196
|
});
|
|
190
197
|
|
|
191
198
|
return wrapped.join("\n");
|
|
@@ -1004,12 +1011,18 @@ function parseTestStep(
|
|
|
1004
1011
|
// Check for expected result labels with different formatting
|
|
1005
1012
|
const expectedMatch = rawTrimmed.match(EXPECTED_LABEL_REGEX);
|
|
1006
1013
|
const expectedStarMatch = rawTrimmed.match(/^\*expected\s*\*:\s*(.*)$/i) ||
|
|
1007
|
-
rawTrimmed.match(/^\*expected\*:\s*(.*)$/i)
|
|
1014
|
+
rawTrimmed.match(/^\*expected\*:\s*(.*)$/i) ||
|
|
1015
|
+
rawTrimmed.match(/^\*{1,2}expected\s*:\*{1,2}\s*(.*)$/i);
|
|
1008
1016
|
|
|
1009
1017
|
if (expectedMatch || expectedStarMatch) {
|
|
1010
1018
|
inExpectedResult = true;
|
|
1011
|
-
|
|
1012
|
-
let content
|
|
1019
|
+
// Prefer the star match (more specific about formatting) to avoid leaking markers
|
|
1020
|
+
let content: string;
|
|
1021
|
+
if (expectedStarMatch) {
|
|
1022
|
+
content = (expectedStarMatch[1] || '').trim();
|
|
1023
|
+
} else {
|
|
1024
|
+
content = rawTrimmed.slice(expectedMatch![0].length).trim();
|
|
1025
|
+
}
|
|
1013
1026
|
|
|
1014
1027
|
// Add the content (if any) from this line
|
|
1015
1028
|
if (content) {
|
package/src/editor/styles.css
CHANGED
|
@@ -504,6 +504,11 @@ html.dark .bn-step-image-preview__content {
|
|
|
504
504
|
transform: rotate(90deg);
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
+
.bn-teststep__view-toggle--disabled {
|
|
508
|
+
cursor: not-allowed;
|
|
509
|
+
opacity: 0.3 !important;
|
|
510
|
+
}
|
|
511
|
+
|
|
507
512
|
.bn-teststep__horizontal-fields {
|
|
508
513
|
display: flex;
|
|
509
514
|
gap: 16px;
|
|
@@ -521,11 +526,6 @@ html.dark .bn-step-image-preview__content {
|
|
|
521
526
|
min-height: 28px;
|
|
522
527
|
}
|
|
523
528
|
|
|
524
|
-
@media (max-width: 860px) {
|
|
525
|
-
.bn-teststep__horizontal-fields {
|
|
526
|
-
flex-direction: column;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
529
|
|
|
530
530
|
.bn-snippet .bn-step-field__input {
|
|
531
531
|
border-color: var(--snippet-border-light);
|