@surrealdb/ui 1.2.12 → 1.2.13

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 (93) hide show
  1. package/.claude/launch.json +17 -0
  2. package/.oxfmtrc.json +27 -0
  3. package/.oxlintrc.json +23 -0
  4. package/.zed/settings.json +34 -34
  5. package/AGENTS.md +21 -19
  6. package/README.md +15 -10
  7. package/dist/ui.css +1 -1
  8. package/dist/ui.d.ts +234 -1
  9. package/dist/ui.js +8217 -7329
  10. package/dist/ui.js.map +1 -1
  11. package/doc/code-style.md +264 -0
  12. package/doc/stacker-plan.md +219 -0
  13. package/package.json +119 -116
  14. package/res/_mixins.scss +15 -11
  15. package/tests/_setup/e2e-helpers.tsx +85 -85
  16. package/tests/_setup/portable-stories.ts +11 -10
  17. package/tests/e2e/MarkdownEditor/content-blocks.test.tsx +140 -139
  18. package/tests/e2e/MarkdownEditor/edits.test.tsx +101 -100
  19. package/tests/e2e/MarkdownEditor/heading-fold.test.tsx +35 -34
  20. package/tests/e2e/MarkdownEditor/hybrid-widgets.test.tsx +178 -177
  21. package/tests/e2e/MarkdownEditor/image-on-image.test.tsx +50 -49
  22. package/tests/e2e/MarkdownEditor/jsx-block-content.test.tsx +195 -194
  23. package/tests/e2e/MarkdownEditor/jsx-highlight.test.tsx +47 -46
  24. package/tests/e2e/MarkdownEditor/jsx-inline-badges.test.tsx +35 -34
  25. package/tests/e2e/MarkdownEditor/jsx-inline-click.test.tsx +39 -38
  26. package/tests/e2e/MarkdownEditor/jsx-selection.test.tsx +28 -27
  27. package/tests/e2e/MarkdownEditor/link-placeholder.test.tsx +49 -48
  28. package/tests/e2e/MarkdownEditor/media-align.test.tsx +40 -39
  29. package/tests/e2e/MarkdownEditor/media-edit.test.tsx +49 -48
  30. package/tests/e2e/MarkdownEditor/media-sizing.test.tsx +108 -107
  31. package/tests/e2e/MarkdownEditor/modes.test.tsx +79 -78
  32. package/tests/e2e/MarkdownEditor/onimage-parity.test.tsx +29 -28
  33. package/tests/e2e/MarkdownEditor/regressions.test.tsx +194 -193
  34. package/tests/e2e/MarkdownEditor/slash-commands.test.tsx +81 -80
  35. package/tests/e2e/MarkdownEditor/table-click.test.tsx +36 -35
  36. package/tests/e2e/MarkdownEditor/table-controls.test.tsx +44 -43
  37. package/tests/e2e/MarkdownEditor/table-format.test.tsx +28 -27
  38. package/tests/e2e/MarkdownEditor/undo-redo.test.tsx +22 -21
  39. package/tests/e2e/MarkdownViewer/parity.test.tsx +144 -143
  40. package/tests/e2e/MarkdownViewer/render.test.tsx +20 -19
  41. package/tests/e2e/Stacker/stacker.test.tsx +304 -0
  42. package/tests/unit/Editor/helpers.test.ts +26 -26
  43. package/tests/unit/MarkdownConformance/error-isolation.test.tsx +26 -25
  44. package/tests/unit/MarkdownConformance/indentation.test.tsx +20 -19
  45. package/tests/unit/MarkdownConformance/inline-code-fuzz.test.tsx +255 -0
  46. package/tests/unit/MarkdownConformance/jsx-attributes.test.tsx +39 -38
  47. package/tests/unit/MarkdownConformance/jsx-fuzz.test.tsx +122 -122
  48. package/tests/unit/MarkdownConformance/links.test.tsx +78 -54
  49. package/tests/unit/MarkdownConformance/render-helpers.tsx +60 -60
  50. package/tests/unit/MarkdownEditor/code-info.test.ts +58 -58
  51. package/tests/unit/MarkdownEditor/decorations.test.ts +423 -421
  52. package/tests/unit/MarkdownEditor/editor-ready.test.ts +22 -22
  53. package/tests/unit/MarkdownEditor/html-descriptors.test.ts +70 -70
  54. package/tests/unit/MarkdownEditor/indented-fence.test.ts +80 -80
  55. package/tests/unit/MarkdownEditor/jsx-attr-scan.test.ts +87 -87
  56. package/tests/unit/MarkdownEditor/jsx-block-widget.test.ts +51 -51
  57. package/tests/unit/MarkdownEditor/jsx-tag-grammar.test.ts +63 -63
  58. package/tests/unit/MarkdownEditor/list-indent.test.ts +67 -67
  59. package/tests/unit/MarkdownEditor/slash-commands.test.ts +150 -150
  60. package/tests/unit/MarkdownEditor/table-format.test.ts +67 -67
  61. package/tests/unit/MarkdownEditor/table.test.ts +96 -96
  62. package/tests/unit/MarkdownEditor/triggers.test.ts +167 -167
  63. package/tests/unit/MarkdownEditor/widget-store.test.ts +90 -90
  64. package/tests/unit/MarkdownViewer/callout.test.tsx +42 -42
  65. package/tests/unit/MarkdownViewer/code-highlight.test.tsx +41 -41
  66. package/tests/unit/MarkdownViewer/code-title.test.tsx +45 -45
  67. package/tests/unit/MarkdownViewer/features.test.tsx +129 -129
  68. package/tests/unit/MarkdownViewer/headings.test.tsx +28 -28
  69. package/tests/unit/MarkdownViewer/indented-jsx-children.test.tsx +53 -53
  70. package/tests/unit/MarkdownViewer/jsx-block-nesting.test.tsx +137 -137
  71. package/tests/unit/MarkdownViewer/jsx.test.tsx +188 -188
  72. package/tests/unit/MarkdownViewer/list-bullets.test.tsx +31 -31
  73. package/tests/unit/MarkdownViewer/list-code.test.tsx +45 -45
  74. package/tests/unit/MarkdownViewer/preserve-newlines.test.tsx +109 -109
  75. package/tests/unit/MarkdownViewer/read-time.test.ts +13 -13
  76. package/tests/unit/MarkdownViewer/renderers.test.tsx +75 -75
  77. package/tests/unit/MarkdownViewer/runnable.test.tsx +51 -51
  78. package/tests/unit/MarkdownViewer/ssr.test.tsx +56 -55
  79. package/tests/unit/MarkdownViewer/syntax-highlighting.test.tsx +43 -42
  80. package/tests/unit/MarkdownViewer/tabs-fenced-code.test.tsx +120 -120
  81. package/tests/unit/MarkdownViewer/tabs.test.tsx +83 -82
  82. package/tests/unit/Stacker/drag.test.ts +21 -0
  83. package/tests/unit/Stacker/helpers.test.ts +105 -0
  84. package/tests/unit/Stacker/reducer.test.ts +107 -0
  85. package/tools/duplicate-vector-mappings.json +141 -141
  86. package/tools/glass-export-overrides.json +6 -6
  87. package/tools/icon-parser.ts +41 -40
  88. package/tools/integration-gradient-exports.json +55 -55
  89. package/tools/picto-compare.ts +32 -32
  90. package/tools/picto-merge-urls.ts +19 -19
  91. package/tools/picto-sync.ts +543 -543
  92. package/tools/picto-verify-gradients.ts +50 -50
  93. package/tools/vector-export-nodes.json +975 -975
