@surrealdb/ui 1.2.11 → 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.
- package/.claude/launch.json +17 -0
- package/.oxfmtrc.json +27 -0
- package/.oxlintrc.json +23 -0
- package/.zed/settings.json +34 -34
- package/AGENTS.md +21 -19
- package/README.md +15 -10
- package/dist/assets/245b1ccaa4927818.svg +3 -0
- package/dist/assets/333e1fe6e67ca4a2.svg +10 -0
- package/dist/assets/4b0e10c655814886.svg +21 -0
- package/dist/assets/9932da008ea5dd32.svg +3 -0
- package/dist/assets/a372168d5789daa2.svg +4 -0
- package/dist/assets/b140e44ee4ae7e35.svg +5 -0
- package/dist/assets/e028075817d928e4.svg +3 -0
- package/dist/assets/ee1065ad5a8f7f14.svg +6 -0
- package/dist/ui.css +1 -1
- package/dist/ui.d.ts +579 -1
- package/dist/ui.js +11553 -8988
- package/dist/ui.js.map +1 -1
- package/doc/code-style.md +264 -0
- package/doc/stacker-plan.md +219 -0
- package/package.json +119 -115
- package/res/_mixins.scss +15 -11
- package/tests/_setup/e2e-helpers.tsx +85 -85
- package/tests/_setup/portable-stories.ts +11 -10
- package/tests/e2e/MarkdownEditor/content-blocks.test.tsx +140 -139
- package/tests/e2e/MarkdownEditor/edits.test.tsx +101 -100
- package/tests/e2e/MarkdownEditor/heading-fold.test.tsx +35 -34
- package/tests/e2e/MarkdownEditor/hybrid-widgets.test.tsx +178 -177
- package/tests/e2e/MarkdownEditor/image-on-image.test.tsx +50 -49
- package/tests/e2e/MarkdownEditor/jsx-block-content.test.tsx +195 -194
- package/tests/e2e/MarkdownEditor/jsx-highlight.test.tsx +47 -46
- package/tests/e2e/MarkdownEditor/jsx-inline-badges.test.tsx +35 -34
- package/tests/e2e/MarkdownEditor/jsx-inline-click.test.tsx +39 -38
- package/tests/e2e/MarkdownEditor/jsx-selection.test.tsx +28 -27
- package/tests/e2e/MarkdownEditor/link-placeholder.test.tsx +49 -48
- package/tests/e2e/MarkdownEditor/media-align.test.tsx +40 -39
- package/tests/e2e/MarkdownEditor/media-edit.test.tsx +49 -48
- package/tests/e2e/MarkdownEditor/media-sizing.test.tsx +108 -107
- package/tests/e2e/MarkdownEditor/modes.test.tsx +79 -78
- package/tests/e2e/MarkdownEditor/onimage-parity.test.tsx +29 -28
- package/tests/e2e/MarkdownEditor/regressions.test.tsx +194 -193
- package/tests/e2e/MarkdownEditor/slash-commands.test.tsx +81 -80
- package/tests/e2e/MarkdownEditor/table-click.test.tsx +36 -35
- package/tests/e2e/MarkdownEditor/table-controls.test.tsx +44 -43
- package/tests/e2e/MarkdownEditor/table-format.test.tsx +28 -27
- package/tests/e2e/MarkdownEditor/undo-redo.test.tsx +22 -21
- package/tests/e2e/MarkdownViewer/parity.test.tsx +144 -143
- package/tests/e2e/MarkdownViewer/render.test.tsx +20 -19
- package/tests/e2e/Stacker/stacker.test.tsx +304 -0
- package/tests/unit/Editor/helpers.test.ts +26 -26
- package/tests/unit/MarkdownConformance/error-isolation.test.tsx +26 -25
- package/tests/unit/MarkdownConformance/indentation.test.tsx +20 -19
- package/tests/unit/MarkdownConformance/inline-code-fuzz.test.tsx +255 -0
- package/tests/unit/MarkdownConformance/jsx-attributes.test.tsx +39 -38
- package/tests/unit/MarkdownConformance/jsx-fuzz.test.tsx +122 -122
- package/tests/unit/MarkdownConformance/links.test.tsx +78 -54
- package/tests/unit/MarkdownConformance/render-helpers.tsx +60 -60
- package/tests/unit/MarkdownEditor/code-info.test.ts +58 -58
- package/tests/unit/MarkdownEditor/decorations.test.ts +423 -421
- package/tests/unit/MarkdownEditor/editor-ready.test.ts +22 -22
- package/tests/unit/MarkdownEditor/html-descriptors.test.ts +70 -70
- package/tests/unit/MarkdownEditor/indented-fence.test.ts +80 -80
- package/tests/unit/MarkdownEditor/jsx-attr-scan.test.ts +87 -87
- package/tests/unit/MarkdownEditor/jsx-block-widget.test.ts +51 -51
- package/tests/unit/MarkdownEditor/jsx-tag-grammar.test.ts +63 -63
- package/tests/unit/MarkdownEditor/list-indent.test.ts +67 -67
- package/tests/unit/MarkdownEditor/slash-commands.test.ts +150 -150
- package/tests/unit/MarkdownEditor/table-format.test.ts +67 -67
- package/tests/unit/MarkdownEditor/table.test.ts +96 -96
- package/tests/unit/MarkdownEditor/triggers.test.ts +167 -167
- package/tests/unit/MarkdownEditor/widget-store.test.ts +90 -90
- package/tests/unit/MarkdownViewer/callout.test.tsx +42 -42
- package/tests/unit/MarkdownViewer/code-highlight.test.tsx +41 -41
- package/tests/unit/MarkdownViewer/code-title.test.tsx +45 -45
- package/tests/unit/MarkdownViewer/features.test.tsx +129 -129
- package/tests/unit/MarkdownViewer/headings.test.tsx +28 -28
- package/tests/unit/MarkdownViewer/indented-jsx-children.test.tsx +53 -53
- package/tests/unit/MarkdownViewer/jsx-block-nesting.test.tsx +137 -137
- package/tests/unit/MarkdownViewer/jsx.test.tsx +188 -188
- package/tests/unit/MarkdownViewer/list-bullets.test.tsx +31 -31
- package/tests/unit/MarkdownViewer/list-code.test.tsx +45 -45
- package/tests/unit/MarkdownViewer/preserve-newlines.test.tsx +109 -109
- package/tests/unit/MarkdownViewer/read-time.test.ts +13 -13
- package/tests/unit/MarkdownViewer/renderers.test.tsx +75 -75
- package/tests/unit/MarkdownViewer/runnable.test.tsx +51 -51
- package/tests/unit/MarkdownViewer/ssr.test.tsx +56 -55
- package/tests/unit/MarkdownViewer/syntax-highlighting.test.tsx +43 -42
- package/tests/unit/MarkdownViewer/tabs-fenced-code.test.tsx +120 -120
- package/tests/unit/MarkdownViewer/tabs.test.tsx +83 -82
- package/tests/unit/Stacker/drag.test.ts +21 -0
- package/tests/unit/Stacker/helpers.test.ts +105 -0
- package/tests/unit/Stacker/reducer.test.ts +107 -0
- package/tools/duplicate-vector-mappings.json +141 -141
- package/tools/glass-export-overrides.json +6 -6
- package/tools/icon-parser.ts +41 -40
- package/tools/integration-gradient-exports.json +55 -55
- package/tools/picto-compare.ts +32 -32
- package/tools/picto-merge-urls.ts +19 -19
- package/tools/picto-sync.ts +543 -543
- package/tools/picto-verify-gradients.ts +50 -50
- 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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
it("opens a session when typing / at the start of a line", () => {
|
|
47
|
+
const view = createView("", 0);
|
|
48
|
+
type(view, "/");
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
expect(getSlashSession(view.state)).toEqual({ from: 0, query: "" });
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
view.destroy();
|
|
53
|
+
});
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
it("opens a session when typing / after whitespace", () => {
|
|
56
|
+
const view = createView("foo ", 4);
|
|
57
|
+
type(view, "/");
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
expect(getSlashSession(view.state)?.from).toBe(4);
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
view.destroy();
|
|
62
|
+
});
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
it("does not open a session mid-word", () => {
|
|
65
|
+
const view = createView("foo", 3);
|
|
66
|
+
type(view, "/");
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
expect(getSlashSession(view.state)).toBeNull();
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
view.destroy();
|
|
71
|
+
});
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
77
|
+
expect(getSlashSession(view.state)).toBeNull();
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
view.destroy();
|
|
80
|
+
});
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
it("tracks the query as the user continues typing", () => {
|
|
83
|
+
const view = createView("", 0);
|
|
84
|
+
type(view, "/head");
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
expect(getSlashSession(view.state)?.query).toBe("head");
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
view.destroy();
|
|
89
|
+
});
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
97
|
-
|
|
96
|
+
type(view, " ");
|
|
97
|
+
expect(getSlashSession(view.state)).toBeNull();
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
view.destroy();
|
|
100
|
+
});
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
view.dispatch({
|
|
108
|
+
changes: { from: slashPos, to: slashPos + 1 },
|
|
109
|
+
userEvent: "delete",
|
|
110
|
+
});
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
expect(getSlashSession(view.state)).toBeNull();
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
view.destroy();
|
|
115
|
+
});
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
it("closes the session when the caret moves before the slash", () => {
|
|
118
|
+
const view = createView("", 0);
|
|
119
|
+
type(view, "/h");
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
view.dispatch({ selection: EditorSelection.cursor(0) });
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
expect(getSlashSession(view.state)).toBeNull();
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
view.destroy();
|
|
126
|
+
});
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
describe("DEFAULT_SLASH_COMMANDS", () => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
183
|
-
|
|
182
|
+
it("emits no decorations when there is no active session", () => {
|
|
183
|
+
const view = createView("hello", 5);
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
expect(collectDecorations(view)).toEqual({ marks: [], widgets: 0 });
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
view.destroy();
|
|
188
|
+
});
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
200
|
-
|
|
199
|
+
view.destroy();
|
|
200
|
+
});
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
});
|