@lapismd/mira 0.0.4 → 0.0.5

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 (203) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/extensions/index.d.ts +7 -0
  3. package/dist/extensions/index.d.ts.map +1 -1
  4. package/dist/preview/editable-markdown-preview/editable-markdown-surface.svelte +20 -9
  5. package/dist/preview/editable-markdown-preview/editable-markdown-surface.svelte.d.ts.map +1 -1
  6. package/dist/preview/editable-markdown-preview/editing-session.d.ts +13 -0
  7. package/dist/preview/editable-markdown-preview/editing-session.d.ts.map +1 -0
  8. package/dist/preview/editable-markdown-preview/editing-session.js +50 -0
  9. package/dist/preview/editable-markdown-preview/editor-surface.svelte +13 -4
  10. package/dist/preview/editable-markdown-preview/editor-surface.svelte.d.ts +1 -0
  11. package/dist/preview/editable-markdown-preview/editor-surface.svelte.d.ts.map +1 -1
  12. package/dist/preview/editable-markdown-preview/serialized-writer.d.ts +5 -1
  13. package/dist/preview/editable-markdown-preview/serialized-writer.d.ts.map +1 -1
  14. package/dist/preview/editable-markdown-preview/serialized-writer.js +29 -0
  15. package/dist/preview/editable-markdown-preview.svelte +8 -6
  16. package/dist/preview/editable-markdown-preview.svelte.d.ts.map +1 -1
  17. package/package.json +5 -2
  18. package/dist/core/code-mirror.test.d.ts +0 -2
  19. package/dist/core/code-mirror.test.d.ts.map +0 -1
  20. package/dist/core/code-mirror.test.js +0 -68
  21. package/dist/core/images.test.d.ts +0 -2
  22. package/dist/core/images.test.d.ts.map +0 -1
  23. package/dist/core/images.test.js +0 -109
  24. package/dist/core/index.test.d.ts +0 -2
  25. package/dist/core/index.test.d.ts.map +0 -1
  26. package/dist/core/index.test.js +0 -11
  27. package/dist/core/markdown-actions.test.d.ts +0 -2
  28. package/dist/core/markdown-actions.test.d.ts.map +0 -1
  29. package/dist/core/markdown-actions.test.js +0 -182
  30. package/dist/extensions/doodle-dividers.test.d.ts +0 -2
  31. package/dist/extensions/doodle-dividers.test.d.ts.map +0 -1
  32. package/dist/extensions/doodle-dividers.test.js +0 -296
  33. package/dist/extensions/index.test.d.ts +0 -2
  34. package/dist/extensions/index.test.d.ts.map +0 -1
  35. package/dist/extensions/index.test.js +0 -283
  36. package/dist/index.test.d.ts +0 -2
  37. package/dist/index.test.d.ts.map +0 -1
  38. package/dist/index.test.js +0 -64
  39. package/dist/internal/appearance-context.test.d.ts +0 -2
  40. package/dist/internal/appearance-context.test.d.ts.map +0 -1
  41. package/dist/internal/appearance-context.test.js +0 -31
  42. package/dist/internal/codemirror/base/index.test.d.ts +0 -2
  43. package/dist/internal/codemirror/base/index.test.d.ts.map +0 -1
  44. package/dist/internal/codemirror/base/index.test.js +0 -303
  45. package/dist/internal/codemirror/base/search.test.d.ts +0 -2
  46. package/dist/internal/codemirror/base/search.test.d.ts.map +0 -1
  47. package/dist/internal/codemirror/base/search.test.js +0 -155
  48. package/dist/internal/codemirror/base/selection-toolbar.test.d.ts +0 -2
  49. package/dist/internal/codemirror/base/selection-toolbar.test.d.ts.map +0 -1
  50. package/dist/internal/codemirror/base/selection-toolbar.test.js +0 -149
  51. package/dist/internal/codemirror/markdown/authoring.test.d.ts +0 -2
  52. package/dist/internal/codemirror/markdown/authoring.test.d.ts.map +0 -1
  53. package/dist/internal/codemirror/markdown/authoring.test.js +0 -222
  54. package/dist/internal/codemirror/markdown/index.test.d.ts +0 -2
  55. package/dist/internal/codemirror/markdown/index.test.d.ts.map +0 -1
  56. package/dist/internal/codemirror/markdown/index.test.js +0 -158
  57. package/dist/internal/codemirror/markdown/showcase-fixture.test.d.ts +0 -2
  58. package/dist/internal/codemirror/markdown/showcase-fixture.test.d.ts.map +0 -1
  59. package/dist/internal/codemirror/markdown/showcase-fixture.test.js +0 -50
  60. package/dist/internal/codemirror/rich/block-controls.test.d.ts +0 -2
  61. package/dist/internal/codemirror/rich/block-controls.test.d.ts.map +0 -1
  62. package/dist/internal/codemirror/rich/block-controls.test.js +0 -401
  63. package/dist/internal/codemirror/rich/block-ranges.test.d.ts +0 -2
  64. package/dist/internal/codemirror/rich/block-ranges.test.d.ts.map +0 -1
  65. package/dist/internal/codemirror/rich/block-ranges.test.js +0 -310
  66. package/dist/internal/codemirror/rich/block-toolbar.test.d.ts +0 -2
  67. package/dist/internal/codemirror/rich/block-toolbar.test.d.ts.map +0 -1
  68. package/dist/internal/codemirror/rich/block-toolbar.test.js +0 -178
  69. package/dist/internal/codemirror/rich/decorations/fold-indicators.test.d.ts +0 -2
  70. package/dist/internal/codemirror/rich/decorations/fold-indicators.test.d.ts.map +0 -1
  71. package/dist/internal/codemirror/rich/decorations/fold-indicators.test.js +0 -8
  72. package/dist/internal/codemirror/rich/index.test.d.ts +0 -2
  73. package/dist/internal/codemirror/rich/index.test.d.ts.map +0 -1
  74. package/dist/internal/codemirror/rich/index.test.js +0 -607
  75. package/dist/internal/codemirror/rich/utils/code-block-lines.test.d.ts +0 -2
  76. package/dist/internal/codemirror/rich/utils/code-block-lines.test.d.ts.map +0 -1
  77. package/dist/internal/codemirror/rich/utils/code-block-lines.test.js +0 -82
  78. package/dist/internal/codemirror/rich/utils/fenced-code.test.d.ts +0 -2
  79. package/dist/internal/codemirror/rich/utils/fenced-code.test.d.ts.map +0 -1
  80. package/dist/internal/codemirror/rich/utils/fenced-code.test.js +0 -12
  81. package/dist/internal/codemirror/rich/utils/headings.test.d.ts +0 -2
  82. package/dist/internal/codemirror/rich/utils/headings.test.d.ts.map +0 -1
  83. package/dist/internal/codemirror/rich/utils/headings.test.js +0 -27
  84. package/dist/internal/codemirror/rich/utils/height-estimates.test.d.ts +0 -2
  85. package/dist/internal/codemirror/rich/utils/height-estimates.test.d.ts.map +0 -1
  86. package/dist/internal/codemirror/rich/utils/height-estimates.test.js +0 -10
  87. package/dist/internal/codemirror/rich/utils/images.test.d.ts +0 -2
  88. package/dist/internal/codemirror/rich/utils/images.test.d.ts.map +0 -1
  89. package/dist/internal/codemirror/rich/utils/images.test.js +0 -37
  90. package/dist/internal/codemirror/rich/utils/indent.test.d.ts +0 -2
  91. package/dist/internal/codemirror/rich/utils/indent.test.d.ts.map +0 -1
  92. package/dist/internal/codemirror/rich/utils/indent.test.js +0 -169
  93. package/dist/internal/codemirror/rich/utils/inline-math.test.d.ts +0 -2
  94. package/dist/internal/codemirror/rich/utils/inline-math.test.d.ts.map +0 -1
  95. package/dist/internal/codemirror/rich/utils/inline-math.test.js +0 -15
  96. package/dist/internal/codemirror/rich/utils/links.test.d.ts +0 -2
  97. package/dist/internal/codemirror/rich/utils/links.test.d.ts.map +0 -1
  98. package/dist/internal/codemirror/rich/utils/links.test.js +0 -28
  99. package/dist/internal/codemirror/rich/utils/list-callouts.test.d.ts +0 -2
  100. package/dist/internal/codemirror/rich/utils/list-callouts.test.d.ts.map +0 -1
  101. package/dist/internal/codemirror/rich/utils/list-callouts.test.js +0 -47
  102. package/dist/internal/codemirror/rich/utils/measured-indent.test.d.ts +0 -2
  103. package/dist/internal/codemirror/rich/utils/measured-indent.test.d.ts.map +0 -1
  104. package/dist/internal/codemirror/rich/utils/measured-indent.test.js +0 -102
  105. package/dist/internal/codemirror/rich/utils/ranges.test.d.ts +0 -2
  106. package/dist/internal/codemirror/rich/utils/ranges.test.d.ts.map +0 -1
  107. package/dist/internal/codemirror/rich/utils/ranges.test.js +0 -22
  108. package/dist/internal/codemirror/rich/utils/tasks.test.d.ts +0 -2
  109. package/dist/internal/codemirror/rich/utils/tasks.test.d.ts.map +0 -1
  110. package/dist/internal/codemirror/rich/utils/tasks.test.js +0 -57
  111. package/dist/internal/task-states.test.d.ts +0 -2
  112. package/dist/internal/task-states.test.d.ts.map +0 -1
  113. package/dist/internal/task-states.test.js +0 -36
  114. package/dist/mira-code-editor.test.d.ts +0 -2
  115. package/dist/mira-code-editor.test.d.ts.map +0 -1
  116. package/dist/mira-code-editor.test.js +0 -93
  117. package/dist/mode-position.test.d.ts +0 -2
  118. package/dist/mode-position.test.d.ts.map +0 -1
  119. package/dist/mode-position.test.js +0 -60
  120. package/dist/preview/code-language.test.d.ts +0 -2
  121. package/dist/preview/code-language.test.d.ts.map +0 -1
  122. package/dist/preview/code-language.test.js +0 -12
  123. package/dist/preview/components/icon.test.d.ts +0 -2
  124. package/dist/preview/components/icon.test.d.ts.map +0 -1
  125. package/dist/preview/components/icon.test.js +0 -20
  126. package/dist/preview/editable-markdown-preview/behavior.test.d.ts +0 -2
  127. package/dist/preview/editable-markdown-preview/behavior.test.d.ts.map +0 -1
  128. package/dist/preview/editable-markdown-preview/behavior.test.js +0 -36
  129. package/dist/preview/editable-markdown-preview/serialized-writer.test.d.ts +0 -2
  130. package/dist/preview/editable-markdown-preview/serialized-writer.test.d.ts.map +0 -1
  131. package/dist/preview/editable-markdown-preview/serialized-writer.test.js +0 -59
  132. package/dist/preview/embed-target.test.d.ts +0 -2
  133. package/dist/preview/embed-target.test.d.ts.map +0 -1
  134. package/dist/preview/embed-target.test.js +0 -78
  135. package/dist/preview/frontmatter/frontmatter.test.d.ts +0 -2
  136. package/dist/preview/frontmatter/frontmatter.test.d.ts.map +0 -1
  137. package/dist/preview/frontmatter/frontmatter.test.js +0 -190
  138. package/dist/preview/frontmatter/suggestions.test.d.ts +0 -2
  139. package/dist/preview/frontmatter/suggestions.test.d.ts.map +0 -1
  140. package/dist/preview/frontmatter/suggestions.test.js +0 -45
  141. package/dist/preview/outline.test.d.ts +0 -2
  142. package/dist/preview/outline.test.d.ts.map +0 -1
  143. package/dist/preview/outline.test.js +0 -98
  144. package/dist/preview/portable-surfaces.test.d.ts +0 -2
  145. package/dist/preview/portable-surfaces.test.d.ts.map +0 -1
  146. package/dist/preview/portable-surfaces.test.js +0 -513
  147. package/dist/preview/preview.test.d.ts +0 -2
  148. package/dist/preview/preview.test.d.ts.map +0 -1
  149. package/dist/preview/preview.test.js +0 -154
  150. package/dist/preview/remark/callouts.test.d.ts +0 -2
  151. package/dist/preview/remark/callouts.test.d.ts.map +0 -1
  152. package/dist/preview/remark/callouts.test.js +0 -23
  153. package/dist/preview/remark/custom-checklists.test.d.ts +0 -2
  154. package/dist/preview/remark/custom-checklists.test.d.ts.map +0 -1
  155. package/dist/preview/remark/custom-checklists.test.js +0 -51
  156. package/dist/preview/remark/links.test.d.ts +0 -2
  157. package/dist/preview/remark/links.test.d.ts.map +0 -1
  158. package/dist/preview/remark/links.test.js +0 -62
  159. package/dist/preview/remark/parity.test.d.ts +0 -2
  160. package/dist/preview/remark/parity.test.d.ts.map +0 -1
  161. package/dist/preview/remark/parity.test.js +0 -105
  162. package/dist/preview/styles/frontmatter-properties.test.d.ts +0 -2
  163. package/dist/preview/styles/frontmatter-properties.test.d.ts.map +0 -1
  164. package/dist/preview/styles/frontmatter-properties.test.js +0 -57
  165. package/dist/preview/styles/source-mode.test.d.ts +0 -2
  166. package/dist/preview/styles/source-mode.test.d.ts.map +0 -1
  167. package/dist/preview/styles/source-mode.test.js +0 -28
  168. package/dist/tables/extension.test.d.ts +0 -2
  169. package/dist/tables/extension.test.d.ts.map +0 -1
  170. package/dist/tables/extension.test.js +0 -100
  171. package/dist/tables/grid-editor-column.test.d.ts +0 -2
  172. package/dist/tables/grid-editor-column.test.d.ts.map +0 -1
  173. package/dist/tables/grid-editor-column.test.js +0 -27
  174. package/dist/tables/grid-table.test.d.ts +0 -2
  175. package/dist/tables/grid-table.test.d.ts.map +0 -1
  176. package/dist/tables/grid-table.test.js +0 -140
  177. package/dist/tables/index.test.d.ts +0 -2
  178. package/dist/tables/index.test.d.ts.map +0 -1
  179. package/dist/tables/index.test.js +0 -7
  180. package/dist/tables/inline-editor-extensions.test.d.ts +0 -2
  181. package/dist/tables/inline-editor-extensions.test.d.ts.map +0 -1
  182. package/dist/tables/inline-editor-extensions.test.js +0 -23
  183. package/dist/tables/pipe-table.test.d.ts +0 -2
  184. package/dist/tables/pipe-table.test.d.ts.map +0 -1
  185. package/dist/tables/pipe-table.test.js +0 -13
  186. package/dist/tables/table-node.test.d.ts +0 -2
  187. package/dist/tables/table-node.test.d.ts.map +0 -1
  188. package/dist/tables/table-node.test.js +0 -167
  189. package/dist/tables/widgets/source-button.test.d.ts +0 -2
  190. package/dist/tables/widgets/source-button.test.d.ts.map +0 -1
  191. package/dist/tables/widgets/source-button.test.js +0 -11
  192. package/dist/themes/theme-contract.test.d.ts +0 -2
  193. package/dist/themes/theme-contract.test.d.ts.map +0 -1
  194. package/dist/themes/theme-contract.test.js +0 -88
  195. package/dist/ui/index.test.d.ts +0 -2
  196. package/dist/ui/index.test.d.ts.map +0 -1
  197. package/dist/ui/index.test.js +0 -7
  198. package/dist/ui/styles.test.d.ts +0 -2
  199. package/dist/ui/styles.test.d.ts.map +0 -1
  200. package/dist/ui/styles.test.js +0 -26
  201. package/dist/ui/table-dnd/table-dnd-utils.test.d.ts +0 -2
  202. package/dist/ui/table-dnd/table-dnd-utils.test.d.ts.map +0 -1
  203. package/dist/ui/table-dnd/table-dnd-utils.test.js +0 -46