@@ -8,206 +8,206 @@ import { createMarkdownLanguage } from "@src/lib/markdown/grammar/language";
8
8
  import { describe, expect, it } from "vitest";
9
9
 
10
10
  function createView(doc: string, cursor = doc.length): EditorView {
11
- const state = EditorState.create({
12
- doc,
13
- extensions: [
14
- createMarkdownLanguage({ extensions: [Callout] }),
15
- slashSessionField,
16
- slashDecorationsField,
17
- ],
18
- selection: EditorSelection.cursor(cursor),
19
- });
20
-
21
- const parent = document.createElement("div");
22
- document.body.appendChild(parent);
23
-
24
- return new EditorView({ state, parent });
11
+ const state = EditorState.create({
12
+ doc,
13
+ extensions: [
14
+ createMarkdownLanguage({ extensions: [Callout] }),
15
+ slashSessionField,
16
+ slashDecorationsField,
17
+ ],
18
+ selection: EditorSelection.cursor(cursor),
19
+ });
20
+
21
+ const parent = document.createElement("div");
22
+ document.body.appendChild(parent);
23
+
24
+ return new EditorView({ state, parent });
25
25
  }
26
26
 
27
27
  /** Simulate typing `text` one character at a time as `input.type` events. */
28
28
  function type(view: EditorView, text: string): void {
29
- for (const char of text) {
30
- const pos = view.state.selection.main.head;
31
- view.dispatch({
32
- changes: { from: pos, insert: char },
33
- selection: EditorSelection.cursor(pos + 1),
34
- userEvent: "input.type",
35
- });
36
- }
29
+ for (const char of text) {
30
+ const pos = view.state.selection.main.head;
31
+ view.dispatch({
32
+ changes: { from: pos, insert: char },
33
+ selection: EditorSelection.cursor(pos + 1),
34
+ userEvent: "input.type",
35
+ });
36
+ }
37
37
  }
