@surrealdb/ui 1.1.1 → 1.2.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/.zed/settings.json +36 -0
- package/AGENTS.md +10 -5
- package/README.md +30 -0
- package/REVIEW.md +1 -1
- package/dist/assets/0d2c2f665b0f41ed.woff2 +0 -0
- package/dist/assets/12b57c6beacdbca0.woff2 +0 -0
- package/dist/assets/23645aad5ccc2b92.woff2 +0 -0
- package/dist/assets/8bbfa6e01a9e6a0f.woff2 +0 -0
- package/dist/assets/93fc40a807be6880.woff2 +0 -0
- package/dist/assets/9c9751ca111e97c2.woff2 +0 -0
- package/dist/assets/9ff55a8a9670220d.woff2 +0 -0
- package/dist/assets/a865edea076e0166.woff2 +0 -0
- package/dist/assets/b921df26851c5aca.woff2 +0 -0
- package/dist/assets/c6a3f4e555097159.woff2 +0 -0
- package/dist/assets/c6c31cb1350b2544.woff2 +0 -0
- package/dist/fonts.css +1 -0
- package/dist/fonts.js +2 -0
- package/dist/fonts.js.map +1 -0
- package/dist/ui.css +1 -1
- package/dist/ui.d.ts +537 -523
- package/dist/ui.js +16328 -14682
- package/dist/ui.js.map +1 -1
- package/package.json +22 -24
- package/tests/_setup/e2e-helpers.tsx +169 -0
- package/tests/_setup/markdown-classes.ts +3 -0
- package/tests/_setup/portable-stories.ts +10 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-continues-an-ordered-list-when-Enter-is-pressed-at-the-end-of-a-numbered-line-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-renders-a-diagram-for-the-fenced-mermaid-block-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-renders-a-horizontal-rule-as-a-separator-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-renders-a-read-only-table-preview-when-the-table-block-is-inactive-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-shows-blockquote-text-from-the-sample-document-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-shows-fenced-TypeScript-sample-code-in-the-document-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/edits.test.tsx/MarkdownEditor---edits-auto-continues-a-bullet-list-when-Enter-is-pressed-at-the-end-of-a-list-item-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/edits.test.tsx/MarkdownEditor---edits-expands-triple-backtick-into-a-fenced-code-block-with-the-caret-on-the-empty-body-line-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/edits.test.tsx/MarkdownEditor---edits-reflects-typed-characters-in-the-underlying-document-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/heading-fold.test.tsx/MarkdownEditor---heading-folds-viewer--folds-and-unfolds-a-heading-section-via-the-margin-control-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---hybrid-widgets-clicking-a-callout-header-focuses-the-editor-and-parks-the-caret-inside-the-callout--REASONING--4--1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---hybrid-widgets-keeps-list-bullets-consistent-after-hopping-the-caret-across-items-several-times-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---hybrid-widgets-reveals-heading-marks-when-the-caret-enters-the-heading-line-and-hides-them-when-it-leaves-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---hybrid-widgets-shows-the-right-callout-title-when-moving-focus-between-consecutive-callouts-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---preview-widgets-clicking-a-callout-header-focuses-the-editor-with-the-caret-in-that-callout-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---preview-widgets-keeps-list-bullets-consistent-after-hopping-the-caret-across-items-several-times-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---preview-widgets-shows-the-right-callout-title-when-moving-focus-between-consecutive-callouts-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/jsx-block-content.test.tsx/MarkdownEditor---block-JSX-components-clicking-the-edit-source-action-selects-the-component-source-range-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/jsx-block-content.test.tsx/MarkdownEditor---block-JSX-components-renders-block-components-via-JsxBlockWidget-with-an-edit-source-action-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/jsx-block-content.test.tsx/MarkdownEditor---inline-JSX-rendering-block-content-keeps-multiple-inline-JSX-widgets-on-the-same-line-side-by-side-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/jsx-highlight.test.tsx/MarkdownEditor---JSX-attribute-highlighting-does-not-let-the-nested-HTML-parser-mis-highlight-attributes-after-an-expression-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/jsx-selection.test.tsx/MarkdownEditor---JSX-selection-reveal-shows-JSX-widget-when-inactive-and-raw-source-when-caret-is-inside-the-tag-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/media-sizing.test.tsx/MarkdownEditor---media-sizing-images-and-videos-do-not-exceed-the-editor-content-width-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/media-sizing.test.tsx/MarkdownEditor---media-sizing-matches-MarkdownViewer-image-width-in-the-split-playground-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/modes.test.tsx/MarkdownEditor---modes-standalone-MarkdownViewer-is-not-a-CodeMirror-surface-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/modes.test.tsx/MarkdownEditor---modes-toggling-the-SegmentedControl-swaps-mode-without-remounting-the-editor-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/regressions.test.tsx/MarkdownEditor---regressions-repeated-checkbox-toggles-do-not-duplicate-or-drift-task-markers-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/regressions.test.tsx/MarkdownEditor---regressions-scroll-position-is-preserved-when-the-document-is-edited--chat-92584463--1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/regressions.test.tsx/MarkdownEditor---regressions-task-checkbox-toggles-between-checked-and-unchecked-in-the-source-document-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/regressions.test.tsx/MarkdownEditor---regressions-toggle-via-checkbox-preserves-task-marker-widgets-visible-from-the-previous-caret-position-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/slash-commands.test.tsx/MarkdownEditor---slash-commands-dismisses-on-Escape-and-leaves-the-slash-as-literal-text-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/slash-commands.test.tsx/MarkdownEditor---slash-commands-highlights-the-slash-and-shows-an-Enter-command-placeholder-until-text-is-typed-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/slash-commands.test.tsx/MarkdownEditor---slash-commands-opens-a-filtered--keyboard-navigable-menu-and-inserts-boilerplate-on-Enter-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/undo-redo.test.tsx/MarkdownEditor---undo-and-redo-restores-undone-document-edits-via-redo-1.png +0 -0
- package/tests/e2e/MarkdownEditor/content-blocks.test.tsx +152 -0
- package/tests/e2e/MarkdownEditor/edits.test.tsx +111 -0
- package/tests/e2e/MarkdownEditor/heading-fold.test.tsx +44 -0
- package/tests/e2e/MarkdownEditor/hybrid-widgets.test.tsx +192 -0
- package/tests/e2e/MarkdownEditor/jsx-block-content.test.tsx +242 -0
- package/tests/e2e/MarkdownEditor/jsx-highlight.test.tsx +68 -0
- package/tests/e2e/MarkdownEditor/jsx-inline-badges.test.tsx +59 -0
- package/tests/e2e/MarkdownEditor/jsx-selection.test.tsx +43 -0
- package/tests/e2e/MarkdownEditor/link-placeholder.test.tsx +67 -0
- package/tests/e2e/MarkdownEditor/media-align.test.tsx +57 -0
- package/tests/e2e/MarkdownEditor/media-edit.test.tsx +63 -0
- package/tests/e2e/MarkdownEditor/media-sizing.test.tsx +123 -0
- package/tests/e2e/MarkdownEditor/modes.test.tsx +93 -0
- package/tests/e2e/MarkdownEditor/regressions.test.tsx +182 -0
- package/tests/e2e/MarkdownEditor/slash-commands.test.tsx +99 -0
- package/tests/e2e/MarkdownEditor/table-click.test.tsx +47 -0
- package/tests/e2e/MarkdownEditor/table-controls.test.tsx +56 -0
- package/tests/e2e/MarkdownEditor/table-format.test.tsx +41 -0
- package/tests/e2e/MarkdownEditor/undo-redo.test.tsx +38 -0
- package/tests/e2e/MarkdownViewer/__screenshots__/parity.test.tsx/MarkdownViewer---editor-parity-matches-computed-font-size-on-first-heading-1.png +0 -0
- package/tests/e2e/MarkdownViewer/__screenshots__/parity.test.tsx/MarkdownViewer---editor-parity-matches-counts-for-shared-structural-classes-1.png +0 -0
- package/tests/e2e/MarkdownViewer/__screenshots__/parity.test.tsx/MarkdownViewer---editor-parity-matches-visible-text-between-preview-editor--blurred--and-MarkdownViewer-1.png +0 -0
- package/tests/e2e/MarkdownViewer/__screenshots__/render.test.tsx/MarkdownViewer---render-exercises-shared-preview-class-names-without-mounting-CodeMirror-1.png +0 -0
- package/tests/e2e/MarkdownViewer/parity.test.tsx +190 -0
- package/tests/e2e/MarkdownViewer/render.test.tsx +35 -0
- package/tests/unit/Editor/helpers.test.ts +42 -0
- package/tests/unit/MarkdownEditor/code-info.test.ts +63 -0
- package/tests/unit/MarkdownEditor/decorations.test.ts +488 -0
- package/tests/unit/MarkdownEditor/editor-ready.test.ts +36 -0
- package/tests/unit/MarkdownEditor/html-descriptors.test.ts +94 -0
- package/tests/unit/MarkdownEditor/jsx-attr-scan.test.ts +115 -0
- package/tests/unit/MarkdownEditor/jsx-tag-grammar.test.ts +88 -0
- package/tests/unit/MarkdownEditor/list-indent.test.ts +95 -0
- package/tests/unit/MarkdownEditor/slash-commands.test.ts +213 -0
- package/tests/unit/MarkdownEditor/table-format.test.ts +83 -0
- package/tests/unit/MarkdownEditor/table.test.ts +119 -0
- package/tests/unit/MarkdownEditor/triggers.test.ts +244 -0
- package/tests/unit/MarkdownEditor/widget-store.test.ts +105 -0
- package/tests/unit/MarkdownViewer/code-title.test.tsx +62 -0
- package/tests/unit/MarkdownViewer/features.test.tsx +110 -0
- package/tests/unit/MarkdownViewer/headings.test.tsx +40 -0
- package/tests/unit/MarkdownViewer/jsx.test.tsx +211 -0
- package/tests/unit/MarkdownViewer/list-bullets.test.tsx +49 -0
- package/tests/unit/MarkdownViewer/list-code.test.tsx +65 -0
- package/tests/unit/MarkdownViewer/renderers.test.tsx +79 -0
- package/tests/unit/MarkdownViewer/runnable.test.tsx +69 -0
- package/tests/unit/MarkdownViewer/ssr.test.tsx +93 -0
- package/dist/yoopta.css +0 -1
- /package/dist/{yoopta.d.ts → fonts.d.ts} +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
|
|
2
|
+
import { composeStories } from "@storybook/react-vite";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
|
|
5
|
+
import { md } from "../../_setup/markdown-classes";
|
|
6
|
+
|
|
7
|
+
const Stories = composeStories(MarkdownStories);
|
|
8
|
+
|
|
9
|
+
describe("MarkdownEditor / heading folds", () => {
|
|
10
|
+
it("MarkdownViewer: heading fold control collapses the first section", async () => {
|
|
11
|
+
mountStory(<Stories.Viewer />);
|
|
12
|
+
await nextFrame();
|
|
13
|
+
await nextFrame();
|
|
14
|
+
|
|
15
|
+
const folds = document.querySelectorAll<HTMLButtonElement>(
|
|
16
|
+
`.${md.headingFold}[data-level="1"]`,
|
|
17
|
+
);
|
|
18
|
+
expect(folds.length).toBeGreaterThan(0);
|
|
19
|
+
|
|
20
|
+
const h1Fold = folds[0];
|
|
21
|
+
if (!h1Fold) throw new Error("expected an H1 fold control");
|
|
22
|
+
|
|
23
|
+
expect(h1Fold.getAttribute("data-folded")).toBe("false");
|
|
24
|
+
expect(h1Fold.getAttribute("aria-label")).toBe("Collapse section");
|
|
25
|
+
|
|
26
|
+
h1Fold.click();
|
|
27
|
+
await nextFrame();
|
|
28
|
+
await nextFrame();
|
|
29
|
+
|
|
30
|
+
const folded = document.querySelector<HTMLButtonElement>(
|
|
31
|
+
`.${md.headingFold}[data-level="1"]`,
|
|
32
|
+
);
|
|
33
|
+
expect(folded?.getAttribute("data-folded")).toBe("true");
|
|
34
|
+
expect(folded?.getAttribute("aria-label")).toBe("Expand section");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("preview mode does not render heading fold controls in the editor", async () => {
|
|
38
|
+
mountStory(<Stories.Editor />);
|
|
39
|
+
await getEditorView();
|
|
40
|
+
await nextFrame();
|
|
41
|
+
|
|
42
|
+
expect(document.querySelectorAll(`.${md.headingFold}`).length).toBe(0);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { EditorSelection } from "@codemirror/state";
|
|
2
|
+
import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
|
|
3
|
+
import { composeStories } from "@storybook/react-vite";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
mountStory,
|
|
7
|
+
nextFrame,
|
|
8
|
+
preparePreviewEditor,
|
|
9
|
+
scrollEditorTo,
|
|
10
|
+
userEvent,
|
|
11
|
+
} from "../../_setup/e2e-helpers";
|
|
12
|
+
import { md } from "../../_setup/markdown-classes";
|
|
13
|
+
|
|
14
|
+
const Stories = composeStories(MarkdownStories);
|
|
15
|
+
|
|
16
|
+
function findLineStart(doc: string, prefix: string): number {
|
|
17
|
+
const idx = doc.indexOf(prefix);
|
|
18
|
+
if (idx < 0) throw new Error(`prefix not found in doc: ${prefix}`);
|
|
19
|
+
return idx;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("MarkdownEditor / preview widgets", () => {
|
|
23
|
+
it("reveals heading marks when the caret enters the heading line and hides them when it leaves", async () => {
|
|
24
|
+
mountStory(<Stories.Editor />);
|
|
25
|
+
const view = await preparePreviewEditor();
|
|
26
|
+
|
|
27
|
+
// Caret far away from the H1 → marks should be hidden.
|
|
28
|
+
view.dispatch({ selection: EditorSelection.cursor(view.state.doc.length) });
|
|
29
|
+
await nextFrame();
|
|
30
|
+
expect(document.querySelectorAll(`.${md.markHidden}`).length).toBeGreaterThan(0);
|
|
31
|
+
|
|
32
|
+
// Move caret onto the H1 line → marks revealed for that block.
|
|
33
|
+
const headingStart = findLineStart(view.state.doc.toString(), "# Welcome");
|
|
34
|
+
await scrollEditorTo(view, headingStart);
|
|
35
|
+
view.dispatch({ selection: EditorSelection.cursor(headingStart + 4) });
|
|
36
|
+
await nextFrame();
|
|
37
|
+
expect(document.querySelectorAll(`.${md.markShown}`).length).toBeGreaterThan(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("keeps list bullets consistent after hopping the caret across items several times", async () => {
|
|
41
|
+
mountStory(<Stories.Editor />);
|
|
42
|
+
const view = await preparePreviewEditor();
|
|
43
|
+
const docStr = view.state.doc.toString();
|
|
44
|
+
|
|
45
|
+
const items = ["- First item", "- Second item", "- Third item"];
|
|
46
|
+
const positions = items.map((line) => findLineStart(docStr, line) + 2);
|
|
47
|
+
const listPos = findLineStart(docStr, "- First item");
|
|
48
|
+
|
|
49
|
+
// Park the caret away from the list so all three bullets render as widgets.
|
|
50
|
+
view.dispatch({ selection: EditorSelection.cursor(0) });
|
|
51
|
+
await scrollEditorTo(view, listPos);
|
|
52
|
+
await nextFrame();
|
|
53
|
+
|
|
54
|
+
let baselineBullets = 0;
|
|
55
|
+
for (let i = 0; i < 40; i++) {
|
|
56
|
+
baselineBullets = document.querySelectorAll(`.${md.listBullet}`).length;
|
|
57
|
+
if (baselineBullets >= 3) break;
|
|
58
|
+
await nextFrame();
|
|
59
|
+
}
|
|
60
|
+
expect(baselineBullets).toBeGreaterThanOrEqual(3);
|
|
61
|
+
|
|
62
|
+
// Hop between list items several times. With prefix-scoped hybrid reveal,
|
|
63
|
+
// the bullet widgets stay visible while the caret is in the item text;
|
|
64
|
+
// the pre-fix regression was: bullets stuck behind or disappeared once you
|
|
65
|
+
// moved the caret around a few times.
|
|
66
|
+
for (let pass = 0; pass < 4; pass++) {
|
|
67
|
+
for (let i = 0; i < positions.length; i++) {
|
|
68
|
+
view.dispatch({ selection: EditorSelection.cursor(positions[i] ?? 0) });
|
|
69
|
+
await nextFrame();
|
|
70
|
+
|
|
71
|
+
const bullets = document.querySelectorAll(`.${md.listBullet}`).length;
|
|
72
|
+
expect(bullets).toBe(baselineBullets);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Caret on the list mark (the `-`) drops that item's bullet widget only.
|
|
77
|
+
const dashPos = findLineStart(docStr, "- First item");
|
|
78
|
+
view.dispatch({ selection: EditorSelection.cursor(dashPos) });
|
|
79
|
+
await nextFrame();
|
|
80
|
+
expect(document.querySelectorAll(`.${md.listBullet}`).length).toBe(baselineBullets - 1);
|
|
81
|
+
|
|
82
|
+
// After leaving the list entirely all bullets must be back.
|
|
83
|
+
view.dispatch({ selection: EditorSelection.cursor(0) });
|
|
84
|
+
await nextFrame();
|
|
85
|
+
expect(document.querySelectorAll(`.${md.listBullet}`).length).toBe(baselineBullets);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("shows the right callout title when moving focus between consecutive callouts", async () => {
|
|
89
|
+
mountStory(<Stories.Editor />);
|
|
90
|
+
const view = await preparePreviewEditor();
|
|
91
|
+
const docStr = view.state.doc.toString();
|
|
92
|
+
|
|
93
|
+
const noteBody = findLineStart(docStr, "> Callouts use the GFM");
|
|
94
|
+
const warningBody = findLineStart(docStr, "> Legacy");
|
|
95
|
+
const tipBody = findLineStart(docStr, "> All systems go");
|
|
96
|
+
const calloutPos = findLineStart(docStr, "> [!note]");
|
|
97
|
+
|
|
98
|
+
// Park caret outside any callout first so all three headers render.
|
|
99
|
+
view.dispatch({ selection: EditorSelection.cursor(0) });
|
|
100
|
+
await scrollEditorTo(view, calloutPos);
|
|
101
|
+
await nextFrame();
|
|
102
|
+
|
|
103
|
+
const allHeaders = () =>
|
|
104
|
+
Array.from(document.querySelectorAll<HTMLElement>(`.${md.calloutHeader}`));
|
|
105
|
+
const headerForKind = (kind: string) =>
|
|
106
|
+
allHeaders().find((el) => el.getAttribute("data-callout-kind") === kind) ?? null;
|
|
107
|
+
|
|
108
|
+
let initial = allHeaders();
|
|
109
|
+
for (let i = 0; i < 40 && initial.length < 3; i++) {
|
|
110
|
+
await nextFrame();
|
|
111
|
+
initial = allHeaders();
|
|
112
|
+
}
|
|
113
|
+
expect(initial.length).toBe(3);
|
|
114
|
+
expect(headerForKind("note")?.textContent).toContain("Heads up");
|
|
115
|
+
expect(headerForKind("warning")?.textContent).toContain("Be careful");
|
|
116
|
+
expect(headerForKind("tip")?.textContent).toContain("Tip");
|
|
117
|
+
|
|
118
|
+
// `[!tip]` has no title, falls back to the visual label.
|
|
119
|
+
|
|
120
|
+
// Hop between the three callouts repeatedly. After each hop the
|
|
121
|
+
// header for the active callout disappears (caret on its block); the
|
|
122
|
+
// other two keep their correct titles. Bug regression: titles would
|
|
123
|
+
// swap or vanish.
|
|
124
|
+
const positions = [
|
|
125
|
+
{ pos: noteBody + 2, hidden: "note" as const },
|
|
126
|
+
{ pos: warningBody + 2, hidden: "warning" as const },
|
|
127
|
+
{ pos: tipBody + 2, hidden: "tip" as const },
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
for (let pass = 0; pass < 4; pass++) {
|
|
131
|
+
for (const { pos, hidden } of positions) {
|
|
132
|
+
view.dispatch({ selection: EditorSelection.cursor(pos) });
|
|
133
|
+
await nextFrame();
|
|
134
|
+
|
|
135
|
+
const visible = allHeaders();
|
|
136
|
+
expect(visible.length).toBe(2);
|
|
137
|
+
expect(headerForKind(hidden)).toBeNull();
|
|
138
|
+
|
|
139
|
+
if (hidden !== "note") {
|
|
140
|
+
expect(headerForKind("note")?.textContent).toContain("Heads up");
|
|
141
|
+
}
|
|
142
|
+
if (hidden !== "warning") {
|
|
143
|
+
expect(headerForKind("warning")?.textContent).toContain("Be careful");
|
|
144
|
+
}
|
|
145
|
+
if (hidden !== "tip") {
|
|
146
|
+
expect(headerForKind("tip")?.textContent).toContain("Tip");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("clicking a callout header focuses the editor with the caret in that callout", async () => {
|
|
153
|
+
mountStory(<Stories.Editor />);
|
|
154
|
+
const view = await preparePreviewEditor();
|
|
155
|
+
const docStr = view.state.doc.toString();
|
|
156
|
+
const calloutPos = findLineStart(docStr, "> [!warning]");
|
|
157
|
+
|
|
158
|
+
// Caret far away so all callout headers render as widgets.
|
|
159
|
+
view.dispatch({ selection: EditorSelection.cursor(0) });
|
|
160
|
+
await scrollEditorTo(view, calloutPos);
|
|
161
|
+
view.contentDOM.blur();
|
|
162
|
+
await nextFrame();
|
|
163
|
+
|
|
164
|
+
let warningHeader: HTMLElement | undefined;
|
|
165
|
+
for (let i = 0; i < 40; i++) {
|
|
166
|
+
warningHeader = Array.from(
|
|
167
|
+
document.querySelectorAll<HTMLElement>(`.${md.calloutHeader}`),
|
|
168
|
+
).find((el) => el.getAttribute("data-callout-kind") === "warning");
|
|
169
|
+
if (warningHeader) break;
|
|
170
|
+
await nextFrame();
|
|
171
|
+
}
|
|
172
|
+
expect(warningHeader).toBeDefined();
|
|
173
|
+
if (!warningHeader) throw new Error("warning callout header not rendered");
|
|
174
|
+
|
|
175
|
+
// Use a real pointer gesture so CodeMirror's input layer sees it
|
|
176
|
+
// (synthetic MouseEvent dispatch does not always reach CM6's
|
|
177
|
+
// contentDOM listeners through the widget's atomic range).
|
|
178
|
+
await userEvent.click(warningHeader);
|
|
179
|
+
await nextFrame();
|
|
180
|
+
await nextFrame();
|
|
181
|
+
|
|
182
|
+
const warningHeaderRange = {
|
|
183
|
+
from: docStr.indexOf("> [!warning]"),
|
|
184
|
+
to: docStr.indexOf("Be careful") + "Be careful".length,
|
|
185
|
+
};
|
|
186
|
+
expect(warningHeaderRange.from).toBeGreaterThan(-1);
|
|
187
|
+
|
|
188
|
+
const sel = view.state.selection.main;
|
|
189
|
+
expect(sel.from).toBeGreaterThanOrEqual(warningHeaderRange.from);
|
|
190
|
+
expect(sel.to).toBeLessThanOrEqual(warningHeaderRange.to + 1);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { Group, MantineProvider, Paper, Text, v8CssVariablesResolver } from "@mantine/core";
|
|
2
|
+
import type { MarkdownComponents } from "@src/lib/markdown";
|
|
3
|
+
import { MarkdownEditor } from "@src/primitives/MarkdownEditor";
|
|
4
|
+
import { MANTINE_THEME } from "@src/theme/mantine";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
|
|
7
|
+
|
|
8
|
+
function Card({ label }: { label: string }) {
|
|
9
|
+
return (
|
|
10
|
+
<Paper
|
|
11
|
+
withBorder
|
|
12
|
+
p="xs"
|
|
13
|
+
radius="sm"
|
|
14
|
+
mb="xs"
|
|
15
|
+
>
|
|
16
|
+
<Group gap="xs">
|
|
17
|
+
<Text size="sm">value =</Text>
|
|
18
|
+
<Text size="sm">{label}</Text>
|
|
19
|
+
</Group>
|
|
20
|
+
</Paper>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const inlineComponents: MarkdownComponents = {
|
|
25
|
+
Card,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const blockComponents: MarkdownComponents = {
|
|
29
|
+
Card: { component: Card, block: true },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const ONE_PER_LINE_DOC = `# Heading
|
|
33
|
+
|
|
34
|
+
<Card label="Alpha" />
|
|
35
|
+
|
|
36
|
+
<Card label="Beta" />
|
|
37
|
+
|
|
38
|
+
<Card label="Gamma" />
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
const MANY_PER_LINE_DOC = `# Heading
|
|
42
|
+
|
|
43
|
+
<Card label="Alpha" /><Card label="Beta" /><Card label="Gamma" />
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
function renderEditor(doc: string, components: MarkdownComponents) {
|
|
47
|
+
return (
|
|
48
|
+
<MantineProvider
|
|
49
|
+
theme={MANTINE_THEME}
|
|
50
|
+
cssVariablesResolver={v8CssVariablesResolver}
|
|
51
|
+
forceColorScheme="dark"
|
|
52
|
+
>
|
|
53
|
+
<Paper
|
|
54
|
+
p="md"
|
|
55
|
+
withBorder
|
|
56
|
+
>
|
|
57
|
+
<MarkdownEditor
|
|
58
|
+
mode="preview"
|
|
59
|
+
document={doc}
|
|
60
|
+
autoFocus={false}
|
|
61
|
+
jsxMode="render"
|
|
62
|
+
components={components}
|
|
63
|
+
/>
|
|
64
|
+
</Paper>
|
|
65
|
+
</MantineProvider>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function lineHeightsForInlineWidgets(): number[] {
|
|
70
|
+
const lines = Array.from(document.querySelectorAll<HTMLElement>(".cm-line"));
|
|
71
|
+
return lines
|
|
72
|
+
.filter((line) => line.querySelector('[data-md-widget="JsxInlineWidget"]'))
|
|
73
|
+
.map((line) => line.getBoundingClientRect().height);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Block replace widgets render between `.cm-line` rows, not inside them. */
|
|
77
|
+
function blockWidgetHostLine(widget: HTMLElement): HTMLElement | null {
|
|
78
|
+
let el: HTMLElement | null = widget;
|
|
79
|
+
while (el) {
|
|
80
|
+
if (el.classList.contains("cm-line")) return el;
|
|
81
|
+
el = el.parentElement;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function mountPreviewEditor(doc: string, components: MarkdownComponents) {
|
|
87
|
+
mountStory(renderEditor(doc, components));
|
|
88
|
+
const view = await getEditorView();
|
|
89
|
+
view.contentDOM.blur();
|
|
90
|
+
await nextFrame();
|
|
91
|
+
await nextFrame();
|
|
92
|
+
return view;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describe("MarkdownEditor / inline JSX rendering block content", () => {
|
|
96
|
+
it("does not stack cm-widgetBuffer images above and below the widget", async () => {
|
|
97
|
+
mountStory(renderEditor(ONE_PER_LINE_DOC, inlineComponents));
|
|
98
|
+
|
|
99
|
+
const view = await getEditorView();
|
|
100
|
+
view.contentDOM.blur();
|
|
101
|
+
await nextFrame();
|
|
102
|
+
await nextFrame();
|
|
103
|
+
|
|
104
|
+
const cardWidgets = document.querySelectorAll<HTMLElement>(
|
|
105
|
+
'[data-md-widget="JsxInlineWidget"]',
|
|
106
|
+
);
|
|
107
|
+
expect(cardWidgets.length).toBe(3);
|
|
108
|
+
|
|
109
|
+
const widgetHeights = Array.from(cardWidgets).map((w) => w.getBoundingClientRect().height);
|
|
110
|
+
const lineHeights = lineHeightsForInlineWidgets();
|
|
111
|
+
expect(lineHeights.length).toBe(3);
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < lineHeights.length; i++) {
|
|
114
|
+
const lineHeight = lineHeights[i] ?? 0;
|
|
115
|
+
const widgetHeight = widgetHeights[i] ?? 0;
|
|
116
|
+
expect(widgetHeight).toBeGreaterThan(0);
|
|
117
|
+
expect(lineHeight).toBeGreaterThan(0);
|
|
118
|
+
expect(lineHeight).toBeLessThan(widgetHeight + 20);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("keeps multiple inline JSX widgets on the same line side-by-side", async () => {
|
|
123
|
+
mountStory(renderEditor(MANY_PER_LINE_DOC, inlineComponents));
|
|
124
|
+
|
|
125
|
+
const view = await getEditorView();
|
|
126
|
+
view.contentDOM.blur();
|
|
127
|
+
await nextFrame();
|
|
128
|
+
await nextFrame();
|
|
129
|
+
|
|
130
|
+
const cardWidgets = document.querySelectorAll<HTMLElement>(
|
|
131
|
+
'[data-md-widget="JsxInlineWidget"]',
|
|
132
|
+
);
|
|
133
|
+
expect(cardWidgets.length).toBe(3);
|
|
134
|
+
|
|
135
|
+
const rects = Array.from(cardWidgets).map((w) => w.getBoundingClientRect());
|
|
136
|
+
const firstTop = rects[0]?.top ?? 0;
|
|
137
|
+
const tolerance = 4;
|
|
138
|
+
for (const r of rects) {
|
|
139
|
+
expect(Math.abs(r.top - firstTop)).toBeLessThan(tolerance);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("MarkdownEditor / block JSX components", () => {
|
|
145
|
+
it("renders block components via JsxBlockWidget with an edit-source action", async () => {
|
|
146
|
+
await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
|
|
147
|
+
|
|
148
|
+
const blockWidgets = document.querySelectorAll<HTMLElement>(
|
|
149
|
+
'[data-md-widget="JsxBlockWidget"]',
|
|
150
|
+
);
|
|
151
|
+
expect(blockWidgets.length).toBe(3);
|
|
152
|
+
expect(document.querySelectorAll('[data-md-widget="JsxInlineWidget"]').length).toBe(0);
|
|
153
|
+
|
|
154
|
+
const editButtons = document.querySelectorAll<HTMLButtonElement>(
|
|
155
|
+
'[aria-label="Edit source"]',
|
|
156
|
+
);
|
|
157
|
+
expect(editButtons.length).toBeGreaterThanOrEqual(3);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("renders block widgets between cm-lines, not inside a cm-line", async () => {
|
|
161
|
+
await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
|
|
162
|
+
|
|
163
|
+
expect(document.querySelectorAll(".jsx-block-line-host").length).toBe(0);
|
|
164
|
+
|
|
165
|
+
for (const widget of Array.from(
|
|
166
|
+
document.querySelectorAll<HTMLElement>('[data-md-widget="JsxBlockWidget"]'),
|
|
167
|
+
)) {
|
|
168
|
+
expect(blockWidgetHostLine(widget)).toBeNull();
|
|
169
|
+
const lineSibling = widget.closest(".cm-line");
|
|
170
|
+
expect(lineSibling).toBeNull();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("block widgets do not get cm-widgetBuffer spacer images", async () => {
|
|
175
|
+
await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
|
|
176
|
+
|
|
177
|
+
for (const widget of Array.from(
|
|
178
|
+
document.querySelectorAll<HTMLElement>('[data-md-widget="JsxBlockWidget"]'),
|
|
179
|
+
)) {
|
|
180
|
+
const parent = widget.parentElement;
|
|
181
|
+
expect(parent?.querySelector("img.cm-widgetBuffer")).toBeNull();
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("clicking the edit-source action selects the component source range", async () => {
|
|
186
|
+
const view = await mountPreviewEditor(ONE_PER_LINE_DOC, blockComponents);
|
|
187
|
+
|
|
188
|
+
const editButton = document.querySelector<HTMLButtonElement>('[aria-label="Edit source"]');
|
|
189
|
+
expect(editButton).toBeTruthy();
|
|
190
|
+
editButton?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
|
|
191
|
+
await nextFrame();
|
|
192
|
+
await nextFrame();
|
|
193
|
+
|
|
194
|
+
const selection = view.state.selection.main;
|
|
195
|
+
const selectedSource = view.state.sliceDoc(selection.from, selection.to);
|
|
196
|
+
expect(selectedSource.trim()).toBe('<Card label="Alpha" />');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("renders block components embedded in a mixed paragraph on a dedicated row", async () => {
|
|
200
|
+
const doc = 'Intro text <Card label="Mid" /> trailing text.';
|
|
201
|
+
await mountPreviewEditor(doc, blockComponents);
|
|
202
|
+
|
|
203
|
+
const blockWidgets = document.querySelectorAll<HTMLElement>(
|
|
204
|
+
'[data-md-widget="JsxBlockWidget"]',
|
|
205
|
+
);
|
|
206
|
+
expect(blockWidgets.length).toBe(1);
|
|
207
|
+
const midWidget = blockWidgets[0];
|
|
208
|
+
expect(midWidget?.textContent).toContain("Mid");
|
|
209
|
+
expect(midWidget && blockWidgetHostLine(midWidget)).toBeNull();
|
|
210
|
+
|
|
211
|
+
const lines = Array.from(document.querySelectorAll<HTMLElement>(".cm-line"));
|
|
212
|
+
expect(lines.some((line) => line.classList.contains("jsx-block-line-host"))).toBe(false);
|
|
213
|
+
|
|
214
|
+
const introLine = lines.find((line) => line.textContent?.includes("Intro text"));
|
|
215
|
+
const trailingLine = lines.find((line) => line.textContent?.includes("trailing text"));
|
|
216
|
+
expect(introLine).toBeTruthy();
|
|
217
|
+
expect(trailingLine).toBeTruthy();
|
|
218
|
+
expect(introLine?.style.display).not.toBe("flex");
|
|
219
|
+
expect(trailingLine?.style.display).not.toBe("flex");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("renders block HTML embedded in a mixed paragraph on a dedicated row", async () => {
|
|
223
|
+
const doc = "hello world `snippet` <div>test</div> foo bar";
|
|
224
|
+
await mountPreviewEditor(doc, blockComponents);
|
|
225
|
+
|
|
226
|
+
const blockWidgets = document.querySelectorAll<HTMLElement>(
|
|
227
|
+
'[data-md-widget="JsxBlockWidget"]',
|
|
228
|
+
);
|
|
229
|
+
expect(blockWidgets.length).toBe(1);
|
|
230
|
+
const divWidget = blockWidgets[0];
|
|
231
|
+
expect(divWidget?.textContent).toContain("test");
|
|
232
|
+
expect(divWidget && blockWidgetHostLine(divWidget)).toBeNull();
|
|
233
|
+
|
|
234
|
+
const lines = Array.from(document.querySelectorAll<HTMLElement>(".cm-line"));
|
|
235
|
+
const helloLine = lines.find((line) => line.textContent?.includes("hello world"));
|
|
236
|
+
const fooLine = lines.find((line) => line.textContent?.includes("foo bar"));
|
|
237
|
+
expect(helloLine).toBeTruthy();
|
|
238
|
+
expect(fooLine).toBeTruthy();
|
|
239
|
+
expect(helloLine?.classList.contains("jsx-block-line-host")).toBe(false);
|
|
240
|
+
expect(fooLine?.classList.contains("jsx-block-line-host")).toBe(false);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { MantineProvider, Paper, v8CssVariablesResolver } from "@mantine/core";
|
|
2
|
+
import { MarkdownEditor } from "@src/primitives/MarkdownEditor";
|
|
3
|
+
import { MANTINE_THEME } from "@src/theme/mantine";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
|
|
6
|
+
|
|
7
|
+
const TAG = '<Since v={"2.0.0"} t="true" b="false" />';
|
|
8
|
+
|
|
9
|
+
/** Dark-theme string green from {@link VIVID_THEME}. */
|
|
10
|
+
const STRING_COLOR = "rgb(0, 255, 110)";
|
|
11
|
+
/** Dark-theme property name from {@link VIVID_THEME}. */
|
|
12
|
+
const PROPERTY_COLOR = "rgb(224, 108, 117)";
|
|
13
|
+
|
|
14
|
+
describe("MarkdownEditor / JSX attribute highlighting", () => {
|
|
15
|
+
it("does not let the nested HTML parser mis-highlight attributes after an expression", async () => {
|
|
16
|
+
mountStory(
|
|
17
|
+
<MantineProvider
|
|
18
|
+
theme={MANTINE_THEME}
|
|
19
|
+
cssVariablesResolver={v8CssVariablesResolver}
|
|
20
|
+
forceColorScheme="dark"
|
|
21
|
+
>
|
|
22
|
+
<Paper
|
|
23
|
+
p="md"
|
|
24
|
+
withBorder
|
|
25
|
+
>
|
|
26
|
+
<MarkdownEditor
|
|
27
|
+
mode="source"
|
|
28
|
+
document={TAG}
|
|
29
|
+
autoFocus={false}
|
|
30
|
+
mih={120}
|
|
31
|
+
/>
|
|
32
|
+
</Paper>
|
|
33
|
+
</MantineProvider>,
|
|
34
|
+
);
|
|
35
|
+
await getEditorView();
|
|
36
|
+
await nextFrame();
|
|
37
|
+
|
|
38
|
+
const colors = readLineTokenColors();
|
|
39
|
+
expect(colors.v).toBe(PROPERTY_COLOR);
|
|
40
|
+
expect(colors.t).toBe(PROPERTY_COLOR);
|
|
41
|
+
expect(colors.b).toBe(PROPERTY_COLOR);
|
|
42
|
+
expect(colors.trueQuoted).toBe(STRING_COLOR);
|
|
43
|
+
expect(colors.falseQuoted).toBe(STRING_COLOR);
|
|
44
|
+
expect(colors.exprLiteral).toBe(STRING_COLOR);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function readLineTokenColors(): Record<string, string> {
|
|
49
|
+
const line = document.querySelector(".cm-line");
|
|
50
|
+
expect(line).not.toBeNull();
|
|
51
|
+
if (!line) return {};
|
|
52
|
+
|
|
53
|
+
const out: Record<string, string> = {};
|
|
54
|
+
for (const span of Array.from(line.querySelectorAll("span"))) {
|
|
55
|
+
const text = span.textContent ?? "";
|
|
56
|
+
if (!text) continue;
|
|
57
|
+
const color = getComputedStyle(span).color;
|
|
58
|
+
|
|
59
|
+
if (text === "v") out.v = color;
|
|
60
|
+
if (text === "t") out.t = color;
|
|
61
|
+
if (text === "b") out.b = color;
|
|
62
|
+
if (text === '"2.0.0"') out.exprLiteral = color;
|
|
63
|
+
if (text === '"true"') out.trueQuoted = color;
|
|
64
|
+
if (text === '"false"') out.falseQuoted = color;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { MantineProvider, Paper, v8CssVariablesResolver } from "@mantine/core";
|
|
2
|
+
import { MarkdownEditor } from "@src/primitives/MarkdownEditor";
|
|
3
|
+
import { MANTINE_THEME } from "@src/theme/mantine";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
|
|
6
|
+
|
|
7
|
+
const JSX_BADGES_DOC = `### Since
|
|
8
|
+
|
|
9
|
+
<Since v="1.5.0" />
|
|
10
|
+
|
|
11
|
+
<Since v="2.0.0" prefix="Available since" />
|
|
12
|
+
|
|
13
|
+
### Label
|
|
14
|
+
|
|
15
|
+
<Label label="Required" />
|
|
16
|
+
|
|
17
|
+
<Label label="Optional" />
|
|
18
|
+
|
|
19
|
+
<Label label="Deprecated" />
|
|
20
|
+
|
|
21
|
+
<Label label="Required" /><Label label="Optional" />
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
describe("MarkdownEditor / inline JSX badges", () => {
|
|
25
|
+
it("renders all Since and Label components inline like the viewer", async () => {
|
|
26
|
+
mountStory(
|
|
27
|
+
<MantineProvider
|
|
28
|
+
theme={MANTINE_THEME}
|
|
29
|
+
cssVariablesResolver={v8CssVariablesResolver}
|
|
30
|
+
forceColorScheme="dark"
|
|
31
|
+
>
|
|
32
|
+
<Paper
|
|
33
|
+
p="md"
|
|
34
|
+
withBorder
|
|
35
|
+
>
|
|
36
|
+
<MarkdownEditor
|
|
37
|
+
mode="preview"
|
|
38
|
+
document={JSX_BADGES_DOC}
|
|
39
|
+
autoFocus={false}
|
|
40
|
+
jsxMode="render"
|
|
41
|
+
/>
|
|
42
|
+
</Paper>
|
|
43
|
+
</MantineProvider>,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const view = await getEditorView();
|
|
47
|
+
view.contentDOM.blur();
|
|
48
|
+
await nextFrame();
|
|
49
|
+
|
|
50
|
+
const badges = document.querySelectorAll(".mantine-Badge-label");
|
|
51
|
+
expect(badges.length).toBe(7);
|
|
52
|
+
expect(document.body.textContent).toContain("1.5.0");
|
|
53
|
+
expect(document.body.textContent).toContain("Required");
|
|
54
|
+
expect(document.body.textContent).toContain("Optional");
|
|
55
|
+
expect(document.body.textContent).toContain("Deprecated");
|
|
56
|
+
expect(document.querySelector(".cm-content")?.textContent).not.toContain("<Since");
|
|
57
|
+
expect(document.querySelector(".cm-content")?.textContent).not.toContain("<Label");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { MantineProvider, Paper, v8CssVariablesResolver } from "@mantine/core";
|
|
2
|
+
import { MarkdownEditor } from "@src/primitives/MarkdownEditor";
|
|
3
|
+
import { Since } from "@src/primitives/Since";
|
|
4
|
+
import { MANTINE_THEME } from "@src/theme/mantine";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
|
|
7
|
+
|
|
8
|
+
const JSX_DOC = `<Since v="9.9.9" />
|
|
9
|
+
|
|
10
|
+
Paragraph after component.
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
describe("MarkdownEditor / JSX selection reveal", () => {
|
|
14
|
+
it("shows JSX widget when inactive and raw source when caret is inside the tag", async () => {
|
|
15
|
+
mountStory(
|
|
16
|
+
<MantineProvider
|
|
17
|
+
theme={MANTINE_THEME}
|
|
18
|
+
cssVariablesResolver={v8CssVariablesResolver}
|
|
19
|
+
forceColorScheme="dark"
|
|
20
|
+
>
|
|
21
|
+
<Paper
|
|
22
|
+
p="md"
|
|
23
|
+
withBorder
|
|
24
|
+
>
|
|
25
|
+
<MarkdownEditor
|
|
26
|
+
mode="preview"
|
|
27
|
+
document={JSX_DOC}
|
|
28
|
+
autoFocus={false}
|
|
29
|
+
jsxMode="render"
|
|
30
|
+
components={{ Since }}
|
|
31
|
+
/>
|
|
32
|
+
</Paper>
|
|
33
|
+
</MantineProvider>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const view = await getEditorView();
|
|
37
|
+
view.contentDOM.blur();
|
|
38
|
+
await nextFrame();
|
|
39
|
+
|
|
40
|
+
expect(document.body.textContent).toContain("9.9.9");
|
|
41
|
+
expect(document.querySelector(".cm-content")?.textContent).not.toContain("<Since");
|
|
42
|
+
});
|
|
43
|
+
});
|