@@ -1,303 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { describe, expect, it } from "vitest";
3
- import { CompletionContext } from "@codemirror/autocomplete";
4
- import { EditorState } from "@codemirror/state";
5
- import { EditorView } from "@codemirror/view";
6
- import { createBaseCodeMirrorExtensions, createMarkdownTemplate, createSlashCommandCompletionSource, createSlashCommandExtensions, createSlashSnippet, } from ".";
7
- const slashCommands = [
8
- createSlashSnippet({
9
- id: "heading",
10
- label: "Heading 1",
11
- description: "Large section heading",
12
- group: "Basic",
13
- keywords: ["h1"],
14
- markdown: "# <|>",
15
- }),
16
- createSlashSnippet({
17
- id: "quote",
18
- label: "Quote",
19
- description: "Create a quote",
20
- group: "Basic",
21
- keywords: ["blockquote"],
22
- markdown: "> <|>",
23
- }),
24
- createSlashSnippet({
25
- id: "table",
26
- label: "Table",
27
- description: "Insert a pipe table",
28
- group: "Blocks",
29
- keywords: ["tabular"],
30
- markdown: "| Column | Value |\n| --- | --- |\n| Item | Detail |\n",
31
- }),
32
- ];
33
- function createSlashEditor(doc = "") {
34
- const parent = document.createElement("div");
35
- document.body.append(parent);
36
- const view = new EditorView({
37
- doc,
38
- selection: { anchor: doc.length },
39
- extensions: [
40
- createSlashCommandExtensions({
41
- commands: slashCommands,
42
- }),
43
- ],
44
- parent,
45
- });
46
- return {
47
- parent,
48
- view,
49
- destroy() {
50
- view.destroy();
51
- parent.remove();
52
- },
53
- };
54
- }
55
- function createAfterWhitespaceSlashEditor(doc = "") {
56
- const parent = document.createElement("div");
57
- document.body.append(parent);
58
- const view = new EditorView({
59
- doc,
60
- selection: { anchor: doc.length },
61
- extensions: [
62
- createSlashCommandExtensions({
63
- commands: slashCommands,
64
- triggerScope: "after-whitespace",
65
- }),
66
- ],
67
- parent,
68
- });
69
- return {
70
- parent,
71
- view,
72
- destroy() {
73
- view.destroy();
74
- parent.remove();
75
- },
76
- };
77
- }
78
- function insertText(view, text) {
79
- const selection = view.state.selection.main;
80
- view.dispatch({
81
- changes: {
82
- from: selection.from,
83
- to: selection.to,
84
- insert: text,
85
- },
86
- selection: {
87
- anchor: selection.from + text.length,
88
- },
89
- });
90
- }
91
- function pressKey(view, key) {
92
- view.contentDOM.dispatchEvent(new KeyboardEvent("keydown", {
93
- bubbles: true,
94
- cancelable: true,
95
- key,
96
- }));
97
- }
98
- function slashMenu(parent) {
99
- return parent.querySelector(".mira-slash-menu");
100
- }
101
- function slashMenuTitles(parent) {
102
- return Array.from(parent.querySelectorAll(".mira-slash-menu__item-title"), (title) => title.textContent ?? "");
103
- }
104
- describe("createBaseCodeMirrorExtensions", () => {
105
- it("returns a non-empty extension set", () => {
106
- expect(createBaseCodeMirrorExtensions()).not.toHaveLength(0);
107
- });
108
- it("uses muted monospaced tabular line numbers", () => {
109
- const parent = document.createElement("div");
110
- document.body.append(parent);
111
- const view = new EditorView({
112
- doc: Array.from({ length: 12 }, (_, index) => `Line ${index + 1}`).join("\n"),
113
- extensions: createBaseCodeMirrorExtensions(),
114
- parent,
115
- });
116
- const gutter = parent.querySelector(".cm-gutter.cm-lineNumbers");
117
- const number = gutter?.querySelector(".cm-gutterElement");
118
- expect(gutter).not.toBeNull();
119
- expect(number).not.toBeNull();
120
- expect(getComputedStyle(gutter).fontFamily).toBe("var(--font-monospace, var(--mira-font-mono))");
121
- expect(getComputedStyle(gutter).fontVariantNumeric).toBe("tabular-nums");
122
- expect(getComputedStyle(gutter).color).toBe("var(--text-faint, var(--mira-muted-foreground))");
123
- expect(getComputedStyle(number).justifyContent).toBe("end");
124
- view.destroy();
125
- parent.remove();
126
- });
127
- });
128
- describe("createSlashCommandExtensions", () => {
129
- it("is empty without commands", () => {
130
- expect(createSlashCommandExtensions()).toEqual([]);
131
- });
132
- it("completes slash commands after a slash trigger", () => {
133
- const source = createSlashCommandCompletionSource([
134
- {
135
- id: "heading",
136
- label: "Heading 1",
137
- group: "Basic",
138
- keywords: ["h1"],
139
- insert: "# ",
140
- },
141
- {
142
- id: "table",
143
- label: "Table",
144
- group: "Blocks",
145
- insert: "| Column |",
146
- },
147
- ]);
148
- const state = EditorState.create({ doc: "/h" });
149
- const result = source(new CompletionContext(state, 2, false));
150
- if (result instanceof Promise) {
151
- throw new Error("Expected synchronous slash completions.");
152
- }
153
- expect(result?.from).toBe(1);
154
- expect(result?.to).toBe(2);
155
- expect(result?.filter).toBe(false);
156
- expect(result?.options.map((option) => option.label)).toEqual([
157
- "Heading 1",
158
- ]);
159
- });
160
- it("does not complete slash commands in the middle of a word", () => {
161
- const source = createSlashCommandCompletionSource([
162
- {
163
- id: "heading",
164
- label: "Heading 1",
165
- insert: "# ",
166
- },
167
- ]);
168
- const state = EditorState.create({ doc: "word/h" });
169
- const result = source(new CompletionContext(state, 6, false));
170
- expect(result).toBeNull();
171
- });
172
- it("keeps the autocomplete UI path available", () => {
173
- expect(createSlashCommandExtensions({
174
- commands: slashCommands,
175
- ui: "autocomplete",
176
- })).not.toHaveLength(0);
177
- });
178
- });
179
- describe("slash command popover", () => {
180
- it("opens on slash at line start and after indentation", () => {
181
- const editor = createSlashEditor();
182
- insertText(editor.view, "/");
183
- expect(slashMenu(editor.parent)).not.toBeNull();
184
- editor.destroy();
185
- const indentedEditor = createSlashEditor(" ");
186
- insertText(indentedEditor.view, "/");
187
- expect(slashMenu(indentedEditor.parent)).not.toBeNull();
188
- indentedEditor.destroy();
189
- });
190
- it("does not open after prose, URLs, or paths", () => {
191
- for (const doc of ["word ", "https:/", "notes"]) {
192
- const editor = createSlashEditor(doc);
193
- insertText(editor.view, "/");
194
- expect(slashMenu(editor.parent)).toBeNull();
195
- editor.destroy();
196
- }
197
- });
198
- it("can opt into opening after whitespace", () => {
199
- const editor = createAfterWhitespaceSlashEditor("word ");
200
- insertText(editor.view, "/");
201
- expect(slashMenu(editor.parent)).not.toBeNull();
202
- editor.destroy();
203
- });
204
- it("filters commands by label, description, and keywords", () => {
205
- const editor = createSlashEditor();
206
- insertText(editor.view, "/tab");
207
- expect(slashMenuTitles(editor.parent)).toEqual(["Table"]);
208
- editor.view.dispatch({
209
- changes: { from: 0, to: editor.view.state.doc.length, insert: "/large" },
210
- selection: { anchor: 6 },
211
- });
212
- expect(slashMenuTitles(editor.parent)).toEqual(["Heading 1"]);
213
- editor.view.dispatch({
214
- changes: { from: 0, to: editor.view.state.doc.length, insert: "/block" },
215
- selection: { anchor: 6 },
216
- });
217
- expect(slashMenuTitles(editor.parent)).toEqual(["Quote"]);
218
- editor.destroy();
219
- });
220
- it("renders no menu when no commands match", () => {
221
- const editor = createSlashEditor();
222
- insertText(editor.view, "/missing");
223
- expect(slashMenu(editor.parent)).toBeNull();
224
- editor.destroy();
225
- });
226
- it("groups commands while preserving command order within groups", () => {
227
- const editor = createSlashEditor();
228
- insertText(editor.view, "/");
229
- expect(Array.from(editor.parent.querySelectorAll(".mira-slash-menu__group"), (group) => group.textContent)).toEqual(["Basic", "Blocks"]);
230
- expect(slashMenuTitles(editor.parent)).toEqual([
231
- "Heading 1",
232
- "Quote",
233
- "Table",
234
- ]);
235
- editor.destroy();
236
- });
237
- it("navigates with wrapping arrow keys and applies the active command", () => {
238
- const editor = createSlashEditor();
239
- insertText(editor.view, "/");
240
- pressKey(editor.view, "ArrowUp");
241
- pressKey(editor.view, "Enter");
242
- expect(editor.view.state.doc.toString()).toBe("| Column | Value |\n| --- | --- |\n| Item | Detail |\n");
243
- expect(slashMenu(editor.parent)).toBeNull();
244
- editor.destroy();
245
- });
246
- it("applies a command selected by mouse", () => {
247
- const editor = createSlashEditor();
248
- insertText(editor.view, "/tab");
249
- editor.parent
250
- .querySelector(".mira-slash-menu__item")
251
- ?.click();
252
- expect(editor.view.state.doc.toString()).toBe("| Column | Value |\n| --- | --- |\n| Item | Detail |\n");
253
- editor.destroy();
254
- });
255
- it("closes on escape, space, and backspace from an empty query", () => {
256
- const escapeEditor = createSlashEditor();
257
- insertText(escapeEditor.view, "/");
258
- pressKey(escapeEditor.view, "Escape");
259
- expect(slashMenu(escapeEditor.parent)).toBeNull();
260
- escapeEditor.destroy();
261
- const spaceEditor = createSlashEditor();
262
- insertText(spaceEditor.view, "/");
263
- insertText(spaceEditor.view, " ");
264
- expect(slashMenu(spaceEditor.parent)).toBeNull();
265
- spaceEditor.destroy();
266
- const backspaceEditor = createSlashEditor();
267
- insertText(backspaceEditor.view, "/");
268
- backspaceEditor.view.dispatch({
269
- changes: { from: 0, to: 1 },
270
- selection: { anchor: 0 },
271
- });
272
- expect(slashMenu(backspaceEditor.parent)).toBeNull();
273
- backspaceEditor.destroy();
274
- });
275
- it("places the cursor at the markdown template marker", () => {
276
- const editor = createSlashEditor();
277
- insertText(editor.view, "/h");
278
- pressKey(editor.view, "Enter");
279
- expect(editor.view.state.doc.toString()).toBe("# ");
280
- expect(editor.view.state.selection.main.head).toBe(2);
281
- editor.destroy();
282
- });
283
- });
284
- describe("slash snippet helpers", () => {
285
- it("creates templates and slash commands with cursor markers", () => {
286
- expect(createMarkdownTemplate("## <|>")).toEqual({
287
- markdown: "## ",
288
- selection: 3,
289
- });
290
- expect(createSlashSnippet({
291
- id: "math",
292
- label: "Math block",
293
- markdown: "$$\n<|>\n$$",
294
- })).toEqual({
295
- id: "math",
296
- label: "Math block",
297
- insert: {
298
- markdown: "$$\n\n$$",
299
- selection: 3,
300
- },
301
- });
302
- });
303
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=search.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"search.test.d.ts","sourceRoot":"","sources":["../../../../src/internal/codemirror/base/search.test.ts"],"names":[],"mappings":""}
@@ -1,155 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { getSearchQuery, openSearchPanel, searchPanelOpen, } from "@codemirror/search";
3
- import { EditorState } from "@codemirror/state";
4
- import { EditorView } from "@codemirror/view";
5
- import { tick } from "svelte";
6
- import { describe, expect, it } from "vitest";
7
- import { createBaseCodeMirrorExtensions } from ".";
8
- import { createMiraSearchExtension, createSearchPanelHost, focusSearchPanelInput, } from "./search";
9
- function createSearchEditor(doc = "Alpha beta Alpha", selection = { anchor: 0, head: 5 }) {
10
- const parent = document.createElement("div");
11
- document.body.append(parent);
12
- const view = new EditorView({
13
- state: EditorState.create({
14
- doc,
15
- selection,
16
- extensions: [createBaseCodeMirrorExtensions()],
17
- }),
18
- parent,
19
- });
20
- return {
21
- parent,
22
- view,
23
- destroy() {
24
- view.destroy();
25
- parent.remove();
26
- },
27
- };
28
- }
29
- function inputValue(input, value) {
30
- input.value = value;
31
- input.dispatchEvent(new Event("input", { bubbles: true }));
32
- }
33
- describe("Mira search extension", () => {
34
- it("opens the Lapis-compatible search interface with the selection", () => {
35
- const editor = createSearchEditor();
36
- openSearchPanel(editor.view);
37
- const panel = editor.parent.querySelector(".mira-search-panel");
38
- const searchInput = editor.parent.querySelector(".mira-search-panel__search-input");
39
- expect(panel).not.toBeNull();
40
- expect(editor.parent.querySelector(".cm-panels-top .mira-search-panel-host")).not.toBeNull();
41
- expect(searchInput?.value).toBe("Alpha");
42
- expect(document.activeElement).toBe(searchInput);
43
- expect(editor.parent.querySelector('[aria-label="Case sensitive"]')).not.toBeNull();
44
- expect(editor.parent.querySelector('[aria-label="Match whole word"]')).not.toBeNull();
45
- expect(editor.parent.querySelector('[aria-label="Use regular expression"]')).not.toBeNull();
46
- const searchField = editor.parent.querySelector(".mira-search-panel__search-field");
47
- const searchOptions = editor.parent.querySelector('[role="group"][aria-label="Search options"]');
48
- expect(searchField?.contains(searchInput)).toBe(true);
49
- expect(searchField?.contains(searchOptions)).toBe(true);
50
- expect(searchOptions?.parentElement).toBe(searchField);
51
- expect(searchField?.querySelector(".mira-search-panel__find-icon svg")).not.toBeNull();
52
- expect(editor.parent.querySelector('[aria-label="Select all matches"]')).not.toBeNull();
53
- editor.destroy();
54
- });
55
- it("updates search flags and reports missing matches", async () => {
56
- const editor = createSearchEditor();
57
- openSearchPanel(editor.view);
58
- editor.parent
59
- .querySelector('[aria-label="Case sensitive"]')
60
- ?.click();
61
- editor.parent
62
- .querySelector('[aria-label="Match whole word"]')
63
- ?.click();
64
- editor.parent
65
- .querySelector('[aria-label="Use regular expression"]')
66
- ?.click();
67
- expect(getSearchQuery(editor.view.state)).toMatchObject({
68
- caseSensitive: true,
69
- wholeWord: true,
70
- regexp: true,
71
- });
72
- const searchInput = editor.parent.querySelector(".mira-search-panel__search-input");
73
- if (!searchInput) {
74
- throw new Error("Expected the search input.");
75
- }
76
- inputValue(searchInput, "Missing");
77
- await tick();
78
- expect(getSearchQuery(editor.view.state).search).toBe("Missing");
79
- expect(searchInput.getAttribute("aria-invalid")).toBe("true");
80
- expect(editor.parent.querySelector('[role="status"]')?.textContent).toContain("No matches found");
81
- editor.destroy();
82
- });
83
- it("expands replace controls and replaces all matches", async () => {
84
- const editor = createSearchEditor("one two one", { anchor: 0, head: 3 });
85
- openSearchPanel(editor.view);
86
- editor.parent
87
- .querySelector('[aria-label="Toggle replace"]')
88
- ?.click();
89
- await tick();
90
- const replaceInput = editor.parent.querySelector('input[aria-label="Replace"]');
91
- if (!replaceInput) {
92
- throw new Error("Expected the replace input.");
93
- }
94
- inputValue(replaceInput, "three");
95
- editor.parent
96
- .querySelector('[aria-label="Replace all matches"]')
97
- ?.click();
98
- expect(editor.view.state.doc.toString()).toBe("three two three");
99
- editor.destroy();
100
- });
101
- it("respects initial search configuration and panel placement", () => {
102
- const parent = document.createElement("div");
103
- document.body.append(parent);
104
- const view = new EditorView({
105
- state: EditorState.create({
106
- doc: "Alpha alpha",
107
- extensions: [
108
- createMiraSearchExtension({
109
- top: false,
110
- caseSensitive: true,
111
- wholeWord: true,
112
- }),
113
- ],
114
- }),
115
- parent,
116
- });
117
- openSearchPanel(view);
118
- expect(getSearchQuery(view.state)).toMatchObject({
119
- caseSensitive: true,
120
- wholeWord: true,
121
- });
122
- expect(parent.querySelector(".cm-panels-bottom .mira-search-panel-host")).not.toBeNull();
123
- view.destroy();
124
- parent.remove();
125
- });
126
- it("closes with Escape from the search panel", () => {
127
- const editor = createSearchEditor();
128
- openSearchPanel(editor.view);
129
- const searchInput = editor.parent.querySelector(".mira-search-panel__search-input");
130
- searchInput?.dispatchEvent(new KeyboardEvent("keydown", {
131
- bubbles: true,
132
- cancelable: true,
133
- key: "Escape",
134
- }));
135
- expect(searchPanelOpen(editor.view.state)).toBe(false);
136
- editor.destroy();
137
- });
138
- it("creates and focuses the panel host in the editor document", () => {
139
- const iframe = document.createElement("iframe");
140
- document.body.append(iframe);
141
- const popupDocument = iframe.contentDocument;
142
- if (!popupDocument) {
143
- throw new Error("Expected an iframe document.");
144
- }
145
- const host = createSearchPanelHost(popupDocument);
146
- const input = popupDocument.createElement("input");
147
- input.className = "mira-search-panel__search-input";
148
- host.append(input);
149
- popupDocument.body.append(host);
150
- focusSearchPanelInput(host);
151
- expect(host.ownerDocument).toBe(popupDocument);
152
- expect(popupDocument.activeElement).toBe(input);
153
- iframe.remove();
154
- });
155
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=selection-toolbar.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"selection-toolbar.test.d.ts","sourceRoot":"","sources":["../../../../src/internal/codemirror/base/selection-toolbar.test.ts"],"names":[],"mappings":""}
@@ -1,149 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { EditorSelection, EditorState } from "@codemirror/state";
3
- import { EditorView } from "@codemirror/view";
4
- import { describe, expect, it } from "vitest";
5
- import { createMarkdownCodeMirrorExtensions } from "../markdown";
6
- import { createSelectionToolbarExtension, defaultMiraSelectionToolbarActions, } from "./selection-toolbar";
7
- function createEditor({ actions, ariaLabel, labels, placement, readonly = false, doc = "Format this text", selection = EditorSelection.single(7, 11), } = {}) {
8
- const parent = document.createElement("div");
9
- document.body.append(parent);
10
- const view = new EditorView({
11
- state: EditorState.create({
12
- doc,
13
- selection,
14
- extensions: [
15
- createMarkdownCodeMirrorExtensions({ sourceMode: true }),
16
- EditorState.readOnly.of(readonly),
17
- createSelectionToolbarExtension({
18
- actions,
19
- ariaLabel,
20
- labels,
21
- placement,
22
- }),
23
- ],
24
- }),
25
- parent,
26
- });
27
- return {
28
- parent,
29
- view,
30
- destroy() {
31
- view.destroy();
32
- parent.remove();
33
- },
34
- };
35
- }
36
- function toolbar(parent) {
37
- return parent.querySelector('[role="toolbar"]');
38
- }
39
- describe("createSelectionToolbarExtension", () => {
40
- it("shows the default accessible actions in screenshot order", () => {
41
- const editor = createEditor();
42
- const selectionToolbar = toolbar(editor.parent);
43
- expect(defaultMiraSelectionToolbarActions).toEqual([
44
- "link",
45
- "bold",
46
- "italic",
47
- "strikethrough",
48
- ]);
49
- expect(selectionToolbar?.getAttribute("aria-label")).toBe("Text formatting");
50
- expect(selectionToolbar?.dataset.placement).toBe("below");
51
- expect(Array.from(selectionToolbar?.querySelectorAll("button") ?? [], (button) => ({
52
- action: button.dataset.miraSelectionAction,
53
- label: button.getAttribute("aria-label"),
54
- title: button.title,
55
- icon: button.querySelector("svg")?.getAttribute("aria-hidden"),
56
- }))).toEqual([
57
- { action: "link", label: "Link", title: "Link", icon: "true" },
58
- { action: "bold", label: "Bold", title: "Bold", icon: "true" },
59
- { action: "italic", label: "Italic", title: "Italic", icon: "true" },
60
- {
61
- action: "strikethrough",
62
- label: "Strikethrough",
63
- title: "Strikethrough",
64
- icon: "true",
65
- },
66
- ]);
67
- expect(Array.from(selectionToolbar?.querySelectorAll("button") ?? [], (button) => button.getAttribute("aria-pressed"))).toEqual(["false", "false", "false", "false"]);
68
- expect(editor.view.state.doc.toString()).toBe("Format this text");
69
- editor.destroy();
70
- });
71
- it("delegates button activation to the shared Markdown action engine", () => {
72
- const editor = createEditor();
73
- const bold = editor.parent.querySelector('[data-mira-selection-action="bold"]');
74
- bold?.click();
75
- expect(editor.view.state.doc.toString()).toBe("Format **this** text");
76
- expect(editor.view.state.selection.main.from).toBe(9);
77
- expect(editor.view.state.selection.main.to).toBe(13);
78
- expect(toolbar(editor.parent)).not.toBeNull();
79
- expect(editor.parent
80
- .querySelector('[data-mira-selection-action="bold"]')
81
- ?.getAttribute("aria-pressed")).toBe("true");
82
- editor.parent
83
- .querySelector('[data-mira-selection-action="bold"]')
84
- ?.click();
85
- expect(editor.view.state.doc.toString()).toBe("Format this text");
86
- expect(editor.parent
87
- .querySelector('[data-mira-selection-action="bold"]')
88
- ?.getAttribute("aria-pressed")).toBe("false");
89
- editor.destroy();
90
- });
91
- it("marks an existing exact formatting span as selected", () => {
92
- const editor = createEditor({
93
- doc: "Format __this__ text",
94
- selection: EditorSelection.single(9, 13),
95
- });
96
- expect(editor.parent
97
- .querySelector('[data-mira-selection-action="bold"]')
98
- ?.getAttribute("aria-pressed")).toBe("true");
99
- expect(editor.parent
100
- .querySelector('[data-mira-selection-action="italic"]')
101
- ?.getAttribute("aria-pressed")).toBe("false");
102
- editor.destroy();
103
- });
104
- it("supports configured action order, labels, placement, and toolbar name", () => {
105
- const editor = createEditor({
106
- actions: ["inlineCode", "italic", "inlineCode"],
107
- ariaLabel: "Quick formatting",
108
- labels: { inlineCode: "Code selection" },
109
- placement: "above",
110
- });
111
- const selectionToolbar = toolbar(editor.parent);
112
- expect(selectionToolbar?.getAttribute("aria-label")).toBe("Quick formatting");
113
- expect(selectionToolbar?.dataset.placement).toBe("above");
114
- expect(Array.from(selectionToolbar?.querySelectorAll("button") ?? [], (button) => button.getAttribute("aria-label"))).toEqual(["Code selection", "Italic"]);
115
- editor.destroy();
116
- });
117
- it("moves keyboard focus into and through the toolbar", () => {
118
- const editor = createEditor();
119
- editor.view.focus();
120
- editor.view.contentDOM.dispatchEvent(new KeyboardEvent("keydown", {
121
- bubbles: true,
122
- cancelable: true,
123
- key: "Tab",
124
- }));
125
- expect(document.activeElement?.getAttribute("aria-label")).toBe("Link");
126
- document.activeElement?.dispatchEvent(new KeyboardEvent("keydown", {
127
- bubbles: true,
128
- cancelable: true,
129
- key: "ArrowRight",
130
- }));
131
- expect(document.activeElement?.getAttribute("aria-label")).toBe("Bold");
132
- document.activeElement?.dispatchEvent(new KeyboardEvent("keydown", {
133
- bubbles: true,
134
- cancelable: true,
135
- key: "Escape",
136
- }));
137
- expect(document.activeElement).toBe(editor.view.contentDOM);
138
- editor.destroy();
139
- });
140
- it("hides for collapsed selections and readonly editors", () => {
141
- const editor = createEditor();
142
- editor.view.dispatch({ selection: EditorSelection.cursor(7) });
143
- expect(toolbar(editor.parent)).toBeNull();
144
- editor.destroy();
145
- const readonlyEditor = createEditor({ readonly: true });
146
- expect(toolbar(readonlyEditor.parent)).toBeNull();
147
- readonlyEditor.destroy();
148
- });
149
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=authoring.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"authoring.test.d.ts","sourceRoot":"","sources":["../../../../src/internal/codemirror/markdown/authoring.test.ts"],"names":[],"mappings":""}