38
38
 
39
39
  function command(id: string) {
40
- const found = DEFAULT_SLASH_COMMANDS.find((c) => c.id === id);
41
- if (!found) throw new Error(`unknown command ${id}`);
42
- return found;
40
+ const found = DEFAULT_SLASH_COMMANDS.find((c) => c.id === id);
41
+ if (!found) throw new Error(`unknown command ${id}`);
42
+ return found;
43
43
  }
44
44
 
45
45
  describe("slashSessionField", () => {
46
- it("opens a session when typing / at the start of a line", () => {
47
- const view = createView("", 0);
48
- type(view, "/");
46
+ it("opens a session when typing / at the start of a line", () => {
47
+ const view = createView("", 0);
48
+ type(view, "/");
49
49
 
50
- expect(getSlashSession(view.state)).toEqual({ from: 0, query: "" });
50
+ expect(getSlashSession(view.state)).toEqual({ from: 0, query: "" });
51
51
 
52
- view.destroy();
53
- });
52
+ view.destroy();
53
+ });
54
54
 
55
- it("opens a session when typing / after whitespace", () => {
56
- const view = createView("foo ", 4);
57
- type(view, "/");
55
+ it("opens a session when typing / after whitespace", () => {
56
+ const view = createView("foo ", 4);
57
+ type(view, "/");
58
58
 
59
- expect(getSlashSession(view.state)?.from).toBe(4);
59
+ expect(getSlashSession(view.state)?.from).toBe(4);
60
60
 
61
- view.destroy();
62
- });
61
+ view.destroy();
62
+ });
63
63
 
64
- it("does not open a session mid-word", () => {
65
- const view = createView("foo", 3);
66
- type(view, "/");
64
+ it("does not open a session mid-word", () => {
65
+ const view = createView("foo", 3);
66
+ type(view, "/");
67
67
 
68
- expect(getSlashSession(view.state)).toBeNull();
68
+ expect(getSlashSession(view.state)).toBeNull();
69
69
 
70
- view.destroy();
71
- });
70
+ view.destroy();
71
+ });
72
72
 
73
- it("does not open a session inside a fenced code block", () => {
74
- const view = createView("```\n\n```", 4);
75
- type(view, "/");
73
+ it("does not open a session inside a fenced code block", () => {
74
+ const view = createView("```\n\n```", 4);
75
+ type(view, "/");
76
76
 
77
- expect(getSlashSession(view.state)).toBeNull();
77
+ expect(getSlashSession(view.state)).toBeNull();
78
78
 
79
- view.destroy();
80
- });
79
+ view.destroy();
80
+ });
81
81
 
82
- it("tracks the query as the user continues typing", () => {
83
- const view = createView("", 0);
84
- type(view, "/head");
82
+ it("tracks the query as the user continues typing", () => {
83
+ const view = createView("", 0);
84
+ type(view, "/head");
85
85
 
86
- expect(getSlashSession(view.state)?.query).toBe("head");
86
+ expect(getSlashSession(view.state)?.query).toBe("head");
87
87
 
88
- view.destroy();
89
- });
88
+ view.destroy();
89
+ });
90
90
 
91
- it("keeps the session open across a single space but closes on two", () => {
92
- const view = createView("", 0);
93
- type(view, "/a ");
94
- expect(getSlashSession(view.state)?.query).toBe("a ");
91
+ it("keeps the session open across a single space but closes on two", () => {
92
+ const view = createView("", 0);
93
+ type(view, "/a ");
94
+ expect(getSlashSession(view.state)?.query).toBe("a ");
95
95
 
96
- type(view, " ");
97
- expect(getSlashSession(view.state)).toBeNull();
96
+ type(view, " ");
97
+ expect(getSlashSession(view.state)).toBeNull();
98
98
 
99
- view.destroy();
100
- });
99
+ view.destroy();
100
+ });
101
101
 
102
- it("closes the session when the slash is deleted", () => {
103
- const view = createView("", 0);
104
- type(view, "/h");
105
- const slashPos = getSlashSession(view.state)?.from ?? -1;
102
+ it("closes the session when the slash is deleted", () => {
103
+ const view = createView("", 0);
104
+ type(view, "/h");
105
+ const slashPos = getSlashSession(view.state)?.from ?? -1;
106
106
 
107
- view.dispatch({
108
- changes: { from: slashPos, to: slashPos + 1 },
109
- userEvent: "delete",
110
- });
107
+ view.dispatch({
108
+ changes: { from: slashPos, to: slashPos + 1 },
109
+ userEvent: "delete",
110
+ });
111
111
 
112
- expect(getSlashSession(view.state)).toBeNull();
112
+ expect(getSlashSession(view.state)).toBeNull();
113
113
 
114
- view.destroy();
115
- });
114
+ view.destroy();
115
+ });
116
116
 
117
- it("closes the session when the caret moves before the slash", () => {
118
- const view = createView("", 0);
119
- type(view, "/h");
117
+ it("closes the session when the caret moves before the slash", () => {
118
+ const view = createView("", 0);
119
+ type(view, "/h");
120
120
 
121
- view.dispatch({ selection: EditorSelection.cursor(0) });
121
+ view.dispatch({ selection: EditorSelection.cursor(0) });
122
122
 
123
- expect(getSlashSession(view.state)).toBeNull();
123
+ expect(getSlashSession(view.state)).toBeNull();
124
124
 
125
- view.destroy();
126
- });
125
+ view.destroy();
126
+ });
127
127
  });
