@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,223 +0,0 @@
1
- import { EditorSelection } from "@codemirror/state";
2
- import { MantineProvider, v8CssVariablesResolver } from "@mantine/core";
3
- import { MarkdownEditor } from "@src/primitives/MarkdownEditor";
4
- import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
5
- import { MANTINE_THEME } from "@src/theme/mantine";
6
- import { composeStories } from "@storybook/react-vite";
7
- import { type ReactElement, useState } from "react";
8
- import { describe, expect, it } from "vitest";
9
-
10
- import {
11
- mountStory,
12
- nextFrame,
13
- preparePreviewEditor,
14
- scrollEditorTo,
15
- userEvent,
16
- } from "../../_setup/e2e-helpers";
17
-
18
- const Stories = composeStories(MarkdownStories);
19
-
20
- function getCheckboxes(): HTMLElement[] {
21
- return Array.from(document.querySelectorAll<HTMLElement>("[data-md-task-marker]"));
22
- }
23
-
24
- const SCROLL_FIXTURE = `# Scroll fixture
25
-
26
- ${Array.from({ length: 80 }, (_, i) => `Line ${i + 1} of fixture content.`).join("\n")}
27
- `;
28
-
29
- function ScrollFixture(): ReactElement {
30
- const [doc, setDoc] = useState(SCROLL_FIXTURE);
31
- // Mounting the primitive directly here means we have to provide our own
32
- // MantineProvider; portable-stories decorators do not apply to ad-hoc JSX.
33
- return (
34
- <MantineProvider
35
- theme={MANTINE_THEME}
36
- cssVariablesResolver={v8CssVariablesResolver}
37
- forceColorScheme="dark"
38
- >
39
- <div style={{ height: 240, overflow: "hidden" }}>
40
- <MarkdownEditor
41
- mode="preview"
42
- document={doc}
43
- onChangeDocument={setDoc}
44
- style={{ height: "100%" }}
45
- />
46
- </div>
47
- </MantineProvider>
48
- );
49
- }
50
-
51
- function WrapFixture(): ReactElement {
52
- const longParagraph = "Word ".repeat(120).trim();
53
- const [doc, setDoc] = useState(longParagraph);
54
-
55
- return (
56
- <MantineProvider
57
- theme={MANTINE_THEME}
58
- cssVariablesResolver={v8CssVariablesResolver}
59
- forceColorScheme="dark"
60
- >
61
- <div style={{ width: 360, overflow: "hidden" }}>
62
- <MarkdownEditor
63
- mode="preview"
64
- document={doc}
65
- onChangeDocument={setDoc}
66
- style={{ height: 240 }}
67
- />
68
- </div>
69
- </MantineProvider>
70
- );
71
- }
72
-
73
- describe("MarkdownEditor / regressions", () => {
74
- it("task checkbox toggles between checked and unchecked in the source document", async () => {
75
- mountStory(<Stories.Editor />);
76
- const view = await preparePreviewEditor();
77
- const taskPos = view.state.doc.toString().indexOf("- [ ] Read the docs");
78
-
79
- // Park caret outside the task list so checkbox widgets render for every item
80
- // (preview mode only swaps to raw `[…]` when the selection hits the marker).
81
- view.dispatch({ selection: EditorSelection.cursor(0) });
82
- await scrollEditorTo(view, taskPos);
83
- await nextFrame();
84
-
85
- const initialDoc = view.state.doc.toString();
86
- expect(initialDoc).toContain("- [ ] Read the docs");
87
- expect(initialDoc).toContain("- [x] Star the repo");
88
-
89
- let first: HTMLElement | undefined;
90
- for (let i = 0; i < 40; i++) {
91
- [first] = getCheckboxes();
92
- if (first) break;
93
- await nextFrame();
94
- }
95
- expect(first).toBeDefined();
96
- if (!first) throw new Error("no task marker rendered");
97
-
98
- await userEvent.click(first);
99
- await nextFrame();
100
- await nextFrame();
101
-
102
- expect(view.state.doc.toString()).toContain("- [x] Read the docs");
103
- expect(view.state.doc.toString()).not.toContain("- [ ] Read the docs");
104
-
105
- // Toggle again — single mousedown handler, no double-toggle on click.
106
- const [firstAgain] = getCheckboxes();
107
- if (!firstAgain) throw new Error("task marker disappeared after toggle");
108
- await userEvent.click(firstAgain);
109
- await nextFrame();
110
- await nextFrame();
111
- expect(view.state.doc.toString()).toContain("- [ ] Read the docs");
112
- });
113
-
114
- it("repeated checkbox toggles do not duplicate or drift task markers", async () => {
115
- mountStory(<Stories.Editor />);
116
- const view = await preparePreviewEditor();
117
- const taskPos = view.state.doc.toString().indexOf("- [ ] Read the docs");
118
-
119
- view.dispatch({ selection: EditorSelection.cursor(0) });
120
- await scrollEditorTo(view, taskPos);
121
- await nextFrame();
122
-
123
- const baselineCount = (view.state.doc.toString().match(/- \[[ x]\] /g) ?? []).length;
124
- expect(baselineCount).toBeGreaterThanOrEqual(3);
125
-
126
- let markers = getCheckboxes();
127
- for (let i = 0; i < 40 && markers.length < 2; i++) {
128
- await nextFrame();
129
- markers = getCheckboxes();
130
- }
131
-
132
- for (let i = 0; i < 6; i++) {
133
- markers = getCheckboxes();
134
- const target = markers[1];
135
- if (!target) throw new Error("task marker missing during loop");
136
- await userEvent.click(target);
137
- await nextFrame();
138
- await nextFrame();
139
- }
140
-
141
- const finalDoc = view.state.doc.toString();
142
- const finalCount = (finalDoc.match(/- \[[ x]\] /g) ?? []).length;
143
- expect(finalCount).toBe(baselineCount);
144
- // Star the repo started as `[x]`, so 6 toggles → still `[x]`.
145
- expect(finalDoc).toContain("- [x] Star the repo");
146
- });
147
-
148
- it("toggle via checkbox preserves task marker widgets visible from the previous caret position", async () => {
149
- mountStory(<Stories.Editor />);
150
- const view = await preparePreviewEditor();
151
- const taskPos = view.state.doc.toString().indexOf("- [ ] Read the docs");
152
-
153
- view.dispatch({ selection: EditorSelection.cursor(0) });
154
- await scrollEditorTo(view, taskPos);
155
- view.contentDOM.blur();
156
- await nextFrame();
157
-
158
- let initialMarkers = 0;
159
- for (let i = 0; i < 40; i++) {
160
- initialMarkers = getCheckboxes().length;
161
- if (initialMarkers >= 3) break;
162
- await nextFrame();
163
- }
164
- expect(initialMarkers).toBeGreaterThanOrEqual(3);
165
-
166
- const [marker] = getCheckboxes();
167
- if (!marker) throw new Error("no task marker");
168
- await userEvent.click(marker);
169
- await nextFrame();
170
- await nextFrame();
171
-
172
- // If the click had dropped the checkbox widget for that line, the count
173
- // would fall (caret would need to sit on the task marker for that).
174
- const afterMarkers = getCheckboxes().length;
175
- expect(afterMarkers).toBe(initialMarkers);
176
- });
177
-
178
- it("scroll position is preserved when an out-of-view edit is dispatched", async () => {
179
- mountStory(<ScrollFixture />);
180
- const view = await preparePreviewEditor();
181
- const scroller = document.querySelector<HTMLElement>(".cm-scroller");
182
- if (!scroller) throw new Error("no .cm-scroller in mounted story");
183
-
184
- // Wait for layout to settle before measuring scroll height.
185
- await nextFrame();
186
- await nextFrame();
187
- expect(scroller.scrollHeight).toBeGreaterThan(scroller.clientHeight);
188
-
189
- scroller.scrollTop = 200;
190
- await nextFrame();
191
- const scrollBefore = scroller.scrollTop;
192
- expect(scrollBefore).toBeGreaterThan(0);
193
-
194
- // Insert at offset 5 (well above the current viewport) without
195
- // moving the selection. The regression was: ANY content change
196
- // reset `.cm-scroller` scrollTop to 0. The fix preserves scroll
197
- // unless the dispatch explicitly requests a scrollIntoView.
198
- view.dispatch({ changes: { from: 5, insert: "X" } });
199
- await nextFrame();
200
- await nextFrame();
201
-
202
- expect(Math.abs(scroller.scrollTop - scrollBefore)).toBeLessThan(20);
203
- expect(scroller.scrollTop).toBeGreaterThan(0);
204
- });
205
-
206
- it("wraps long paragraph lines instead of extending the editor width", async () => {
207
- mountStory(<WrapFixture />);
208
- await nextFrame();
209
- await nextFrame();
210
-
211
- const scroller = document.querySelector<HTMLElement>(".cm-scroller");
212
- const content = document.querySelector<HTMLElement>(".cm-content");
213
-
214
- // `EditorView.lineWrapping` applies via contentAttributes, so the
215
- // `cm-lineWrapping` class lands on `.cm-content` (not the outer editor).
216
- expect(content?.classList.contains("cm-lineWrapping")).toBe(true);
217
- expect(scroller).toBeTruthy();
218
- expect(content).toBeTruthy();
219
- if (!scroller || !content) return;
220
-
221
- expect(content.scrollWidth).toBeLessThanOrEqual(scroller.clientWidth + 2);
222
- });
223
- });
@@ -1,100 +0,0 @@
1
- import { EditorSelection } from "@codemirror/state";
2
- import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
3
- import { composeStories } from "@storybook/react-vite";
4
- import { describe, expect, it } from "vitest";
5
-
6
- import {
7
- focusAndType,
8
- getEditorView,
9
- mountStory,
10
- nextFrame,
11
- readDoc,
12
- sleep,
13
- typeText,
14
- } from "../../_setup/e2e-helpers";
15
-
16
- const Stories = composeStories(MarkdownStories);
17
-
18
- /** Query the visible slash menu's command items (rendered in a portal). */
19
- function menuItems(): HTMLElement[] {
20
- return Array.from(document.querySelectorAll<HTMLElement>("[data-navigation-item-id]"));
21
- }
22
-
23
- /** Move the caret to a fresh empty line at the end of the document. */
24
- async function caretOnFreshLine() {
25
- const view = await getEditorView();
26
- view.focus();
27
- const end = view.state.doc.length;
28
- view.dispatch({
29
- changes: { from: end, insert: "\n\n" },
30
- selection: EditorSelection.cursor(end + 2),
31
- scrollIntoView: true,
32
- });
33
- await nextFrame();
34
- await nextFrame();
35
- return view;
36
- }
37
-
38
- describe("MarkdownEditor / slash commands", () => {
39
- it("opens a filtered, keyboard-navigable menu and inserts boilerplate on Enter", async () => {
40
- mountStory(<Stories.Editor />);
41
- await caretOnFreshLine();
42
-
43
- await focusAndType("/head");
44
- await sleep(50);
45
-
46
- const items = menuItems();
47
- expect(items.length).toBeGreaterThan(0);
48
- // Filtering by "head" should only surface heading commands.
49
- expect(items.every((el) => el.dataset.navigationItemId?.startsWith("heading"))).toBe(true);
50
-
51
- // Navigate to the second heading and select it.
52
- await typeText("{ArrowDown}");
53
- await sleep(20);
54
- await typeText("{Enter}");
55
- await sleep(50);
56
-
57
- const doc = await readDoc();
58
- // The "/head" text is replaced by a heading prefix; no stray newline.
59
- expect(doc).toContain("## ");
60
- expect(doc).not.toContain("/head");
61
- expect(menuItems().length).toBe(0);
62
- });
63
-
64
- it("dismisses on Escape and leaves the slash as literal text", async () => {
65
- mountStory(<Stories.Editor />);
66
- await caretOnFreshLine();
67
-
68
- await focusAndType("/quote");
69
- await sleep(50);
70
- expect(menuItems().length).toBeGreaterThan(0);
71
-
72
- await typeText("{Escape}");
73
- await sleep(50);
74
-
75
- expect(menuItems().length).toBe(0);
76
- const doc = await readDoc();
77
- expect(doc).toContain("/quote");
78
- });
79
-
80
- it("highlights the slash and shows an Enter command placeholder until text is typed", async () => {
81
- mountStory(<Stories.Editor />);
82
- await caretOnFreshLine();
83
-
84
- await focusAndType("/");
85
- await sleep(50);
86
-
87
- const highlight = document.querySelector(".cm-md-slash-active");
88
- expect(highlight).not.toBeNull();
89
- const placeholder = document.querySelector(".cm-md-slash-placeholder");
90
- expect(placeholder?.textContent).toBe("Type to search");
91
-
92
- // Typing the query removes the placeholder but keeps the highlight.
93
- await typeText("head");
94
- await sleep(50);
95
-
96
- expect(document.querySelector(".cm-md-slash-placeholder")).toBeNull();
97
- const stillHighlighted = document.querySelector(".cm-md-slash-active");
98
- expect(stillHighlighted?.textContent).toBe("/head");
99
- });
100
- });
@@ -1,48 +0,0 @@
1
- import { EditorSelection } from "@codemirror/state";
2
- import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
3
- import { composeStories } from "@storybook/react-vite";
4
- import { describe, expect, it } from "vitest";
5
-
6
- import {
7
- mountStory,
8
- nextFrame,
9
- page,
10
- preparePreviewEditor,
11
- scrollEditorTo,
12
- } from "../../_setup/e2e-helpers";
13
- import { md } from "../../_setup/markdown-classes";
14
-
15
- const Stories = composeStories(MarkdownStories);
16
-
17
- describe("MarkdownEditor / table cell click", () => {
18
- it("places the caret in the source cell when a rendered cell is clicked", async () => {
19
- mountStory(<Stories.Editor />);
20
- const view = await preparePreviewEditor();
21
- const doc = view.state.doc.toString();
22
-
23
- const tablePos = doc.indexOf("| Feature |");
24
- expect(tablePos).toBeGreaterThan(-1);
25
- await scrollEditorTo(view, tablePos);
26
-
27
- // Park the caret far away so the table renders as a widget.
28
- view.dispatch({ selection: EditorSelection.cursor(0) });
29
- await nextFrame();
30
- await nextFrame();
31
-
32
- const widget = document.querySelector(`.${md.tableWidget}`);
33
- expect(widget).not.toBeNull();
34
-
35
- // Click the "Headers" body cell.
36
- const cell = page.getByText("Headers", { exact: true });
37
- await cell.click();
38
- await nextFrame();
39
-
40
- // The caret should land on the "Headers" cell inside the table source.
41
- const expected = doc.indexOf("Headers", tablePos);
42
- expect(view.state.selection.main.head).toBe(expected);
43
-
44
- // Clicking activates the block, revealing the raw source (widget gone).
45
- await nextFrame();
46
- expect(document.querySelector(`.${md.tableWidget}`)).toBeNull();
47
- });
48
- });
@@ -1,57 +0,0 @@
1
- import { EditorSelection } from "@codemirror/state";
2
- import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
3
- import { composeStories } from "@storybook/react-vite";
4
- import { describe, expect, it } from "vitest";
5
-
6
- import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
7
-
8
- const Stories = composeStories(MarkdownStories);
9
-
10
- function clickControl(label: string): boolean {
11
- const button = document.querySelector<HTMLButtonElement>(`button[aria-label="${label}"]`);
12
- if (!button) return false;
13
- button.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0 }));
14
- button.dispatchEvent(new MouseEvent("click", { bubbles: true, button: 0 }));
15
- return true;
16
- }
17
-
18
- describe("MarkdownEditor / table controls", () => {
19
- it("adds and removes rows and columns from the active table", async () => {
20
- mountStory(<Stories.EditorTableFormatter />);
21
- const view = await getEditorView();
22
- await nextFrame();
23
-
24
- // Park the caret inside the table to reveal the floating controls.
25
- const adaPos = view.state.doc.toString().indexOf("Ada");
26
- view.dispatch({ selection: EditorSelection.cursor(adaPos) });
27
- view.focus();
28
- await nextFrame();
29
- await nextFrame();
30
-
31
- const tableLines = () =>
32
- view.state.doc
33
- .toString()
34
- .split("\n")
35
- .filter((l) => l.includes("|"));
36
- const headerPipes = () => (tableLines()[0]?.match(/\|/g) ?? []).length;
37
-
38
- const beforeCols = headerPipes();
39
- const beforeRows = tableLines().length;
40
-
41
- expect(clickControl("Add column")).toBe(true);
42
- await nextFrame();
43
- expect(headerPipes()).toBe(beforeCols + 1);
44
-
45
- expect(clickControl("Add row")).toBe(true);
46
- await nextFrame();
47
- expect(tableLines().length).toBe(beforeRows + 1);
48
-
49
- expect(clickControl("Remove row")).toBe(true);
50
- await nextFrame();
51
- expect(tableLines().length).toBe(beforeRows);
52
-
53
- expect(clickControl("Remove column")).toBe(true);
54
- await nextFrame();
55
- expect(headerPipes()).toBe(beforeCols);
56
- });
57
- });
@@ -1,42 +0,0 @@
1
- import { EditorSelection } from "@codemirror/state";
2
- import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
3
- import { composeStories } from "@storybook/react-vite";
4
- import { describe, expect, it } from "vitest";
5
-
6
- import {
7
- focusEditor,
8
- getEditorView,
9
- mountStory,
10
- nextFrame,
11
- userEvent,
12
- } from "../../_setup/e2e-helpers";
13
-
14
- const Stories = composeStories(MarkdownStories);
15
-
16
- describe("MarkdownEditor / table formatter", () => {
17
- it("realigns the table columns after typing in a cell", async () => {
18
- mountStory(<Stories.EditorTableFormatter />);
19
- const view = await getEditorView();
20
- await nextFrame();
21
-
22
- const doc = view.state.doc.toString();
23
- const target = doc.indexOf("Ada") + "Ada".length;
24
- expect(target).toBeGreaterThan(0);
25
-
26
- view.dispatch({ selection: EditorSelection.cursor(target) });
27
- await focusEditor();
28
- await userEvent.keyboard("lyn"); // "Ada" -> "Adalyn"
29
- await nextFrame();
30
- await nextFrame();
31
-
32
- const next = view.state.doc.toString();
33
- // Column 1 grew to fit "Adalyn"; the delimiter row should align to it.
34
- expect(next).toContain("Adalyn");
35
- const lines = next.split("\n");
36
- const widths = lines
37
- .filter((l) => l.includes("|"))
38
- .map((l) => l.indexOf("|", l.indexOf("|") + 1));
39
- // All rows share the same position for the second pipe (aligned column 1).
40
- expect(new Set(widths).size).toBe(1);
41
- });
42
- });
@@ -1,39 +0,0 @@
1
- import { redo, undo } from "@codemirror/commands";
2
- import { EditorSelection } from "@codemirror/state";
3
- import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
4
- import { composeStories } from "@storybook/react-vite";
5
- import { describe, expect, it } from "vitest";
6
-
7
- import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
8
-
9
- const Stories = composeStories(MarkdownStories);
10
-
11
- describe("MarkdownEditor / undo and redo", () => {
12
- it("restores undone document edits via redo", async () => {
13
- mountStory(<Stories.Editor />);
14
- const view = await getEditorView();
15
-
16
- const start = Date.now();
17
- while (view.state.doc.length === 0 && Date.now() - start < 5000) {
18
- await nextFrame();
19
- }
20
- expect(view.state.doc.length).toBeGreaterThan(0);
21
-
22
- const before = view.state.doc.toString();
23
-
24
- view.dispatch({
25
- changes: { from: view.state.doc.length, insert: "\nredo-marker" },
26
- selection: EditorSelection.cursor(view.state.doc.length),
27
- });
28
- await nextFrame();
29
-
30
- const afterEdit = view.state.doc.toString();
31
- expect(afterEdit).toContain("redo-marker");
32
-
33
- expect(undo(view)).toBe(true);
34
- expect(view.state.doc.toString()).toBe(before);
35
-
36
- expect(redo(view)).toBe(true);
37
- expect(view.state.doc.toString()).toBe(afterEdit);
38
- });
39
- });