@surrealdb/ui 1.2.16 → 1.2.18

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