128
128
 
129
129
  describe("DEFAULT_SLASH_COMMANDS", () => {
130
- it("replaces the /query range with a heading prefix and positions the caret", () => {
131
- const view = createView("", 0);
132
- type(view, "/h1");
133
- const session = getSlashSession(view.state);
134
- expect(session).not.toBeNull();
135
-
136
- command("heading-1").run(view, {
137
- from: session?.from ?? 0,
138
- to: view.state.selection.main.head,
139
- });
140
-
141
- expect(view.state.doc.toString()).toBe("# ");
142
- expect(view.state.selection.main.head).toBe(2);
143
- // Replacing the slash text closes the session.
144
- expect(getSlashSession(view.state)).toBeNull();
145
-
146
- view.destroy();
147
- });
148
-
149
- it("inserts a fenced code block with the caret on the empty middle line", () => {
150
- const view = createView("", 0);
151
- type(view, "/code");
152
-
153
- command("code-block").run(view, {
154
- from: 0,
155
- to: view.state.selection.main.head,
156
- });
157
-
158
- expect(view.state.doc.toString()).toBe("```\n\n```");
159
- expect(view.state.selection.main.head).toBe(4);
160
-
161
- view.destroy();
162
- });
130
+ it("replaces the /query range with a heading prefix and positions the caret", () => {
131
+ const view = createView("", 0);
132
+ type(view, "/h1");
133
+ const session = getSlashSession(view.state);
134
+ expect(session).not.toBeNull();
135
+
136
+ command("heading-1").run(view, {
137
+ from: session?.from ?? 0,
138
+ to: view.state.selection.main.head,
139
+ });
140
+
141
+ expect(view.state.doc.toString()).toBe("# ");
142
+ expect(view.state.selection.main.head).toBe(2);
143
+ // Replacing the slash text closes the session.
144
+ expect(getSlashSession(view.state)).toBeNull();
145
+
146
+ view.destroy();
147
+ });
148
+
149
+ it("inserts a fenced code block with the caret on the empty middle line", () => {
150
+ const view = createView("", 0);
151
+ type(view, "/code");
152
+
153
+ command("code-block").run(view, {
154
+ from: 0,
155
+ to: view.state.selection.main.head,
156
+ });
157
+
158
+ expect(view.state.doc.toString()).toBe("```\n\n```");
159
+ expect(view.state.selection.main.head).toBe(4);
160
+
161
+ view.destroy();
162
+ });
163
163
  });
