@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,234 +0,0 @@
|
|
|
1
|
-
import { MantineProvider, v8CssVariablesResolver } from "@mantine/core";
|
|
2
|
-
import type { MarkdownComponents } from "@src/lib/markdown";
|
|
3
|
-
import { MarkdownViewer } from "@src/primitives/MarkdownViewer";
|
|
4
|
-
import { MANTINE_THEME } from "@src/theme/mantine";
|
|
5
|
-
import { Children, isValidElement, type ReactElement, type ReactNode } from "react";
|
|
6
|
-
import { renderToString } from "react-dom/server";
|
|
7
|
-
import { beforeAll, describe, expect, it } from "vitest";
|
|
8
|
-
|
|
9
|
-
beforeAll(() => {
|
|
10
|
-
if (typeof window !== "undefined" && typeof window.matchMedia !== "function") {
|
|
11
|
-
Object.defineProperty(window, "matchMedia", {
|
|
12
|
-
writable: true,
|
|
13
|
-
value: (query: string) => ({
|
|
14
|
-
matches: false,
|
|
15
|
-
media: query,
|
|
16
|
-
onchange: null,
|
|
17
|
-
addListener: () => {},
|
|
18
|
-
removeListener: () => {},
|
|
19
|
-
addEventListener: () => {},
|
|
20
|
-
removeEventListener: () => {},
|
|
21
|
-
dispatchEvent: () => false,
|
|
22
|
-
}),
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
function shell(node: ReactElement): string {
|
|
28
|
-
return renderToString(
|
|
29
|
-
<MantineProvider
|
|
30
|
-
theme={MANTINE_THEME}
|
|
31
|
-
cssVariablesResolver={v8CssVariablesResolver}
|
|
32
|
-
forceColorScheme="dark"
|
|
33
|
-
>
|
|
34
|
-
{node}
|
|
35
|
-
</MantineProvider>,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* A `<Tabs>`-shaped block component that renders every item's label AND content
|
|
41
|
-
* (unlike Mantine's `Tabs`, which omits inactive panels in SSR). Lets a single
|
|
42
|
-
* `renderToString` assert the full parsed structure of all tab bodies.
|
|
43
|
-
*/
|
|
44
|
-
function EagerGroup({ children }: { children?: ReactNode }): ReactElement {
|
|
45
|
-
const items = Children.toArray(children).filter(isValidElement);
|
|
46
|
-
return (
|
|
47
|
-
<div data-eager-group>
|
|
48
|
-
<div
|
|
49
|
-
data-labels={items.map((el) => (el.props as { label?: string }).label).join("|")}
|
|
50
|
-
/>
|
|
51
|
-
{items.map((el, i) => (
|
|
52
|
-
<section
|
|
53
|
-
data-panel={i}
|
|
54
|
-
key={(el.props as { label?: string }).label ?? i}
|
|
55
|
-
>
|
|
56
|
-
{(el.props as { children?: ReactNode }).children}
|
|
57
|
-
</section>
|
|
58
|
-
))}
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function EagerItem({ children }: { children?: ReactNode }): ReactElement {
|
|
64
|
-
return <>{children}</>;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const EAGER_COMPONENTS: MarkdownComponents = {
|
|
68
|
-
Tabs: { component: EagerGroup, block: true },
|
|
69
|
-
TabItem: { component: EagerItem, block: true },
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
function stripTags(html: string): string {
|
|
73
|
-
return html.replace(/<[^>]+>/g, "");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
describe("MarkdownViewer / JSX block nesting", () => {
|
|
77
|
-
it("pairs a Tabs block into exactly its authored TabItems (no synthetic tabs)", () => {
|
|
78
|
-
const content = `<Tabs syncKey="demo">
|
|
79
|
-
<TabItem label="Create an instance">
|
|
80
|
-
\tSurrealDB Cloud allows you to connect.
|
|
81
|
-
|
|
82
|
-
\t**1. Create Instance.**
|
|
83
|
-
|
|
84
|
-
\tWhen creating an instance, you enter the name.
|
|
85
|
-
</TabItem>
|
|
86
|
-
<TabItem label="Connect to your instance">
|
|
87
|
-
\tYou can connect via [Surrealist](/docs/x).
|
|
88
|
-
|
|
89
|
-
\tSince SurrealDB Cloud is closely integrated.
|
|
90
|
-
</TabItem>
|
|
91
|
-
</Tabs>`;
|
|
92
|
-
const html = shell(<MarkdownViewer content={content} />);
|
|
93
|
-
|
|
94
|
-
// Exactly two tab buttons with the authored labels — no paragraphs
|
|
95
|
-
// leaking out as extra "Tab N" children.
|
|
96
|
-
const tabButtons = html.match(/mantine-Tabs-tab\b/g) ?? [];
|
|
97
|
-
expect(tabButtons).toHaveLength(2);
|
|
98
|
-
const labels = [...html.matchAll(/mantine-Tabs-tabLabel">([^<]*)</g)].map((m) => m[1]);
|
|
99
|
-
expect(labels).toEqual(["Create an instance", "Connect to your instance"]);
|
|
100
|
-
|
|
101
|
-
// The active tab renders its full multi-paragraph markdown body.
|
|
102
|
-
const text = stripTags(html);
|
|
103
|
-
expect(text).toContain("SurrealDB Cloud allows you to connect.");
|
|
104
|
-
expect(text).toContain("1. Create Instance.");
|
|
105
|
-
expect(text).toContain("When creating an instance");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("renders full multi-paragraph markdown for every TabItem (via eager group)", () => {
|
|
109
|
-
const content = `<Tabs syncKey="demo">
|
|
110
|
-
<TabItem label="Create an instance">
|
|
111
|
-
\tSurrealDB Cloud allows you to connect.
|
|
112
|
-
|
|
113
|
-
\t**1. Create Instance.**
|
|
114
|
-
|
|
115
|
-
\tWhen creating an instance, you enter the name.
|
|
116
|
-
</TabItem>
|
|
117
|
-
<TabItem label="Connect to your instance">
|
|
118
|
-
\tYou can connect via [Surrealist](/docs/x).
|
|
119
|
-
|
|
120
|
-
\t[Connect via Surrealist](/docs/y)
|
|
121
|
-
|
|
122
|
-
\tSince SurrealDB Cloud is closely integrated.
|
|
123
|
-
</TabItem>
|
|
124
|
-
</Tabs>`;
|
|
125
|
-
const html = shell(
|
|
126
|
-
<MarkdownViewer
|
|
127
|
-
content={content}
|
|
128
|
-
components={EAGER_COMPONENTS}
|
|
129
|
-
/>,
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
expect(html).toContain('data-labels="Create an instance|Connect to your instance"');
|
|
133
|
-
expect(html).not.toContain("Tab 2");
|
|
134
|
-
|
|
135
|
-
// Both panels rendered, in order, with all their paragraphs.
|
|
136
|
-
const text = stripTags(html);
|
|
137
|
-
expect(text).toContain("SurrealDB Cloud allows you to connect.");
|
|
138
|
-
expect(text).toContain("1. Create Instance.");
|
|
139
|
-
expect(text).toContain("When creating an instance");
|
|
140
|
-
expect(text).toContain("You can connect via Surrealist");
|
|
141
|
-
expect(text).toContain("closely integrated");
|
|
142
|
-
// Links inside both tabs survive.
|
|
143
|
-
expect(html).toContain('href="/docs/x"');
|
|
144
|
-
expect(html).toContain('href="/docs/y"');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("renders a single-paragraph Tabs block (regression)", () => {
|
|
148
|
-
const content = `<Tabs syncKey="s">
|
|
149
|
-
<TabItem label="First">
|
|
150
|
-
Alpha content.
|
|
151
|
-
</TabItem>
|
|
152
|
-
<TabItem label="Second">
|
|
153
|
-
Beta content.
|
|
154
|
-
</TabItem>
|
|
155
|
-
</Tabs>`;
|
|
156
|
-
const html = shell(
|
|
157
|
-
<MarkdownViewer
|
|
158
|
-
content={content}
|
|
159
|
-
components={EAGER_COMPONENTS}
|
|
160
|
-
/>,
|
|
161
|
-
);
|
|
162
|
-
expect(html).toContain('data-labels="First|Second"');
|
|
163
|
-
expect(html).not.toContain("Tab 2");
|
|
164
|
-
const text = stripTags(html);
|
|
165
|
-
expect(text).toContain("Alpha content.");
|
|
166
|
-
expect(text).toContain("Beta content.");
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("renders block content around a Tabs block", () => {
|
|
170
|
-
const content = `# Heading
|
|
171
|
-
|
|
172
|
-
Intro paragraph.
|
|
173
|
-
|
|
174
|
-
<Tabs syncKey="s">
|
|
175
|
-
<TabItem label="One">
|
|
176
|
-
First tab.
|
|
177
|
-
</TabItem>
|
|
178
|
-
<TabItem label="Two">
|
|
179
|
-
Second tab.
|
|
180
|
-
</TabItem>
|
|
181
|
-
</Tabs>
|
|
182
|
-
|
|
183
|
-
Outro paragraph.`;
|
|
184
|
-
const html = shell(
|
|
185
|
-
<MarkdownViewer
|
|
186
|
-
content={content}
|
|
187
|
-
components={EAGER_COMPONENTS}
|
|
188
|
-
/>,
|
|
189
|
-
);
|
|
190
|
-
const text = stripTags(html);
|
|
191
|
-
expect(text).toContain("Heading");
|
|
192
|
-
expect(text).toContain("Intro paragraph.");
|
|
193
|
-
expect(text).toContain("Outro paragraph.");
|
|
194
|
-
expect(html).toContain('data-labels="One|Two"');
|
|
195
|
-
expect(text).toContain("First tab.");
|
|
196
|
-
expect(text).toContain("Second tab.");
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("renders nested block components (Tabs inside a TabItem)", () => {
|
|
200
|
-
const content = `<Tabs syncKey="outer">
|
|
201
|
-
<TabItem label="Outer A">
|
|
202
|
-
Outer A body.
|
|
203
|
-
|
|
204
|
-
<Tabs syncKey="inner">
|
|
205
|
-
<TabItem label="Inner X">
|
|
206
|
-
Inner X body.
|
|
207
|
-
</TabItem>
|
|
208
|
-
<TabItem label="Inner Y">
|
|
209
|
-
Inner Y body.
|
|
210
|
-
</TabItem>
|
|
211
|
-
</Tabs>
|
|
212
|
-
</TabItem>
|
|
213
|
-
<TabItem label="Outer B">
|
|
214
|
-
Outer B body.
|
|
215
|
-
</TabItem>
|
|
216
|
-
</Tabs>`;
|
|
217
|
-
const html = shell(
|
|
218
|
-
<MarkdownViewer
|
|
219
|
-
content={content}
|
|
220
|
-
components={EAGER_COMPONENTS}
|
|
221
|
-
/>,
|
|
222
|
-
);
|
|
223
|
-
// Outer group has exactly the two authored tabs.
|
|
224
|
-
expect(html).toContain('data-labels="Outer A|Outer B"');
|
|
225
|
-
// Inner group nested inside Outer A has its own two tabs.
|
|
226
|
-
expect(html).toContain('data-labels="Inner X|Inner Y"');
|
|
227
|
-
expect(html).not.toContain("Tab 2");
|
|
228
|
-
const text = stripTags(html);
|
|
229
|
-
expect(text).toContain("Outer A body.");
|
|
230
|
-
expect(text).toContain("Outer B body.");
|
|
231
|
-
expect(text).toContain("Inner X body.");
|
|
232
|
-
expect(text).toContain("Inner Y body.");
|
|
233
|
-
});
|
|
234
|
-
});
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { MantineProvider, v8CssVariablesResolver } from "@mantine/core";
|
|
2
|
-
import { extractHeadings, markdownSourceFromString, parseMarkdownTree } from "@src/lib/markdown";
|
|
3
|
-
import { MarkdownViewer } from "@src/primitives/MarkdownViewer";
|
|
4
|
-
import { MANTINE_THEME } from "@src/theme/mantine";
|
|
5
|
-
import type { ReactElement } from "react";
|
|
6
|
-
import { renderToString } from "react-dom/server";
|
|
7
|
-
import { beforeAll, describe, expect, it } from "vitest";
|
|
8
|
-
|
|
9
|
-
beforeAll(() => {
|
|
10
|
-
if (typeof window !== "undefined" && typeof window.matchMedia !== "function") {
|
|
11
|
-
Object.defineProperty(window, "matchMedia", {
|
|
12
|
-
writable: true,
|
|
13
|
-
value: (query: string) => ({
|
|
14
|
-
matches: false,
|
|
15
|
-
media: query,
|
|
16
|
-
onchange: null,
|
|
17
|
-
addListener: () => {},
|
|
18
|
-
removeListener: () => {},
|
|
19
|
-
addEventListener: () => {},
|
|
20
|
-
removeEventListener: () => {},
|
|
21
|
-
dispatchEvent: () => false,
|
|
22
|
-
}),
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
function shell(node: ReactElement): string {
|
|
28
|
-
return renderToString(
|
|
29
|
-
<MantineProvider
|
|
30
|
-
theme={MANTINE_THEME}
|
|
31
|
-
cssVariablesResolver={v8CssVariablesResolver}
|
|
32
|
-
forceColorScheme="dark"
|
|
33
|
-
>
|
|
34
|
-
{node}
|
|
35
|
-
</MantineProvider>,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe("MarkdownViewer / JSX and HTML", () => {
|
|
40
|
-
it("renders a JSX component when jsxMode is render", () => {
|
|
41
|
-
const html = shell(<MarkdownViewer content={'<Since v="2.0.0" />'} />);
|
|
42
|
-
expect(html).toContain("2.0.0");
|
|
43
|
-
expect(html).not.toContain("<Since");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("shows graceful fallback when jsxMode is graceful and component is missing", () => {
|
|
47
|
-
const html = shell(
|
|
48
|
-
<MarkdownViewer
|
|
49
|
-
content="<UnknownComponent />"
|
|
50
|
-
jsxMode="graceful"
|
|
51
|
-
/>,
|
|
52
|
-
);
|
|
53
|
-
expect(html).toContain("unsupported JSX");
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("parses JSON attribute values on JSX components", () => {
|
|
57
|
-
function ValueEcho({ value }: { value: unknown }) {
|
|
58
|
-
return <span data-value={JSON.stringify(value)} />;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const html = shell(
|
|
62
|
-
<MarkdownViewer
|
|
63
|
-
content={"<ValueEcho value={42} />"}
|
|
64
|
-
jsxMode="render"
|
|
65
|
-
components={{ ValueEcho }}
|
|
66
|
-
/>,
|
|
67
|
-
);
|
|
68
|
-
expect(html).toContain("data-value");
|
|
69
|
-
expect(html).toContain("42");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("leaves inline brace text as plain text", () => {
|
|
73
|
-
const html = shell(<MarkdownViewer content="Count: {count}" />);
|
|
74
|
-
expect(html).toContain("{count}");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("renders details/summary HTML blocks", () => {
|
|
78
|
-
const html = shell(
|
|
79
|
-
<MarkdownViewer
|
|
80
|
-
content={`<details>
|
|
81
|
-
<summary>Toggle</summary>
|
|
82
|
-
|
|
83
|
-
Hidden **bold** text
|
|
84
|
-
|
|
85
|
-
</details>`}
|
|
86
|
-
jsxMode="render"
|
|
87
|
-
/>,
|
|
88
|
-
);
|
|
89
|
-
expect(html).toContain("Toggle");
|
|
90
|
-
expect(html).toContain("bold");
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("extractHeadings returns h2 and h3 slugs", () => {
|
|
94
|
-
const source = "## Section\n\n### Sub\n";
|
|
95
|
-
const tree = parseMarkdownTree(source);
|
|
96
|
-
const headings = extractHeadings(tree, markdownSourceFromString(source));
|
|
97
|
-
expect(headings).toHaveLength(2);
|
|
98
|
-
expect(headings[0]?.text).toBe("Section");
|
|
99
|
-
expect(headings[1]?.text).toBe("Sub");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("onResolveMedia rewrites image src", () => {
|
|
103
|
-
const html = shell(
|
|
104
|
-
<MarkdownViewer
|
|
105
|
-
content='<img src="@ui/pictoAI" />'
|
|
106
|
-
jsxMode="render"
|
|
107
|
-
onResolveMedia={(node) => ({ ...node, src: "https://example.com/resolved.png" })}
|
|
108
|
-
/>,
|
|
109
|
-
);
|
|
110
|
-
expect(html).toContain("https://example.com/resolved.png");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("renders inline paired HTML anchor without duplicating link text", () => {
|
|
114
|
-
const html = shell(
|
|
115
|
-
<MarkdownViewer content={'Visit <a href="https://example.com">link text</a> here.'} />,
|
|
116
|
-
);
|
|
117
|
-
const matches = html.match(/link text/g) ?? [];
|
|
118
|
-
expect(matches).toHaveLength(1);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("throws when jsxMode is throw and a JSX component is unsupported", () => {
|
|
122
|
-
expect(() =>
|
|
123
|
-
shell(
|
|
124
|
-
<MarkdownViewer
|
|
125
|
-
content="<Foo />"
|
|
126
|
-
jsxMode="throw"
|
|
127
|
-
/>,
|
|
128
|
-
),
|
|
129
|
-
).toThrow(/Unsupported JSX component <Foo \/>/);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("omits unsupported JSX when jsxMode is omit", () => {
|
|
133
|
-
const html = shell(
|
|
134
|
-
<MarkdownViewer
|
|
135
|
-
content="<Unknown />"
|
|
136
|
-
jsxMode="omit"
|
|
137
|
-
/>,
|
|
138
|
-
);
|
|
139
|
-
expect(html).not.toContain("unsupported");
|
|
140
|
-
expect(html).not.toContain("Unsupported JSX");
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("adds heading id and autolink anchor by default", () => {
|
|
144
|
-
const html = shell(<MarkdownViewer content="## Hello" />);
|
|
145
|
-
expect(html).toMatch(/id="hello"/);
|
|
146
|
-
expect(html).toContain('href="#hello"');
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it("renders the same output when tree and source are passed via tree prop", () => {
|
|
150
|
-
const content = "# Tree prop\n\nParagraph body.";
|
|
151
|
-
const tree = parseMarkdownTree(content);
|
|
152
|
-
const source = markdownSourceFromString(content);
|
|
153
|
-
const fromTree = shell(<MarkdownViewer tree={{ tree, source }} />);
|
|
154
|
-
const fromContent = shell(<MarkdownViewer content={content} />);
|
|
155
|
-
expect(fromTree).toBe(fromContent);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("uses paragraph renderer override when provided", () => {
|
|
159
|
-
const html = shell(
|
|
160
|
-
<MarkdownViewer
|
|
161
|
-
content="Custom paragraph."
|
|
162
|
-
renderers={{
|
|
163
|
-
paragraph: () => <span data-custom-paragraph>PARAGRAPH_MARKER</span>,
|
|
164
|
-
}}
|
|
165
|
-
/>,
|
|
166
|
-
);
|
|
167
|
-
expect(html).toContain("PARAGRAPH_MARKER");
|
|
168
|
-
expect(html).not.toContain("Custom paragraph.");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("renders multi-line paragraph JSX tags", () => {
|
|
172
|
-
const html = shell(<MarkdownViewer content={'<Since\n v="2.0.0"\n/>'} />);
|
|
173
|
-
expect(html).toContain("2.0.0");
|
|
174
|
-
expect(html).not.toContain("<Since");
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("renders multiple JSX components on one line", () => {
|
|
178
|
-
const html = shell(
|
|
179
|
-
<MarkdownViewer content={'<Label label="Required" /><Label label="Optional" />'} />,
|
|
180
|
-
);
|
|
181
|
-
expect(html.match(/mantine-Badge-label/g)?.length).toBe(2);
|
|
182
|
-
expect(html).toContain("Required");
|
|
183
|
-
expect(html).toContain("Optional");
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it("renders inline HTML on separate source lines with line breaks", () => {
|
|
187
|
-
const html = shell(
|
|
188
|
-
<MarkdownViewer
|
|
189
|
-
content={`<span>foo</span> <span>bar</span>
|
|
190
|
-
<span>hello</span>
|
|
191
|
-
<span>world</span>`}
|
|
192
|
-
/>,
|
|
193
|
-
);
|
|
194
|
-
expect(html).toContain("foo");
|
|
195
|
-
expect(html).toContain("bar");
|
|
196
|
-
expect(html).toContain("hello");
|
|
197
|
-
expect(html).toContain("world");
|
|
198
|
-
expect(html.match(/<br\s*\/?>/g)?.length).toBe(2);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it("renders jsx-only paragraphs on separate lines as separate p elements", () => {
|
|
202
|
-
const html = shell(
|
|
203
|
-
<MarkdownViewer
|
|
204
|
-
content={'<Since v="1.5.0" />\n\n<Since v="2.0.0" prefix="Available since" />'}
|
|
205
|
-
/>,
|
|
206
|
-
);
|
|
207
|
-
expect(html.match(/mantine-Badge-label/g)?.length).toBe(2);
|
|
208
|
-
const paragraphs = html.match(/<p[^>]*>[\s\S]*?<\/p>/g) ?? [];
|
|
209
|
-
expect(paragraphs.length).toBeGreaterThanOrEqual(2);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { MantineProvider, v8CssVariablesResolver } from "@mantine/core";
|
|
2
|
-
import { unorderedBulletForDepth } from "@src/lib/markdown/view/components/ListBullet";
|
|
3
|
-
import { MarkdownViewer } from "@src/primitives/MarkdownViewer";
|
|
4
|
-
import { MANTINE_THEME } from "@src/theme/mantine";
|
|
5
|
-
import type { ReactElement } from "react";
|
|
6
|
-
import { renderToString } from "react-dom/server";
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
8
|
-
|
|
9
|
-
function shell(node: ReactElement): string {
|
|
10
|
-
return renderToString(
|
|
11
|
-
<MantineProvider
|
|
12
|
-
theme={MANTINE_THEME}
|
|
13
|
-
cssVariablesResolver={v8CssVariablesResolver}
|
|
14
|
-
forceColorScheme="dark"
|
|
15
|
-
>
|
|
16
|
-
{node}
|
|
17
|
-
</MantineProvider>,
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
describe("unorderedBulletForDepth", () => {
|
|
22
|
-
it("cycles through bullet, circle, square by depth", () => {
|
|
23
|
-
expect(unorderedBulletForDepth(0)).toBe("•");
|
|
24
|
-
expect(unorderedBulletForDepth(1)).toBe("◦");
|
|
25
|
-
expect(unorderedBulletForDepth(2)).toBe("▪");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("repeats the sequence for deeper nesting", () => {
|
|
29
|
-
expect(unorderedBulletForDepth(3)).toBe("•");
|
|
30
|
-
expect(unorderedBulletForDepth(4)).toBe("◦");
|
|
31
|
-
expect(unorderedBulletForDepth(5)).toBe("▪");
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe("MarkdownViewer / nested list bullets", () => {
|
|
36
|
-
it("renders distinct glyphs per nesting level", () => {
|
|
37
|
-
const content = ["- top", " - second", " - third"].join("\n");
|
|
38
|
-
const html = shell(<MarkdownViewer content={content} />);
|
|
39
|
-
expect(html).toContain("•");
|
|
40
|
-
expect(html).toContain("◦");
|
|
41
|
-
expect(html).toContain("▪");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("keeps ordered list markers as numbers regardless of depth", () => {
|
|
45
|
-
const content = ["1. top", " 1. nested"].join("\n");
|
|
46
|
-
const html = shell(<MarkdownViewer content={content} />);
|
|
47
|
-
expect(html).not.toContain("◦");
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,65 +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 type { ReactElement } from "react";
|
|
5
|
-
import { renderToString } from "react-dom/server";
|
|
6
|
-
import { beforeAll, describe, expect, it } from "vitest";
|
|
7
|
-
|
|
8
|
-
beforeAll(() => {
|
|
9
|
-
if (typeof window !== "undefined" && typeof window.matchMedia !== "function") {
|
|
10
|
-
Object.defineProperty(window, "matchMedia", {
|
|
11
|
-
writable: true,
|
|
12
|
-
value: (query: string) => ({
|
|
13
|
-
matches: false,
|
|
14
|
-
media: query,
|
|
15
|
-
onchange: null,
|
|
16
|
-
addListener: () => {},
|
|
17
|
-
removeListener: () => {},
|
|
18
|
-
addEventListener: () => {},
|
|
19
|
-
removeEventListener: () => {},
|
|
20
|
-
dispatchEvent: () => false,
|
|
21
|
-
}),
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
function shell(node: ReactElement): string {
|
|
27
|
-
return renderToString(
|
|
28
|
-
<MantineProvider
|
|
29
|
-
theme={MANTINE_THEME}
|
|
30
|
-
cssVariablesResolver={v8CssVariablesResolver}
|
|
31
|
-
forceColorScheme="dark"
|
|
32
|
-
>
|
|
33
|
-
{node}
|
|
34
|
-
</MantineProvider>,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Syntax highlighting splits a line into per-token spans, so assert on single
|
|
39
|
-
// identifier tokens that survive tokenisation rather than whole expressions.
|
|
40
|
-
describe("MarkdownViewer / code inside lists", () => {
|
|
41
|
-
it("renders a fenced code block nested in a list item", () => {
|
|
42
|
-
const content = ["- Item with code:", " ```js", " alphaVar;", " ```"].join("\n");
|
|
43
|
-
const html = shell(<MarkdownViewer content={content} />);
|
|
44
|
-
expect(html).toContain("alphaVar");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("renders multiple code lines nested in a list item", () => {
|
|
48
|
-
const content = ["- Item:", " ```js", " alphaVar;", " betaVar;", " ```"].join("\n");
|
|
49
|
-
const html = shell(<MarkdownViewer content={content} />);
|
|
50
|
-
expect(html).toContain("alphaVar");
|
|
51
|
-
expect(html).toContain("betaVar");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("renders a code block that is the first child of a list item", () => {
|
|
55
|
-
const content = ["- ```ts", " codeFirstVar;", " ```"].join("\n");
|
|
56
|
-
const html = shell(<MarkdownViewer content={content} />);
|
|
57
|
-
expect(html).toContain("codeFirstVar");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("renders a fenced code block inside a task list item", () => {
|
|
61
|
-
const content = ["- [ ] Task with code", " ```js", " taskCodeVar;", " ```"].join("\n");
|
|
62
|
-
const html = shell(<MarkdownViewer content={content} />);
|
|
63
|
-
expect(html).toContain("taskCodeVar");
|
|
64
|
-
});
|
|
65
|
-
});
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { MantineProvider, v8CssVariablesResolver } from "@mantine/core";
|
|
2
|
-
import type { MarkdownComponents } from "@src/lib/markdown";
|
|
3
|
-
import { MarkdownViewer } from "@src/primitives/MarkdownViewer";
|
|
4
|
-
import { MANTINE_THEME } from "@src/theme/mantine";
|
|
5
|
-
import type { ReactElement, ReactNode } from "react";
|
|
6
|
-
import { renderToString } from "react-dom/server";
|
|
7
|
-
import { beforeAll, describe, expect, it } from "vitest";
|
|
8
|
-
|
|
9
|
-
beforeAll(() => {
|
|
10
|
-
if (typeof window !== "undefined" && typeof window.matchMedia !== "function") {
|
|
11
|
-
Object.defineProperty(window, "matchMedia", {
|
|
12
|
-
writable: true,
|
|
13
|
-
value: (query: string) => ({
|
|
14
|
-
matches: false,
|
|
15
|
-
media: query,
|
|
16
|
-
onchange: null,
|
|
17
|
-
addListener: () => {},
|
|
18
|
-
removeListener: () => {},
|
|
19
|
-
addEventListener: () => {},
|
|
20
|
-
removeEventListener: () => {},
|
|
21
|
-
dispatchEvent: () => false,
|
|
22
|
-
}),
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
function shell(node: ReactElement): string {
|
|
28
|
-
return renderToString(
|
|
29
|
-
<MantineProvider
|
|
30
|
-
theme={MANTINE_THEME}
|
|
31
|
-
cssVariablesResolver={v8CssVariablesResolver}
|
|
32
|
-
forceColorScheme="dark"
|
|
33
|
-
>
|
|
34
|
-
{node}
|
|
35
|
-
</MantineProvider>,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function TwoColumn({ children }: { children?: ReactNode }): ReactElement {
|
|
40
|
-
return <div data-testid="two-column">{children}</div>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function ColBox({ children }: { children?: ReactNode }): ReactElement {
|
|
44
|
-
return <div data-testid="col-box">{children}</div>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const LAYOUT = `<TwoColumn>
|
|
48
|
-
<ColBox></ColBox>
|
|
49
|
-
<ColBox></ColBox>
|
|
50
|
-
<ColBox></ColBox>
|
|
51
|
-
</TwoColumn>`;
|
|
52
|
-
|
|
53
|
-
describe("MarkdownViewer / custom component preserveNewlines", () => {
|
|
54
|
-
it("does not inject <br /> between block children (they render as blocks)", () => {
|
|
55
|
-
const components: MarkdownComponents = {
|
|
56
|
-
TwoColumn: { component: TwoColumn, block: true },
|
|
57
|
-
ColBox: { component: ColBox, block: true },
|
|
58
|
-
};
|
|
59
|
-
const html = shell(
|
|
60
|
-
<MarkdownViewer
|
|
61
|
-
content={LAYOUT}
|
|
62
|
-
components={components}
|
|
63
|
-
/>,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// Three boxes still render, in order.
|
|
67
|
-
expect(html.match(/data-testid="col-box"/g)?.length).toBe(3);
|
|
68
|
-
// Block-level children are carved out and rendered as their own blocks:
|
|
69
|
-
// the newlines that merely separate them must NOT become <br /> elements.
|
|
70
|
-
// A stray <br /> here would also be counted as an extra child by
|
|
71
|
-
// introspecting parents (e.g. <Tabs> reading each <TabItem>), so the two
|
|
72
|
-
// behaviours are unified — `preserveNewlines` governs prose line breaks
|
|
73
|
-
// (see the prose test below), not gaps between block children.
|
|
74
|
-
const inner = html.slice(
|
|
75
|
-
html.indexOf('data-testid="two-column"'),
|
|
76
|
-
html.lastIndexOf("</div>"),
|
|
77
|
-
);
|
|
78
|
-
expect(inner).not.toMatch(/<br\s*\/?>/);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("omits <br /> between block children when preserveNewlines is false", () => {
|
|
82
|
-
const components: MarkdownComponents = {
|
|
83
|
-
TwoColumn: { component: TwoColumn, block: true, preserveNewlines: false },
|
|
84
|
-
ColBox: { component: ColBox, block: true },
|
|
85
|
-
};
|
|
86
|
-
const html = shell(
|
|
87
|
-
<MarkdownViewer
|
|
88
|
-
content={LAYOUT}
|
|
89
|
-
components={components}
|
|
90
|
-
/>,
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
// All three boxes still render.
|
|
94
|
-
expect(html.match(/data-testid="col-box"/g)?.length).toBe(3);
|
|
95
|
-
// The TwoColumn wrapper is present.
|
|
96
|
-
expect(html).toContain('data-testid="two-column"');
|
|
97
|
-
|
|
98
|
-
// No stray line breaks injected between the child blocks.
|
|
99
|
-
const inner = html.slice(
|
|
100
|
-
html.indexOf('data-testid="two-column"'),
|
|
101
|
-
html.lastIndexOf("</div>"),
|
|
102
|
-
);
|
|
103
|
-
expect(inner).not.toMatch(/<br\s*\/?>/);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("still preserves newlines for prose components by default", () => {
|
|
107
|
-
function Prose({ children }: { children?: ReactNode }): ReactElement {
|
|
108
|
-
return <section data-testid="prose">{children}</section>;
|
|
109
|
-
}
|
|
110
|
-
const components: MarkdownComponents = {
|
|
111
|
-
Prose: { component: Prose, block: true },
|
|
112
|
-
};
|
|
113
|
-
const html = shell(
|
|
114
|
-
<MarkdownViewer
|
|
115
|
-
content={"<Prose>\nLine one\nLine two\n</Prose>"}
|
|
116
|
-
components={components}
|
|
117
|
-
/>,
|
|
118
|
-
);
|
|
119
|
-
expect(html).toContain("Line one");
|
|
120
|
-
expect(html).toContain("Line two");
|
|
121
|
-
// Multi-line prose keeps its line break.
|
|
122
|
-
expect(html).toMatch(/<br\s*\/?>/);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("disabling preserveNewlines collapses multi-line text to a single run", () => {
|
|
126
|
-
function Prose({ children }: { children?: ReactNode }): ReactElement {
|
|
127
|
-
return <section data-testid="prose">{children}</section>;
|
|
128
|
-
}
|
|
129
|
-
const components: MarkdownComponents = {
|
|
130
|
-
Prose: { component: Prose, block: true, preserveNewlines: false },
|
|
131
|
-
};
|
|
132
|
-
const html = shell(
|
|
133
|
-
<MarkdownViewer
|
|
134
|
-
content={"<Prose>\nLine one\nLine two\n</Prose>"}
|
|
135
|
-
components={components}
|
|
136
|
-
/>,
|
|
137
|
-
);
|
|
138
|
-
expect(html).toContain("Line one");
|
|
139
|
-
expect(html).toContain("Line two");
|
|
140
|
-
const inner = html.slice(html.indexOf('data-testid="prose"'));
|
|
141
|
-
expect(inner).not.toMatch(/<br\s*\/?>/);
|
|
142
|
-
});
|
|
143
|
-
});
|