@surrealdb/ui 1.2.15 → 1.2.17
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/dist/assets/1299e4695ad0bd61.svg +5 -0
- package/dist/assets/408117e904653548.svg +48 -0
- package/dist/assets/b516b020352f6369.svg +1 -0
- package/dist/assets/dfde8de566c3c2b9.svg +11 -0
- package/dist/assets/e9eae7bc863f211b.svg +1 -0
- package/dist/assets/f74ae3707a5d418f.svg +1 -0
- package/dist/ui.d.ts +12 -0
- package/dist/ui.js +678 -649
- package/dist/ui.js.map +1 -1
- package/package.json +8 -2
- package/.claude/launch.json +0 -17
- package/.oxfmtrc.json +0 -27
- package/.oxlintrc.json +0 -23
- package/.picto-fix-png/Cursor.png +0 -0
- package/.picto-fix-png/DocumentGradient.png +0 -0
- package/.picto-fix-png/FileGradient.png +0 -0
- package/.picto-fix-png/HealthBubbleGradient.png +0 -0
- package/.picto-fix-png/LearnGradient.png +0 -0
- package/.picto-fix-png/MediaFileGradient.png +0 -0
- package/.zed/settings.json +0 -36
- package/AGENTS.md +0 -111
- package/CLAUDE.md +0 -111
- package/REVIEW.md +0 -81
- package/doc/code-style.md +0 -264
- package/doc/release.md +0 -102
- package/doc/stacker-plan.md +0 -219
- package/release-notes.md +0 -0
- package/tests/_setup/e2e-helpers.tsx +0 -169
- package/tests/_setup/markdown-classes.ts +0 -3
- package/tests/_setup/portable-stories.ts +0 -33
- 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-inline-click.test.tsx/MarkdownEditor---inline-JSX-click-reveal-reveals-the-raw-source-when-an-inline-component-badge-is-clicked-1.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/jsx-inline-click.test.tsx/MarkdownEditor---inline-JSX-click-reveal-reveals-the-raw-source-when-an-inline-component-badge-is-clicked-2.png +0 -0
- package/tests/e2e/MarkdownEditor/__screenshots__/jsx-inline-click.test.tsx/MarkdownEditor---inline-JSX-click-reveal-reveals-the-raw-source-when-an-inline-component-badge-is-clicked-3.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 +0 -153
- package/tests/e2e/MarkdownEditor/edits.test.tsx +0 -112
- package/tests/e2e/MarkdownEditor/heading-fold.test.tsx +0 -45
- package/tests/e2e/MarkdownEditor/hybrid-widgets.test.tsx +0 -193
- package/tests/e2e/MarkdownEditor/image-on-image.test.tsx +0 -73
- package/tests/e2e/MarkdownEditor/jsx-block-content.test.tsx +0 -243
- package/tests/e2e/MarkdownEditor/jsx-highlight.test.tsx +0 -69
- package/tests/e2e/MarkdownEditor/jsx-inline-badges.test.tsx +0 -60
- package/tests/e2e/MarkdownEditor/jsx-inline-click.test.tsx +0 -54
- package/tests/e2e/MarkdownEditor/jsx-selection.test.tsx +0 -44
- package/tests/e2e/MarkdownEditor/link-placeholder.test.tsx +0 -68
- package/tests/e2e/MarkdownEditor/media-align.test.tsx +0 -58
- package/tests/e2e/MarkdownEditor/media-edit.test.tsx +0 -64
- package/tests/e2e/MarkdownEditor/media-sizing.test.tsx +0 -124
- package/tests/e2e/MarkdownEditor/modes.test.tsx +0 -94
- package/tests/e2e/MarkdownEditor/onimage-parity.test.tsx +0 -50
- package/tests/e2e/MarkdownEditor/regressions.test.tsx +0 -223
- package/tests/e2e/MarkdownEditor/slash-commands.test.tsx +0 -100
- package/tests/e2e/MarkdownEditor/table-click.test.tsx +0 -48
- package/tests/e2e/MarkdownEditor/table-controls.test.tsx +0 -57
- package/tests/e2e/MarkdownEditor/table-format.test.tsx +0 -42
- package/tests/e2e/MarkdownEditor/undo-redo.test.tsx +0 -39
- 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 +0 -191
- package/tests/e2e/MarkdownViewer/render.test.tsx +0 -36
- package/tests/e2e/Stacker/stacker.test.tsx +0 -304
- package/tests/unit/Editor/helpers.test.ts +0 -42
- package/tests/unit/MarkdownConformance/error-isolation.test.tsx +0 -44
- package/tests/unit/MarkdownConformance/indentation.test.tsx +0 -30
- package/tests/unit/MarkdownConformance/inline-code-fuzz.test.tsx +0 -255
- package/tests/unit/MarkdownConformance/jsx-attributes.test.tsx +0 -57
- package/tests/unit/MarkdownConformance/jsx-fuzz.test.tsx +0 -165
- package/tests/unit/MarkdownConformance/links.test.tsx +0 -95
- package/tests/unit/MarkdownConformance/render-helpers.tsx +0 -88
- package/tests/unit/MarkdownEditor/code-info.test.ts +0 -63
- package/tests/unit/MarkdownEditor/decorations.test.ts +0 -490
- package/tests/unit/MarkdownEditor/editor-ready.test.ts +0 -36
- package/tests/unit/MarkdownEditor/html-descriptors.test.ts +0 -94
- package/tests/unit/MarkdownEditor/indented-fence.test.ts +0 -103
- package/tests/unit/MarkdownEditor/jsx-attr-scan.test.ts +0 -115
- package/tests/unit/MarkdownEditor/jsx-block-widget.test.ts +0 -92
- package/tests/unit/MarkdownEditor/jsx-tag-grammar.test.ts +0 -88
- package/tests/unit/MarkdownEditor/list-indent.test.ts +0 -95
- package/tests/unit/MarkdownEditor/slash-commands.test.ts +0 -213
- package/tests/unit/MarkdownEditor/table-format.test.ts +0 -83
- package/tests/unit/MarkdownEditor/table.test.ts +0 -119
- package/tests/unit/MarkdownEditor/triggers.test.ts +0 -244
- package/tests/unit/MarkdownEditor/widget-store.test.ts +0 -105
- package/tests/unit/MarkdownViewer/callout.test.tsx +0 -60
- package/tests/unit/MarkdownViewer/code-highlight.test.tsx +0 -61
- package/tests/unit/MarkdownViewer/code-title.test.tsx +0 -62
- package/tests/unit/MarkdownViewer/features.test.tsx +0 -164
- package/tests/unit/MarkdownViewer/headings.test.tsx +0 -40
- package/tests/unit/MarkdownViewer/indented-jsx-children.test.tsx +0 -98
- package/tests/unit/MarkdownViewer/jsx-block-nesting.test.tsx +0 -234
- package/tests/unit/MarkdownViewer/jsx.test.tsx +0 -211
- package/tests/unit/MarkdownViewer/list-bullets.test.tsx +0 -49
- package/tests/unit/MarkdownViewer/list-code.test.tsx +0 -65
- package/tests/unit/MarkdownViewer/preserve-newlines.test.tsx +0 -143
- package/tests/unit/MarkdownViewer/read-time.test.ts +0 -21
- package/tests/unit/MarkdownViewer/renderers.test.tsx +0 -95
- package/tests/unit/MarkdownViewer/runnable.test.tsx +0 -69
- package/tests/unit/MarkdownViewer/ssr.test.tsx +0 -94
- package/tests/unit/MarkdownViewer/syntax-highlighting.test.tsx +0 -74
- package/tests/unit/MarkdownViewer/tabs-fenced-code.test.tsx +0 -196
- package/tests/unit/MarkdownViewer/tabs.test.tsx +0 -116
- package/tests/unit/Stacker/drag.test.ts +0 -21
- package/tests/unit/Stacker/helpers.test.ts +0 -105
- package/tests/unit/Stacker/reducer.test.ts +0 -107
- package/tools/duplicate-vector-mappings.json +0 -143
- package/tools/glass-export-overrides.json +0 -8
- package/tools/icon-parser.ts +0 -78
- package/tools/integration-gradient-exports.json +0 -57
- package/tools/picto-compare.ts +0 -64
- package/tools/picto-merge-urls.ts +0 -43
- package/tools/picto-sync.ts +0 -630
- package/tools/picto-verify-gradients.ts +0 -86
- package/tools/vector-export-nodes.json +0 -977
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { Grid, MantineProvider, Paper, v8CssVariablesResolver } from "@mantine/core";
|
|
2
|
-
import { MarkdownEditor } from "@src/primitives/MarkdownEditor";
|
|
3
|
-
import { MarkdownViewer } from "@src/primitives/MarkdownViewer";
|
|
4
|
-
import { MANTINE_THEME } from "@src/theme/mantine";
|
|
5
|
-
import { type FC, useState } from "react";
|
|
6
|
-
import { describe, expect, it } from "vitest";
|
|
7
|
-
|
|
8
|
-
import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
|
|
9
|
-
import { md, mdViewer } from "../../_setup/markdown-classes";
|
|
10
|
-
|
|
11
|
-
const PARITY_FIXTURE = `# Title
|
|
12
|
-
|
|
13
|
-
Paragraph *italic* and **bold** and \`code\`.
|
|
14
|
-
|
|
15
|
-
## Section
|
|
16
|
-
|
|
17
|
-
Visit [link](https://surrealdb.com).
|
|
18
|
-
|
|
19
|
-
- One
|
|
20
|
-
- Two
|
|
21
|
-
|
|
22
|
-
- [ ] todo one
|
|
23
|
-
- [x] todo two **with bold**
|
|
24
|
-
|
|
25
|
-
\`\`\`ts
|
|
26
|
-
return true;
|
|
27
|
-
\`\`\`
|
|
28
|
-
|
|
29
|
-
<details>
|
|
30
|
-
<summary>Expand</summary>
|
|
31
|
-
|
|
32
|
-
Inside details.
|
|
33
|
-
|
|
34
|
-
</details>
|
|
35
|
-
`;
|
|
36
|
-
|
|
37
|
-
const ParityLayout: FC = () => {
|
|
38
|
-
const [doc] = useState(PARITY_FIXTURE);
|
|
39
|
-
return (
|
|
40
|
-
<MantineProvider
|
|
41
|
-
theme={MANTINE_THEME}
|
|
42
|
-
cssVariablesResolver={v8CssVariablesResolver}
|
|
43
|
-
forceColorScheme="dark"
|
|
44
|
-
>
|
|
45
|
-
<Grid gap="md">
|
|
46
|
-
<Grid.Col span={{ base: 12, md: 6 }}>
|
|
47
|
-
<Paper
|
|
48
|
-
p="md"
|
|
49
|
-
radius="md"
|
|
50
|
-
withBorder
|
|
51
|
-
>
|
|
52
|
-
<MarkdownEditor
|
|
53
|
-
mode="preview"
|
|
54
|
-
document={doc}
|
|
55
|
-
autoFocus={false}
|
|
56
|
-
mih={200}
|
|
57
|
-
/>
|
|
58
|
-
</Paper>
|
|
59
|
-
</Grid.Col>
|
|
60
|
-
<Grid.Col span={{ base: 12, md: 6 }}>
|
|
61
|
-
<Paper
|
|
62
|
-
p="md"
|
|
63
|
-
radius="md"
|
|
64
|
-
withBorder
|
|
65
|
-
>
|
|
66
|
-
<MarkdownViewer content={doc} />
|
|
67
|
-
</Paper>
|
|
68
|
-
</Grid.Col>
|
|
69
|
-
</Grid>
|
|
70
|
-
</MantineProvider>
|
|
71
|
-
);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function visibleText(el: Element): string {
|
|
75
|
-
const clone = el.cloneNode(true) as Element;
|
|
76
|
-
|
|
77
|
-
for (const n of clone.querySelectorAll(`.${md.markHidden}, .cm-line-folded`)) {
|
|
78
|
-
n.remove();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return clone.textContent?.replace(/\s+/g, "") ?? "";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const PARITY_CLASSES = [md.h1, md.h2, md.strong, md.emphasis, md.codeInline, md.link] as const;
|
|
85
|
-
|
|
86
|
-
describe("MarkdownViewer / editor parity", () => {
|
|
87
|
-
it("renders checklist text alongside checkboxes in the viewer", async () => {
|
|
88
|
-
mountStory(<ParityLayout />);
|
|
89
|
-
await getEditorView();
|
|
90
|
-
await nextFrame();
|
|
91
|
-
|
|
92
|
-
const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
|
|
93
|
-
expect(vrEl).not.toBeNull();
|
|
94
|
-
if (vrEl === null) return;
|
|
95
|
-
|
|
96
|
-
const taskItems = vrEl.querySelectorAll(`li.${md.taskItem}`);
|
|
97
|
-
expect(taskItems.length).toBe(2);
|
|
98
|
-
|
|
99
|
-
const text = (i: number) =>
|
|
100
|
-
(taskItems[i]?.querySelector(`.${md.taskText}`)?.textContent ?? "").trim();
|
|
101
|
-
expect(text(0)).toContain("todo one");
|
|
102
|
-
expect(text(1)).toContain("todo two");
|
|
103
|
-
expect(taskItems[1]?.querySelector(`.${md.strong}`)?.textContent).toBe("with bold");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("uses the same link colour as the editor", async () => {
|
|
107
|
-
mountStory(<ParityLayout />);
|
|
108
|
-
const view = await getEditorView();
|
|
109
|
-
view.contentDOM.blur();
|
|
110
|
-
await nextFrame();
|
|
111
|
-
|
|
112
|
-
const cmLink = document.querySelector(`.cm-content .${md.link}`);
|
|
113
|
-
const vwLink = document.querySelector(`.${mdViewer.viewerRoot} .${md.link}`);
|
|
114
|
-
expect(cmLink).not.toBeNull();
|
|
115
|
-
expect(vwLink).not.toBeNull();
|
|
116
|
-
if (cmLink === null || vwLink === null) return;
|
|
117
|
-
|
|
118
|
-
expect(getComputedStyle(vwLink).color).toBe(getComputedStyle(cmLink).color);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("matches visible text between preview editor (blurred) and MarkdownViewer", async () => {
|
|
122
|
-
mountStory(<ParityLayout />);
|
|
123
|
-
const view = await getEditorView();
|
|
124
|
-
view.contentDOM.blur();
|
|
125
|
-
await nextFrame();
|
|
126
|
-
await nextFrame();
|
|
127
|
-
|
|
128
|
-
const cmEl = document.querySelector(".cm-content");
|
|
129
|
-
const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
|
|
130
|
-
expect(cmEl).not.toBeNull();
|
|
131
|
-
expect(vrEl).not.toBeNull();
|
|
132
|
-
if (cmEl === null || vrEl === null) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
expect(visibleText(vrEl)).toBe(visibleText(cmEl));
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("matches counts for shared structural classes", async () => {
|
|
140
|
-
mountStory(<ParityLayout />);
|
|
141
|
-
const view = await getEditorView();
|
|
142
|
-
view.contentDOM.blur();
|
|
143
|
-
await nextFrame();
|
|
144
|
-
|
|
145
|
-
const cmEl = document.querySelector(".cm-content");
|
|
146
|
-
const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
|
|
147
|
-
expect(cmEl).not.toBeNull();
|
|
148
|
-
expect(vrEl).not.toBeNull();
|
|
149
|
-
if (cmEl === null || vrEl === null) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
for (const cls of PARITY_CLASSES) {
|
|
154
|
-
expect(vrEl.querySelectorAll(`.${cls}`).length).toBe(
|
|
155
|
-
cmEl.querySelectorAll(`.${cls}`).length,
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("matches computed font size on first heading", async () => {
|
|
161
|
-
mountStory(<ParityLayout />);
|
|
162
|
-
const view = await getEditorView();
|
|
163
|
-
view.contentDOM.blur();
|
|
164
|
-
await nextFrame();
|
|
165
|
-
|
|
166
|
-
const cmH1 = document.querySelector(`.cm-content .${md.h1}`);
|
|
167
|
-
const vwH1 = document.querySelector(`.${mdViewer.viewerRoot} .${md.h1}`);
|
|
168
|
-
expect(cmH1).not.toBeNull();
|
|
169
|
-
expect(vwH1).not.toBeNull();
|
|
170
|
-
if (cmH1 === null || vwH1 === null) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const a = getComputedStyle(cmH1);
|
|
175
|
-
const b = getComputedStyle(vwH1);
|
|
176
|
-
expect(a.fontSize).toBe(b.fontSize);
|
|
177
|
-
expect(a.fontWeight).toBe(b.fontWeight);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it("renders HTML details in both editor preview and viewer", async () => {
|
|
181
|
-
mountStory(<ParityLayout />);
|
|
182
|
-
const view = await getEditorView();
|
|
183
|
-
view.contentDOM.blur();
|
|
184
|
-
await nextFrame();
|
|
185
|
-
|
|
186
|
-
const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
|
|
187
|
-
expect(vrEl?.textContent).toContain("Expand");
|
|
188
|
-
expect(vrEl?.textContent).toContain("Inside details");
|
|
189
|
-
expect(document.querySelector(".cm-content")?.textContent).toContain("Inside details");
|
|
190
|
-
});
|
|
191
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { MantineProvider, v8CssVariablesResolver } from "@mantine/core";
|
|
2
|
-
import { MarkdownViewer } from "@src/primitives/MarkdownViewer";
|
|
3
|
-
import { MANTINE_THEME } from "@src/theme/mantine";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
|
|
6
|
-
import { mountStory, nextFrame } from "../../_setup/e2e-helpers";
|
|
7
|
-
import { md, mdViewer } from "../../_setup/markdown-classes";
|
|
8
|
-
|
|
9
|
-
const FIXTURE = `# Hello
|
|
10
|
-
|
|
11
|
-
Paragraph **bold**.
|
|
12
|
-
`;
|
|
13
|
-
|
|
14
|
-
describe("MarkdownViewer / render", () => {
|
|
15
|
-
it("exercises shared preview class names without mounting CodeMirror", async () => {
|
|
16
|
-
mountStory(
|
|
17
|
-
<MantineProvider
|
|
18
|
-
theme={MANTINE_THEME}
|
|
19
|
-
cssVariablesResolver={v8CssVariablesResolver}
|
|
20
|
-
forceColorScheme="dark"
|
|
21
|
-
>
|
|
22
|
-
<MarkdownViewer content={FIXTURE} />
|
|
23
|
-
</MantineProvider>,
|
|
24
|
-
);
|
|
25
|
-
await nextFrame();
|
|
26
|
-
await nextFrame();
|
|
27
|
-
|
|
28
|
-
const root = document.querySelector(`.${mdViewer.viewerRoot}`);
|
|
29
|
-
expect(root).not.toBeNull();
|
|
30
|
-
expect(document.querySelector(".cm-editor")).toBeNull();
|
|
31
|
-
|
|
32
|
-
expect(root?.querySelector(`.${md.heading}`)).not.toBeNull();
|
|
33
|
-
expect(root?.querySelector(`.${md.h1}`)).not.toBeNull();
|
|
34
|
-
expect(root?.querySelector(`.${md.strong}`)).not.toBeNull();
|
|
35
|
-
});
|
|
36
|
-
});
|
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import * as StackerStories from "@src/stories/playground/Stacker.stories";
|
|
2
|
-
import { composeStories } from "@storybook/react-vite";
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
|
|
5
|
-
import { mountStory, nextFrame, sleep, userEvent } from "../../_setup/e2e-helpers";
|
|
6
|
-
|
|
7
|
-
const Stories = composeStories(StackerStories);
|
|
8
|
-
|
|
9
|
-
// ── DOM query helpers ─────────────────────────────────────────────────────────
|
|
10
|
-
function dialogs(): HTMLElement[] {
|
|
11
|
-
return [...document.querySelectorAll<HTMLElement>('[role="dialog"]')];
|
|
12
|
-
}
|
|
13
|
-
function scrims(): HTMLElement[] {
|
|
14
|
-
return [...document.querySelectorAll<HTMLElement>("[data-stacker-scrim]")];
|
|
15
|
-
}
|
|
16
|
-
function sheets(): HTMLElement[] {
|
|
17
|
-
return [...document.querySelectorAll<HTMLElement>("[data-stacker-sheet]")];
|
|
18
|
-
}
|
|
19
|
-
function modals(): HTMLElement[] {
|
|
20
|
-
return [...document.querySelectorAll<HTMLElement>('[data-stacker="modal"]')];
|
|
21
|
-
}
|
|
22
|
-
function cards(): HTMLElement[] {
|
|
23
|
-
return [...document.querySelectorAll<HTMLElement>("[data-stacker-card]")];
|
|
24
|
-
}
|
|
25
|
-
/** Leaf element whose exact text matches — matches the ModalBase/plain titles. */
|
|
26
|
-
function hasText(text: string): boolean {
|
|
27
|
-
return [...document.querySelectorAll<HTMLElement>("*")].some(
|
|
28
|
-
(el) => el.children.length === 0 && el.textContent?.trim() === text,
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function until(predicate: () => boolean, timeoutMs = 3000): Promise<void> {
|
|
33
|
-
const start = Date.now();
|
|
34
|
-
while (Date.now() - start < timeoutMs) {
|
|
35
|
-
if (predicate()) return;
|
|
36
|
-
await nextFrame();
|
|
37
|
-
}
|
|
38
|
-
throw new Error("condition not met within timeout");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function findButton(name: string, scope: ParentNode = document): HTMLButtonElement | undefined {
|
|
42
|
-
return [...scope.querySelectorAll<HTMLButtonElement>("button")].find(
|
|
43
|
-
(b) => b.textContent?.trim() === name || b.getAttribute("aria-label") === name,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
async function clickButton(name: string, scope: ParentNode = document): Promise<void> {
|
|
47
|
-
await until(() => findButton(name, scope) !== undefined);
|
|
48
|
-
const button = findButton(name, scope);
|
|
49
|
-
if (!button) throw new Error(`button not found: ${name}`);
|
|
50
|
-
await userEvent.click(button);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function scaleOf(el: Element): number {
|
|
54
|
-
const t = getComputedStyle(el).transform;
|
|
55
|
-
if (!t || t === "none") return 1;
|
|
56
|
-
const m = t.match(/matrix\(([^)]+)\)/);
|
|
57
|
-
return m ? Number.parseFloat(m[1].split(",")[0]) : 1;
|
|
58
|
-
}
|
|
59
|
-
async function pageScale(): Promise<number> {
|
|
60
|
-
await until(() => document.querySelector("[data-stacker-page]") !== null);
|
|
61
|
-
return scaleOf(document.querySelector("[data-stacker-page]") as Element);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
describe("Stacker", () => {
|
|
65
|
-
it("E1 mounts nothing until an overlay is opened", async () => {
|
|
66
|
-
mountStory(<Stories.Default />);
|
|
67
|
-
await sleep(60);
|
|
68
|
-
expect(dialogs()).toHaveLength(0);
|
|
69
|
-
expect(scrims()).toHaveLength(0);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("E2 renders a modal (forced) and never scales the page", async () => {
|
|
73
|
-
mountStory(<Stories.ForcedModal />);
|
|
74
|
-
await clickButton("Open forced modal");
|
|
75
|
-
await until(() => modals().length === 1);
|
|
76
|
-
await sleep(300);
|
|
77
|
-
expect(sheets()).toHaveLength(0);
|
|
78
|
-
expect(await pageScale()).toBeCloseTo(1, 2);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("E3 renders a drawer and scales the page when mobileQuery matches", async () => {
|
|
82
|
-
mountStory(<Stories.MobileEmulated />);
|
|
83
|
-
await clickButton("Open overlay");
|
|
84
|
-
await until(() => sheets().length === 1);
|
|
85
|
-
expect(modals()).toHaveLength(0);
|
|
86
|
-
await sleep(500);
|
|
87
|
-
expect(await pageScale()).toBeLessThan(0.99);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("E4 respects a forced drawer variant on desktop", async () => {
|
|
91
|
-
mountStory(<Stories.ForcedDrawer />);
|
|
92
|
-
await clickButton("Open forced drawer");
|
|
93
|
-
await until(() => sheets().length === 1);
|
|
94
|
-
expect(modals()).toHaveLength(0);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("E5 coerces later overlays to the stack's kind", async () => {
|
|
98
|
-
mountStory(<Stories.CoercedStack />);
|
|
99
|
-
await clickButton("Open drawer, then a modal");
|
|
100
|
-
await until(() => sheets().length === 1);
|
|
101
|
-
await clickButton('Open a "modal" on top');
|
|
102
|
-
await until(() => sheets().length === 2);
|
|
103
|
-
expect(modals()).toHaveLength(0); // the "modal" was coerced to a drawer
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("E6 stacks modals with one scrim, hiding the covered card", async () => {
|
|
107
|
-
mountStory(<Stories.ForcedModal />);
|
|
108
|
-
await clickButton("Open forced modal");
|
|
109
|
-
await until(() => cards().length === 1);
|
|
110
|
-
await sleep(300);
|
|
111
|
-
|
|
112
|
-
await clickButton("Push another");
|
|
113
|
-
await until(() => cards().length === 2);
|
|
114
|
-
await sleep(400);
|
|
115
|
-
|
|
116
|
-
expect(scrims()).toHaveLength(1);
|
|
117
|
-
expect(dialogs()).toHaveLength(2);
|
|
118
|
-
|
|
119
|
-
const [lower, top] = cards();
|
|
120
|
-
// The covered card is hidden entirely (no layout box) while another is on top…
|
|
121
|
-
const lowerRect = lower.getBoundingClientRect();
|
|
122
|
-
expect(lowerRect.width).toBe(0);
|
|
123
|
-
expect(lowerRect.height).toBe(0);
|
|
124
|
-
// …while the top card stays visible.
|
|
125
|
-
expect(top.getBoundingClientRect().height).toBeGreaterThan(0);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("E7 reveals the previous content on pop with the scrim intact", async () => {
|
|
129
|
-
mountStory(<Stories.ForcedModal />);
|
|
130
|
-
await clickButton("Open forced modal");
|
|
131
|
-
await until(() => hasText("Forced modal"));
|
|
132
|
-
await clickButton("Push another");
|
|
133
|
-
await until(() => hasText("Overlay 2"));
|
|
134
|
-
await sleep(400);
|
|
135
|
-
|
|
136
|
-
await clickButton("Close this", cards()[1]);
|
|
137
|
-
await until(() => cards().length === 1 && hasText("Forced modal"));
|
|
138
|
-
await sleep(300);
|
|
139
|
-
expect(scrims()).toHaveLength(1);
|
|
140
|
-
expect(dialogs()).toHaveLength(1);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("E8 peeks lower drawers out and fades their content", async () => {
|
|
144
|
-
mountStory(<Stories.StackedDrawers />);
|
|
145
|
-
await clickButton("Open stacked drawers");
|
|
146
|
-
await until(() => sheets().length === 1);
|
|
147
|
-
await clickButton("Push another");
|
|
148
|
-
await until(() => sheets().length === 2);
|
|
149
|
-
await sleep(400);
|
|
150
|
-
|
|
151
|
-
const [lower, top] = sheets();
|
|
152
|
-
expect(lower.getBoundingClientRect().left).toBeLessThan(top.getBoundingClientRect().left);
|
|
153
|
-
expect(Number(getComputedStyle(lower).opacity)).toBe(1); // sheet surface stays visible
|
|
154
|
-
const lowerBody = lower.querySelector('[class*="body"]') as HTMLElement;
|
|
155
|
-
await until(() => Number(getComputedStyle(lowerBody).opacity) < 0.1);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("E9 does not compound the page scale and restores it after close", async () => {
|
|
159
|
-
mountStory(<Stories.StackedDrawers />);
|
|
160
|
-
await clickButton("Open stacked drawers");
|
|
161
|
-
await until(() => sheets().length === 1);
|
|
162
|
-
await sleep(500);
|
|
163
|
-
const oneDrawer = await pageScale();
|
|
164
|
-
expect(oneDrawer).toBeLessThan(0.99);
|
|
165
|
-
|
|
166
|
-
await clickButton("Push another");
|
|
167
|
-
await until(() => sheets().length === 2);
|
|
168
|
-
await sleep(500);
|
|
169
|
-
expect(await pageScale()).toBeCloseTo(oneDrawer, 2);
|
|
170
|
-
|
|
171
|
-
await clickButton("Close this", sheets()[1]);
|
|
172
|
-
await until(() => sheets().length === 1);
|
|
173
|
-
await clickButton("Close this", sheets()[0]);
|
|
174
|
-
await until(() => sheets().length === 0);
|
|
175
|
-
await sleep(500);
|
|
176
|
-
expect(await pageScale()).toBeCloseTo(1, 2);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("E10 pops only the top overlay on Escape", async () => {
|
|
180
|
-
mountStory(<Stories.ForcedModal />);
|
|
181
|
-
await clickButton("Open forced modal");
|
|
182
|
-
await until(() => hasText("Forced modal"));
|
|
183
|
-
await clickButton("Push another");
|
|
184
|
-
await until(() => hasText("Overlay 2"));
|
|
185
|
-
await sleep(400);
|
|
186
|
-
|
|
187
|
-
await userEvent.keyboard("{Escape}");
|
|
188
|
-
await until(() => hasText("Forced modal") && !hasText("Overlay 2"));
|
|
189
|
-
expect(dialogs()).toHaveLength(1);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("E10b blocks dismissal when closeOnEscape/closeOnClickOutside are false", async () => {
|
|
193
|
-
mountStory(<Stories.DismissControl />);
|
|
194
|
-
await clickButton("No dismiss affordances");
|
|
195
|
-
await until(() => hasText("Locked"));
|
|
196
|
-
await sleep(300);
|
|
197
|
-
|
|
198
|
-
await userEvent.keyboard("{Escape}");
|
|
199
|
-
await sleep(250);
|
|
200
|
-
expect(hasText("Locked")).toBe(true); // still open
|
|
201
|
-
|
|
202
|
-
scrims()[0]?.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
203
|
-
await sleep(250);
|
|
204
|
-
expect(hasText("Locked")).toBe(true);
|
|
205
|
-
|
|
206
|
-
await clickButton("Close from inside");
|
|
207
|
-
await until(() => !hasText("Locked"));
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it("E11 keeps the close button pinned top-right with a long header", async () => {
|
|
211
|
-
mountStory(<Stories.LongHeader />);
|
|
212
|
-
await clickButton("Open long header");
|
|
213
|
-
await until(() => dialogs().length === 1);
|
|
214
|
-
await sleep(300);
|
|
215
|
-
|
|
216
|
-
const dialog = dialogs()[0];
|
|
217
|
-
const close = dialog.querySelector('[aria-label="Close"]') as HTMLElement;
|
|
218
|
-
const header = dialog.querySelector('[class*="header"]') as HTMLElement;
|
|
219
|
-
const closeRect = close.getBoundingClientRect();
|
|
220
|
-
const headerRect = header.getBoundingClientRect();
|
|
221
|
-
// Pinned to the top of a tall wrapping header (not vertically centered)…
|
|
222
|
-
expect(closeRect.top - headerRect.top).toBeLessThan(40);
|
|
223
|
-
// …and to its right edge (within the header padding).
|
|
224
|
-
expect(headerRect.right - closeRect.right).toBeLessThan(60);
|
|
225
|
-
expect(headerRect.right - closeRect.right).toBeGreaterThanOrEqual(0);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("E12 drags a draggable drawer to dismiss, and springs back a small drag", async () => {
|
|
229
|
-
mountStory(<Stories.DraggableDrawer />);
|
|
230
|
-
await clickButton("Open draggable drawer");
|
|
231
|
-
await until(() => sheets().length === 1);
|
|
232
|
-
await sleep(400);
|
|
233
|
-
|
|
234
|
-
const grip = document.querySelector('[class*="grip"]') as HTMLElement;
|
|
235
|
-
|
|
236
|
-
// Small drag (30px down) → springs back, stays open.
|
|
237
|
-
await dragSheet(grip, 0, 30);
|
|
238
|
-
await sleep(400);
|
|
239
|
-
expect(sheets()).toHaveLength(1);
|
|
240
|
-
|
|
241
|
-
// Large drag past halfway → dismisses.
|
|
242
|
-
const height = sheets()[0].getBoundingClientRect().height;
|
|
243
|
-
await dragSheet(grip, 0, Math.round(height * 0.7));
|
|
244
|
-
await until(() => sheets().length === 0);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("E13 fires onClose once per close and empties the stack with closeAll", async () => {
|
|
248
|
-
mountStory(<Stories.Lifecycle />);
|
|
249
|
-
const count = () =>
|
|
250
|
-
Number(
|
|
251
|
-
(document.querySelector('[data-testid="close-count"]')?.textContent ?? "").replace(
|
|
252
|
-
/\D/g,
|
|
253
|
-
"",
|
|
254
|
-
),
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
await clickButton("Open");
|
|
258
|
-
await until(() => hasText("Tracked overlay"));
|
|
259
|
-
await clickButton("Close this");
|
|
260
|
-
await until(() => count() === 1);
|
|
261
|
-
await sleep(300);
|
|
262
|
-
|
|
263
|
-
await clickButton("Open");
|
|
264
|
-
await until(() => hasText("Tracked overlay"));
|
|
265
|
-
await clickButton("Push another");
|
|
266
|
-
await until(() => hasText("Overlay 2"));
|
|
267
|
-
// "Close all" lives on the page (covered by the scrim), so fire it natively.
|
|
268
|
-
findButton("Close all")?.click();
|
|
269
|
-
await until(() => dialogs().length === 0 && scrims().length === 0);
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
/** Dispatch a pointer drag on `el`: down on the element, moves + up on document. */
|
|
274
|
-
async function dragSheet(el: HTMLElement, dx: number, dy: number): Promise<void> {
|
|
275
|
-
const rect = el.getBoundingClientRect();
|
|
276
|
-
const startX = rect.left + rect.width / 2;
|
|
277
|
-
const startY = rect.top + rect.height / 2;
|
|
278
|
-
|
|
279
|
-
const opts = (x: number, y: number): PointerEventInit => ({
|
|
280
|
-
pointerId: 1,
|
|
281
|
-
bubbles: true,
|
|
282
|
-
cancelable: true,
|
|
283
|
-
clientX: x,
|
|
284
|
-
clientY: y,
|
|
285
|
-
button: 0,
|
|
286
|
-
buttons: 1,
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
el.dispatchEvent(new PointerEvent("pointerdown", opts(startX, startY)));
|
|
290
|
-
const steps = 6;
|
|
291
|
-
for (let i = 1; i <= steps; i++) {
|
|
292
|
-
document.dispatchEvent(
|
|
293
|
-
new PointerEvent(
|
|
294
|
-
"pointermove",
|
|
295
|
-
opts(startX + (dx * i) / steps, startY + (dy * i) / steps),
|
|
296
|
-
),
|
|
297
|
-
);
|
|
298
|
-
await nextFrame();
|
|
299
|
-
}
|
|
300
|
-
document.dispatchEvent(
|
|
301
|
-
new PointerEvent("pointerup", { ...opts(startX + dx, startY + dy), buttons: 0 }),
|
|
302
|
-
);
|
|
303
|
-
await nextFrame();
|
|
304
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { EditorState } from "@codemirror/state";
|
|
2
|
-
import { EditorView } from "@codemirror/view";
|
|
3
|
-
import { setEditorText } from "@src/lib/editor/helpers";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
|
|
6
|
-
describe("setEditorText", () => {
|
|
7
|
-
let container: HTMLDivElement;
|
|
8
|
-
let view: EditorView;
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
view?.destroy();
|
|
12
|
-
container?.remove();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
function createView(doc: string, anchor: number): EditorView {
|
|
16
|
-
container = document.createElement("div");
|
|
17
|
-
document.body.appendChild(container);
|
|
18
|
-
|
|
19
|
-
return new EditorView({
|
|
20
|
-
parent: container,
|
|
21
|
-
state: EditorState.create({ doc, selection: { anchor, head: anchor } }),
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
it("clamps the selection when replacing with shorter text", () => {
|
|
26
|
-
view = createView("abcd", 4);
|
|
27
|
-
|
|
28
|
-
expect(() => setEditorText(view, "ab")).not.toThrow();
|
|
29
|
-
|
|
30
|
-
expect(view.state.doc.toString()).toBe("ab");
|
|
31
|
-
expect(view.state.selection.main.head).toBe(2);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("preserves a valid selection when replacing with longer text", () => {
|
|
35
|
-
view = createView("ab", 2);
|
|
36
|
-
|
|
37
|
-
setEditorText(view, "abcd");
|
|
38
|
-
|
|
39
|
-
expect(view.state.doc.toString()).toBe("abcd");
|
|
40
|
-
expect(view.state.selection.main.head).toBe(2);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { MarkdownComponents } from "@src/lib/markdown";
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
|
|
4
|
-
import { renderMarkdownClient, visibleText } from "./render-helpers";
|
|
5
|
-
|
|
6
|
-
function Boom(): never {
|
|
7
|
-
throw new Error("boom");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const components: MarkdownComponents = { Boom };
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
vi.restoreAllMocks();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// Error boundaries recover on the client (React does not recover them during SSR
|
|
17
|
-
// `renderToString`), so these render into jsdom via `renderMarkdownClient`.
|
|
18
|
-
describe("Conformance / error isolation", () => {
|
|
19
|
-
it("renders a fallback for a throwing component instead of crashing", () => {
|
|
20
|
-
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
21
|
-
const html = renderMarkdownClient("<Boom />", { components });
|
|
22
|
-
expect(visibleText(html)).toContain("Failed to render component");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("still renders the rest of the document around a throwing component", () => {
|
|
26
|
-
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
27
|
-
const html = renderMarkdownClient("Before\n\n<Boom />\n\nAfter", { components });
|
|
28
|
-
const text = visibleText(html);
|
|
29
|
-
expect(text).toContain("Before");
|
|
30
|
-
expect(text).toContain("After");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("renders a fallback for a throwing code renderer", () => {
|
|
34
|
-
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
35
|
-
const html = renderMarkdownClient("```boom\ncode\n```", {
|
|
36
|
-
codeRenderers: {
|
|
37
|
-
boom: () => {
|
|
38
|
-
throw new Error("renderer boom");
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
expect(visibleText(html)).toContain("Failed to render code block");
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { renderMarkdown, visibleText } from "./render-helpers";
|
|
4
|
-
|
|
5
|
-
// This library disables CommonMark indented code blocks (see SPEC.md): leading
|
|
6
|
-
// indentation nests JSX children, never forms a code block.
|
|
7
|
-
describe("Conformance / indented code blocks are disabled", () => {
|
|
8
|
-
it("does not turn a 4-space indented line into a code block", () => {
|
|
9
|
-
const html = renderMarkdown(" not code");
|
|
10
|
-
expect(html).not.toContain("<pre");
|
|
11
|
-
expect(visibleText(html)).toContain("not code");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("does not turn a tab-indented line into a code block", () => {
|
|
15
|
-
const html = renderMarkdown("\tnot code");
|
|
16
|
-
expect(html).not.toContain("<pre");
|
|
17
|
-
expect(visibleText(html)).toContain("not code");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("does not turn a deeply indented (8-space) line into a code block", () => {
|
|
21
|
-
const html = renderMarkdown(" still not code");
|
|
22
|
-
expect(html).not.toContain("<pre");
|
|
23
|
-
expect(visibleText(html)).toContain("still not code");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("still renders an explicit fenced code block", () => {
|
|
27
|
-
const html = renderMarkdown("```\nreal code\n```");
|
|
28
|
-
expect(visibleText(html)).toContain("real code");
|
|
29
|
-
});
|
|
30
|
-
});
|