164
164
 
165
165
  function collectDecorations(view: EditorView) {
166
- const set = view.state.field(slashDecorationsField);
167
- const marks: Array<{ from: number; to: number }> = [];
168
- let widgets = 0;
169
-
170
- set.between(0, view.state.doc.length, (from, to, deco) => {
171
- if (deco.spec.widget) {
172
- widgets += 1;
173
- } else {
174
- marks.push({ from, to });
175
- }
176
- });
177
-
178
- return { marks, widgets };
166
+ const set = view.state.field(slashDecorationsField);
167
+ const marks: Array<{ from: number; to: number }> = [];
168
+ let widgets = 0;
169
+
170
+ set.between(0, view.state.doc.length, (from, to, deco) => {
171
+ if (deco.spec.widget) {
172
+ widgets += 1;
173
+ } else {
174
+ marks.push({ from, to });
175
+ }
176
+ });
177
+
178
+ return { marks, widgets };
179
179
  }
180
180
 
181
181
  describe("slashDecorationsField", () => {
182
- it("emits no decorations when there is no active session", () => {
183
- const view = createView("hello", 5);
182
+ it("emits no decorations when there is no active session", () => {
183
+ const view = createView("hello", 5);
184
184
 
185
- expect(collectDecorations(view)).toEqual({ marks: [], widgets: 0 });
185
+ expect(collectDecorations(view)).toEqual({ marks: [], widgets: 0 });
186
186
 
187
- view.destroy();
188
- });
187
+ view.destroy();
188
+ });
189
189
 
190
- it("highlights the lone slash and shows the placeholder when the query is empty", () => {
191
- const view = createView("", 0);
192
- type(view, "/");
190
+ it("highlights the lone slash and shows the placeholder when the query is empty", () => {
191
+ const view = createView("", 0);
192
+ type(view, "/");
193
193
 
194
- const { marks, widgets } = collectDecorations(view);
195
- // Mark wraps the slash and the trailing placeholder widget (0..2).
196
- expect(marks).toEqual([{ from: 0, to: 2 }]);
197
- expect(widgets).toBe(1);
194
+ const { marks, widgets } = collectDecorations(view);
195
+ // Mark wraps the slash and the trailing placeholder widget (0..2).
196
+ expect(marks).toEqual([{ from: 0, to: 2 }]);
197
+ expect(widgets).toBe(1);
198
198
 
199
- view.destroy();
200
- });
199
+ view.destroy();
200
+ });
201
201
 
202
- it("extends the highlight over the query and drops the placeholder once text is typed", () => {
203
- const view = createView("", 0);
204
- type(view, "/head");
202
+ it("extends the highlight over the query and drops the placeholder once text is typed", () => {
203
+ const view = createView("", 0);
204
+ type(view, "/head");
205
205
 
206
- const { marks, widgets } = collectDecorations(view);
207
- // Mark spans the slash + "head" (positions 0..5).
208
- expect(marks).toEqual([{ from: 0, to: 5 }]);
209
- expect(widgets).toBe(0);
206
+ const { marks, widgets } = collectDecorations(view);
207
+ // Mark spans the slash + "head" (positions 0..5).
208
+ expect(marks).toEqual([{ from: 0, to: 5 }]);
209
+ expect(widgets).toBe(0);
210
210
 
211
- view.destroy();
212
- });
211
+ view.destroy();
212
+ });
213
213
  });
