@surrealdb/ui 1.2.16 → 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.
Files changed (149) hide show
  1. package/dist/assets/1299e4695ad0bd61.svg +5 -0
  2. package/dist/assets/408117e904653548.svg +48 -0
  3. package/dist/assets/b516b020352f6369.svg +1 -0
  4. package/dist/assets/dfde8de566c3c2b9.svg +11 -0
  5. package/dist/assets/e9eae7bc863f211b.svg +1 -0
  6. package/dist/assets/f74ae3707a5d418f.svg +1 -0
  7. package/dist/ui.d.ts +12 -0
  8. package/dist/ui.js +659 -647
  9. package/dist/ui.js.map +1 -1
  10. package/package.json +8 -2
  11. package/.claude/launch.json +0 -17
  12. package/.oxfmtrc.json +0 -27
  13. package/.oxlintrc.json +0 -23
  14. package/.picto-fix-png/Cursor.png +0 -0
  15. package/.picto-fix-png/DocumentGradient.png +0 -0
  16. package/.picto-fix-png/FileGradient.png +0 -0
  17. package/.picto-fix-png/HealthBubbleGradient.png +0 -0
  18. package/.picto-fix-png/LearnGradient.png +0 -0
  19. package/.picto-fix-png/MediaFileGradient.png +0 -0
  20. package/.zed/settings.json +0 -36
  21. package/AGENTS.md +0 -111
  22. package/CLAUDE.md +0 -111
  23. package/REVIEW.md +0 -81
  24. package/doc/code-style.md +0 -264
  25. package/doc/release.md +0 -102
  26. package/doc/stacker-plan.md +0 -219
  27. package/release-notes.md +0 -39
  28. package/tests/_setup/e2e-helpers.tsx +0 -169
  29. package/tests/_setup/markdown-classes.ts +0 -3
  30. package/tests/_setup/portable-stories.ts +0 -33
  31. 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
  32. package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-renders-a-diagram-for-the-fenced-mermaid-block-1.png +0 -0
  33. package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-renders-a-horizontal-rule-as-a-separator-1.png +0 -0
  34. 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
  35. package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-shows-blockquote-text-from-the-sample-document-1.png +0 -0
  36. package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-shows-fenced-TypeScript-sample-code-in-the-document-1.png +0 -0
  37. 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
  38. 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
  39. package/tests/e2e/MarkdownEditor/__screenshots__/edits.test.tsx/MarkdownEditor---edits-reflects-typed-characters-in-the-underlying-document-1.png +0 -0
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. package/tests/e2e/MarkdownEditor/__screenshots__/media-sizing.test.tsx/MarkdownEditor---media-sizing-matches-MarkdownViewer-image-width-in-the-split-playground-1.png +0 -0
  58. package/tests/e2e/MarkdownEditor/__screenshots__/modes.test.tsx/MarkdownEditor---modes-standalone-MarkdownViewer-is-not-a-CodeMirror-surface-1.png +0 -0
  59. package/tests/e2e/MarkdownEditor/__screenshots__/modes.test.tsx/MarkdownEditor---modes-toggling-the-SegmentedControl-swaps-mode-without-remounting-the-editor-1.png +0 -0
  60. package/tests/e2e/MarkdownEditor/__screenshots__/regressions.test.tsx/MarkdownEditor---regressions-repeated-checkbox-toggles-do-not-duplicate-or-drift-task-markers-1.png +0 -0
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. package/tests/e2e/MarkdownEditor/__screenshots__/undo-redo.test.tsx/MarkdownEditor---undo-and-redo-restores-undone-document-edits-via-redo-1.png +0 -0
  68. package/tests/e2e/MarkdownEditor/content-blocks.test.tsx +0 -153
  69. package/tests/e2e/MarkdownEditor/edits.test.tsx +0 -112
  70. package/tests/e2e/MarkdownEditor/heading-fold.test.tsx +0 -45
  71. package/tests/e2e/MarkdownEditor/hybrid-widgets.test.tsx +0 -193
  72. package/tests/e2e/MarkdownEditor/image-on-image.test.tsx +0 -73
  73. package/tests/e2e/MarkdownEditor/jsx-block-content.test.tsx +0 -243
  74. package/tests/e2e/MarkdownEditor/jsx-highlight.test.tsx +0 -69
  75. package/tests/e2e/MarkdownEditor/jsx-inline-badges.test.tsx +0 -60
  76. package/tests/e2e/MarkdownEditor/jsx-inline-click.test.tsx +0 -54
  77. package/tests/e2e/MarkdownEditor/jsx-selection.test.tsx +0 -44
  78. package/tests/e2e/MarkdownEditor/link-placeholder.test.tsx +0 -68
  79. package/tests/e2e/MarkdownEditor/media-align.test.tsx +0 -58
  80. package/tests/e2e/MarkdownEditor/media-edit.test.tsx +0 -64
  81. package/tests/e2e/MarkdownEditor/media-sizing.test.tsx +0 -124
  82. package/tests/e2e/MarkdownEditor/modes.test.tsx +0 -94
  83. package/tests/e2e/MarkdownEditor/onimage-parity.test.tsx +0 -50
  84. package/tests/e2e/MarkdownEditor/regressions.test.tsx +0 -223
  85. package/tests/e2e/MarkdownEditor/slash-commands.test.tsx +0 -100
  86. package/tests/e2e/MarkdownEditor/table-click.test.tsx +0 -48
  87. package/tests/e2e/MarkdownEditor/table-controls.test.tsx +0 -57
  88. package/tests/e2e/MarkdownEditor/table-format.test.tsx +0 -42
  89. package/tests/e2e/MarkdownEditor/undo-redo.test.tsx +0 -39
  90. package/tests/e2e/MarkdownViewer/__screenshots__/parity.test.tsx/MarkdownViewer---editor-parity-matches-computed-font-size-on-first-heading-1.png +0 -0
  91. package/tests/e2e/MarkdownViewer/__screenshots__/parity.test.tsx/MarkdownViewer---editor-parity-matches-counts-for-shared-structural-classes-1.png +0 -0
  92. package/tests/e2e/MarkdownViewer/__screenshots__/parity.test.tsx/MarkdownViewer---editor-parity-matches-visible-text-between-preview-editor--blurred--and-MarkdownViewer-1.png +0 -0
  93. package/tests/e2e/MarkdownViewer/__screenshots__/render.test.tsx/MarkdownViewer---render-exercises-shared-preview-class-names-without-mounting-CodeMirror-1.png +0 -0
  94. package/tests/e2e/MarkdownViewer/parity.test.tsx +0 -191
  95. package/tests/e2e/MarkdownViewer/render.test.tsx +0 -36
  96. package/tests/e2e/Stacker/stacker.test.tsx +0 -304
  97. package/tests/unit/Editor/helpers.test.ts +0 -42
  98. package/tests/unit/MarkdownConformance/error-isolation.test.tsx +0 -44
  99. package/tests/unit/MarkdownConformance/indentation.test.tsx +0 -30
  100. package/tests/unit/MarkdownConformance/inline-code-fuzz.test.tsx +0 -255
  101. package/tests/unit/MarkdownConformance/jsx-attributes.test.tsx +0 -57
  102. package/tests/unit/MarkdownConformance/jsx-fuzz.test.tsx +0 -165
  103. package/tests/unit/MarkdownConformance/links.test.tsx +0 -95
  104. package/tests/unit/MarkdownConformance/render-helpers.tsx +0 -88
  105. package/tests/unit/MarkdownEditor/code-info.test.ts +0 -63
  106. package/tests/unit/MarkdownEditor/decorations.test.ts +0 -490
  107. package/tests/unit/MarkdownEditor/editor-ready.test.ts +0 -36
  108. package/tests/unit/MarkdownEditor/html-descriptors.test.ts +0 -94
  109. package/tests/unit/MarkdownEditor/indented-fence.test.ts +0 -103
  110. package/tests/unit/MarkdownEditor/jsx-attr-scan.test.ts +0 -115
  111. package/tests/unit/MarkdownEditor/jsx-block-widget.test.ts +0 -92
  112. package/tests/unit/MarkdownEditor/jsx-tag-grammar.test.ts +0 -88
  113. package/tests/unit/MarkdownEditor/list-indent.test.ts +0 -95
  114. package/tests/unit/MarkdownEditor/slash-commands.test.ts +0 -213
  115. package/tests/unit/MarkdownEditor/table-format.test.ts +0 -83
  116. package/tests/unit/MarkdownEditor/table.test.ts +0 -119
  117. package/tests/unit/MarkdownEditor/triggers.test.ts +0 -244
  118. package/tests/unit/MarkdownEditor/widget-store.test.ts +0 -105
  119. package/tests/unit/MarkdownViewer/callout.test.tsx +0 -60
  120. package/tests/unit/MarkdownViewer/code-highlight.test.tsx +0 -61
  121. package/tests/unit/MarkdownViewer/code-title.test.tsx +0 -62
  122. package/tests/unit/MarkdownViewer/features.test.tsx +0 -164
  123. package/tests/unit/MarkdownViewer/headings.test.tsx +0 -40
  124. package/tests/unit/MarkdownViewer/indented-jsx-children.test.tsx +0 -98
  125. package/tests/unit/MarkdownViewer/jsx-block-nesting.test.tsx +0 -234
  126. package/tests/unit/MarkdownViewer/jsx.test.tsx +0 -211
  127. package/tests/unit/MarkdownViewer/list-bullets.test.tsx +0 -49
  128. package/tests/unit/MarkdownViewer/list-code.test.tsx +0 -65
  129. package/tests/unit/MarkdownViewer/preserve-newlines.test.tsx +0 -143
  130. package/tests/unit/MarkdownViewer/read-time.test.ts +0 -21
  131. package/tests/unit/MarkdownViewer/renderers.test.tsx +0 -95
  132. package/tests/unit/MarkdownViewer/runnable.test.tsx +0 -69
  133. package/tests/unit/MarkdownViewer/ssr.test.tsx +0 -94
  134. package/tests/unit/MarkdownViewer/syntax-highlighting.test.tsx +0 -74
  135. package/tests/unit/MarkdownViewer/tabs-fenced-code.test.tsx +0 -196
  136. package/tests/unit/MarkdownViewer/tabs.test.tsx +0 -116
  137. package/tests/unit/Stacker/drag.test.ts +0 -21
  138. package/tests/unit/Stacker/helpers.test.ts +0 -105
  139. package/tests/unit/Stacker/reducer.test.ts +0 -107
  140. package/tests/unit/Syntax/language.test.ts +0 -63
  141. package/tools/duplicate-vector-mappings.json +0 -143
  142. package/tools/glass-export-overrides.json +0 -8
  143. package/tools/icon-parser.ts +0 -78
  144. package/tools/integration-gradient-exports.json +0 -57
  145. package/tools/picto-compare.ts +0 -64
  146. package/tools/picto-merge-urls.ts +0 -43
  147. package/tools/picto-sync.ts +0 -630
  148. package/tools/picto-verify-gradients.ts +0 -86
  149. 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
- });