@@ -3,81 +3,81 @@ import { markdownTableFormatter, tableFormatSpec } from "@src/lib/markdown/edito
3
3
  import { describe, expect, it } from "vitest";
4
4
 
5
5
  function stateWith(doc: string, caret: number): EditorState {
6
- return EditorState.create({
7
- doc,
8
- selection: EditorSelection.cursor(caret),
9
- extensions: [markdownTableFormatter()],
10
- });
6
+ return EditorState.create({
7
+ doc,
8
+ selection: EditorSelection.cursor(caret),
9
+ extensions: [markdownTableFormatter()],
10
+ });
11
11
  }
12
12
 
13
13
  describe("tableFormatSpec", () => {
14
- it("aligns columns and pads cells with trailing spaces", () => {
15
- const doc = "| a | b |\n| - | - |\n| longvalue | y |";
16
- const state = stateWith(doc, doc.indexOf("longvalue"));
17
- const spec = tableFormatSpec(state);
18
- expect(spec).not.toBeNull();
19
- const next = state.update(spec ?? {}).state;
20
- expect(next.doc.toString()).toBe(
21
- ["| a | b |", "| --------- | --- |", "| longvalue | y |"].join("\n"),
22
- );
23
- });
14
+ it("aligns columns and pads cells with trailing spaces", () => {
15
+ const doc = "| a | b |\n| - | - |\n| longvalue | y |";
16
+ const state = stateWith(doc, doc.indexOf("longvalue"));
17
+ const spec = tableFormatSpec(state);
18
+ expect(spec).not.toBeNull();
19
+ const next = state.update(spec ?? {}).state;
20
+ expect(next.doc.toString()).toBe(
21
+ ["| a | b |", "| --------- | --- |", "| longvalue | y |"].join("\n"),
22
+ );
23
+ });
24
24
 
25
- it("returns null for an already-formatted table", () => {
26
- const doc = ["| a | b |", "| --------- | --- |", "| longvalue | y |"].join(
27
- "\n",
28
- );
29
- const state = stateWith(doc, doc.indexOf("longvalue"));
30
- expect(tableFormatSpec(state)).toBeNull();
31
- });
25
+ it("returns null for an already-formatted table", () => {
26
+ const doc = ["| a | b |", "| --------- | --- |", "| longvalue | y |"].join(
27
+ "\n",
28
+ );
29
+ const state = stateWith(doc, doc.indexOf("longvalue"));
30
+ expect(tableFormatSpec(state)).toBeNull();
31
+ });
32
32
 
33
- it("returns null when the caret is not in a table", () => {
34
- const doc = "just a paragraph with a | pipe";
35
- const state = stateWith(doc, 3);
36
- expect(tableFormatSpec(state)).toBeNull();
37
- });
33
+ it("returns null when the caret is not in a table", () => {
34
+ const doc = "just a paragraph with a | pipe";
35
+ const state = stateWith(doc, 3);
36
+ expect(tableFormatSpec(state)).toBeNull();
37
+ });
38
38
 
39
- it("keeps the caret within the edited cell after formatting", () => {
40
- const doc = "| a | b |\n| - | - |\n| longvalue | y |";
41
- // Caret right after "long" inside the body cell.
42
- const caret = doc.indexOf("longvalue") + 4;
43
- const state = stateWith(doc, caret);
44
- const spec = tableFormatSpec(state);
45
- const next = state.update(spec ?? {}).state;
46
- const formatted = next.doc.toString();
47
- const cellStart = formatted.indexOf("longvalue");
48
- expect(next.selection.main.head).toBe(cellStart + 4);
49
- });
39
+ it("keeps the caret within the edited cell after formatting", () => {
40
+ const doc = "| a | b |\n| - | - |\n| longvalue | y |";
41
+ // Caret right after "long" inside the body cell.
42
+ const caret = doc.indexOf("longvalue") + 4;
43
+ const state = stateWith(doc, caret);
44
+ const spec = tableFormatSpec(state);
45
+ const next = state.update(spec ?? {}).state;
46
+ const formatted = next.doc.toString();
47
+ const cellStart = formatted.indexOf("longvalue");
48
+ expect(next.selection.main.head).toBe(cellStart + 4);
49
+ });
50
50
  });
51
51
 
52
52
  describe("markdownTableFormatter", () => {
53
- it("reformats the table after an edit inside it", () => {
54
- const doc = "| a | b |\n| - | - |\n| x | y |";
55
- const state = EditorState.create({
56
- doc,
57
- selection: EditorSelection.cursor(doc.indexOf("x") + 1),
58
- extensions: [markdownTableFormatter()],
59
- });
60
- // Type "tra" after "x" → "xtra", which widens column 1.
61
- const tr = state.update({
62
- changes: { from: doc.indexOf("x") + 1, insert: "tra" },
63
- selection: EditorSelection.cursor(doc.indexOf("x") + 4),
64
- });
65
- const next = tr.state;
66
- expect(next.doc.toString()).toContain("| xtra | y |");
67
- });
53
+ it("reformats the table after an edit inside it", () => {
54
+ const doc = "| a | b |\n| - | - |\n| x | y |";
55
+ const state = EditorState.create({
56
+ doc,
57
+ selection: EditorSelection.cursor(doc.indexOf("x") + 1),
58
+ extensions: [markdownTableFormatter()],
59
+ });
60
+ // Type "tra" after "x" → "xtra", which widens column 1.
61
+ const tr = state.update({
62
+ changes: { from: doc.indexOf("x") + 1, insert: "tra" },
63
+ selection: EditorSelection.cursor(doc.indexOf("x") + 4),
64
+ });
65
+ const next = tr.state;
66
+ expect(next.doc.toString()).toContain("| xtra | y |");
67
+ });
68
68
 
69
- it("does not loop on its own formatting transaction", () => {
70
- const doc = "| a | b |\n| - | - |\n| x | y |";
71
- const state = EditorState.create({
72
- doc,
73
- selection: EditorSelection.cursor(doc.indexOf("x") + 1),
74
- extensions: [markdownTableFormatter()],
75
- });
76
- const tr = state.update({
77
- changes: { from: doc.indexOf("x") + 1, insert: "tra" },
78
- });
79
- // A single user edit produces a stable, idempotent result.
80
- const again = tr.state.update({ selection: EditorSelection.cursor(0) });
81
- expect(again.state.doc.toString()).toBe(tr.state.doc.toString());
82
- });
69
+ it("does not loop on its own formatting transaction", () => {
70
+ const doc = "| a | b |\n| - | - |\n| x | y |";
71
+ const state = EditorState.create({
72
+ doc,
73
+ selection: EditorSelection.cursor(doc.indexOf("x") + 1),
74
+ extensions: [markdownTableFormatter()],
75
+ });
76
+ const tr = state.update({
77
+ changes: { from: doc.indexOf("x") + 1, insert: "tra" },
78
+ });
79
+ // A single user edit produces a stable, idempotent result.
80
+ const again = tr.state.update({ selection: EditorSelection.cursor(0) });
81
+ expect(again.state.doc.toString()).toBe(tr.state.doc.toString());
82
+ });
83
83
  });