vitest-prosemirror 0.3.0 → 0.3.2
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/README.md +1 -1
- package/dist/vitest-prosemirror.d.ts +0 -1
- package/dist/vitest-prosemirror.js +607 -326
- package/package.json +57 -36
- package/dist/vitest-prosemirror.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/vitest-prosemirror)
|
|
4
4
|
[](https://github.com/marekdedic/vitest-prosemirror/actions)
|
|
5
|
-
[](https://app.codecov.io/gh/marekdedic/vitest-prosemirror)
|
|
6
6
|
[](https://www.npmjs.com/package/vitest-prosemirror)
|
|
7
7
|
[](https://github.com/marekdedic/vitest-prosemirror/blob/master/LICENSE)
|
|
8
8
|
|
|
@@ -26,7 +26,6 @@ export declare class ProseMirrorTester {
|
|
|
26
26
|
constructor(documentRoot: Node_2, options?: Partial<Options>);
|
|
27
27
|
insertText(text: string, modifiers?: KeyboardModifiers): void;
|
|
28
28
|
selectText(selection: TesterSelection): void;
|
|
29
|
-
private getSelection;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export declare type TesterSelection = "all" | "end" | "start" | {
|
|
@@ -1,345 +1,626 @@
|
|
|
1
1
|
import { expect } from "vitest";
|
|
2
2
|
import stringifyObject from "stringify-object";
|
|
3
|
-
import { EditorState,
|
|
3
|
+
import { AllSelection, EditorState, Selection, TextSelection } from "prosemirror-state";
|
|
4
4
|
import { EditorView } from "prosemirror-view";
|
|
5
|
+
//#region src/stringifyProseMirrorNode.ts
|
|
5
6
|
function getMarks(marks, origContent) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
content = `${mark.type.name}(${items.join(", ")})`;
|
|
19
|
-
}
|
|
20
|
-
return content;
|
|
7
|
+
let content = `'${origContent}'`;
|
|
8
|
+
for (const mark of [...marks].reverse()) {
|
|
9
|
+
const hasAttrs = Object.keys(mark.attrs).length > 0;
|
|
10
|
+
const items = [content];
|
|
11
|
+
if (hasAttrs) items.unshift(stringifyObject(mark.attrs, {
|
|
12
|
+
indent: " ",
|
|
13
|
+
inlineCharacterLimit: 1e3
|
|
14
|
+
}));
|
|
15
|
+
content = `${mark.type.name}(${items.join(", ")})`;
|
|
16
|
+
}
|
|
17
|
+
return content;
|
|
21
18
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
var renamedTypes = {
|
|
20
|
+
hardBreak: "br",
|
|
21
|
+
heading: "h",
|
|
22
|
+
horizontalRule: "hr",
|
|
23
|
+
paragraph: "p"
|
|
27
24
|
};
|
|
28
25
|
function stringifyProseMirrorNode(node, indentation = "") {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
content.push(stringifyProseMirrorNode(item, nextIndentation));
|
|
44
|
-
});
|
|
45
|
-
if (!hasAttrs && content.length === 1 && ((_a = node.content.firstChild) == null ? void 0 : _a.type.name) === "text") {
|
|
46
|
-
return `${indentation}${type}(${stringifyProseMirrorNode(node.content.firstChild, "")})`;
|
|
47
|
-
}
|
|
48
|
-
const joiner = `,
|
|
49
|
-
`;
|
|
50
|
-
const prefix = hasAttrs ? `
|
|
51
|
-
${nextIndentation}` : content.length > 0 ? "\n" : "";
|
|
52
|
-
const postfix = content.length > 0 ? `
|
|
53
|
-
${indentation}` : "";
|
|
54
|
-
return `${indentation}${type}(${prefix}${content.join(joiner)},${postfix})`;
|
|
26
|
+
if (node.type.name === "text") return `${indentation}${getMarks(node.marks, node.text ?? "")}`;
|
|
27
|
+
const type = renamedTypes[node.type.name] ?? node.type.name;
|
|
28
|
+
const content = [];
|
|
29
|
+
const nextIndentation = `${indentation} `;
|
|
30
|
+
if (Object.keys(node.attrs).length > 0) content.push(`${nextIndentation}${stringifyObject(node.attrs, {
|
|
31
|
+
indent: " ",
|
|
32
|
+
inlineCharacterLimit: 1e3
|
|
33
|
+
})},`);
|
|
34
|
+
node.content.forEach((item) => {
|
|
35
|
+
content.push(`${stringifyProseMirrorNode(item, nextIndentation)},`);
|
|
36
|
+
});
|
|
37
|
+
if (content.length === 0) return `${indentation}${type}()`;
|
|
38
|
+
if (content.length === 1 && node.content.firstChild?.type.name === "text") return `${indentation}${type}(${stringifyProseMirrorNode(node.content.firstChild, "")})`;
|
|
39
|
+
return `${indentation}${type}(\n${content.join("\n")}\n${indentation})`;
|
|
55
40
|
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/utils/mockRangeRects.ts
|
|
43
|
+
var mockRangeRects = () => {
|
|
44
|
+
const zeroRect = () => new DOMRect(0, 0, 0, 0);
|
|
45
|
+
const emptyRectList = () => [];
|
|
46
|
+
const rangePrototype = Range.prototype;
|
|
47
|
+
if (typeof rangePrototype.getBoundingClientRect !== "function") Range.prototype.getBoundingClientRect = zeroRect;
|
|
48
|
+
if (typeof rangePrototype.getClientRects !== "function") Range.prototype.getClientRects = emptyRectList;
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/utils/MutationObserverMock.ts
|
|
52
|
+
var MutationObserverMock = class MutationObserverMock {
|
|
53
|
+
static activeObservers = /* @__PURE__ */ new Map();
|
|
54
|
+
callback;
|
|
55
|
+
target;
|
|
56
|
+
constructor(callback) {
|
|
57
|
+
this.callback = callback;
|
|
58
|
+
this.target = void 0;
|
|
59
|
+
}
|
|
60
|
+
static createMutation(target, mutationRecords) {
|
|
61
|
+
const observer = MutationObserverMock.activeObservers.get(target);
|
|
62
|
+
if (observer === void 0) return;
|
|
63
|
+
observer.callback(mutationRecords.map((record) => ({
|
|
64
|
+
addedNodes: [],
|
|
65
|
+
attributeName: null,
|
|
66
|
+
attributeNamespace: null,
|
|
67
|
+
nextSibling: null,
|
|
68
|
+
oldValue: null,
|
|
69
|
+
previousSibling: null,
|
|
70
|
+
removedNodes: [],
|
|
71
|
+
...record
|
|
72
|
+
})), observer);
|
|
73
|
+
}
|
|
74
|
+
disconnect() {
|
|
75
|
+
if (this.target !== void 0) MutationObserverMock.activeObservers.delete(this.target);
|
|
76
|
+
this.target = void 0;
|
|
77
|
+
}
|
|
78
|
+
observe(target) {
|
|
79
|
+
this.target = target;
|
|
80
|
+
MutationObserverMock.activeObservers.set(target, this);
|
|
81
|
+
}
|
|
82
|
+
takeRecords() {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/utils/selection.ts
|
|
88
|
+
var resolveSelection = (doc, selection) => {
|
|
89
|
+
if (selection === "all") return new AllSelection(doc);
|
|
90
|
+
if (typeof selection === "object" && "$anchor" in selection && "$head" in selection) return selection;
|
|
91
|
+
if (typeof selection === "object" && "from" in selection && "to" in selection) return TextSelection.between(doc.resolve(selection.from), doc.resolve(selection.to));
|
|
92
|
+
let pos = 0;
|
|
93
|
+
if (selection === "end") pos = doc.nodeSize - 2;
|
|
94
|
+
else if (selection !== "start") pos = selection;
|
|
95
|
+
return TextSelection.near(doc.resolve(pos));
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/utils/dom.ts
|
|
99
|
+
var characterDataAt = (node, offset) => {
|
|
100
|
+
if (node instanceof CharacterData) return {
|
|
101
|
+
offset,
|
|
102
|
+
target: node
|
|
103
|
+
};
|
|
104
|
+
const after = node.childNodes[offset];
|
|
105
|
+
if (after instanceof CharacterData) return {
|
|
106
|
+
offset: 0,
|
|
107
|
+
target: after
|
|
108
|
+
};
|
|
109
|
+
return null;
|
|
110
|
+
};
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/utils/keyboardInput.ts
|
|
56
113
|
function tokenizeKeyboardInput(input) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
currentGroupOpener = null;
|
|
79
|
-
group = "";
|
|
80
|
-
} else {
|
|
81
|
-
group += char;
|
|
82
|
-
}
|
|
83
|
-
} else if (["[", "{"].includes(char)) {
|
|
84
|
-
currentGroupOpener = char;
|
|
85
|
-
} else {
|
|
86
|
-
output.push(char);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
output.forEach(assertSupported);
|
|
90
|
-
return output;
|
|
114
|
+
const output = [];
|
|
115
|
+
let currentGroupOpener = null;
|
|
116
|
+
let group = "";
|
|
117
|
+
for (const char of input) if (currentGroupOpener !== null) {
|
|
118
|
+
if (["]", "}"].includes(char)) {
|
|
119
|
+
if (group.endsWith("\\") && char === matchingBrace(currentGroupOpener)) group = group.slice(0, -2) + char;
|
|
120
|
+
else if (char === matchingBrace(currentGroupOpener)) {
|
|
121
|
+
if (group.length === 4 && group.startsWith("Key")) output.push(group.slice(3).toLowerCase());
|
|
122
|
+
else output.push(group);
|
|
123
|
+
currentGroupOpener = null;
|
|
124
|
+
group = "";
|
|
125
|
+
} else group += char;
|
|
126
|
+
} else if (group === "" && currentGroupOpener === char) {
|
|
127
|
+
output.push(char);
|
|
128
|
+
currentGroupOpener = null;
|
|
129
|
+
group = "";
|
|
130
|
+
} else group += char;
|
|
131
|
+
} else if (["[", "{"].includes(char)) currentGroupOpener = char;
|
|
132
|
+
else output.push(char);
|
|
133
|
+
output.forEach(assertSupported);
|
|
134
|
+
return output;
|
|
91
135
|
}
|
|
92
136
|
function assertSupported(character) {
|
|
93
|
-
|
|
94
|
-
throw new Error("Unsupported keyboard input");
|
|
95
|
-
}
|
|
137
|
+
if (/^\/.+/u.exec(character) || /.+>[\d]*\/?$/u.exec(character)) throw new Error("Unsupported keyboard input");
|
|
96
138
|
}
|
|
97
139
|
function matchingBrace(opener) {
|
|
98
|
-
|
|
140
|
+
return opener === "{" ? "}" : "]";
|
|
99
141
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/utils/keyIdentity.ts
|
|
144
|
+
var namedKeys = new Map(Object.entries({
|
|
145
|
+
Alt: 18,
|
|
146
|
+
ArrowDown: 40,
|
|
147
|
+
ArrowLeft: 37,
|
|
148
|
+
ArrowRight: 39,
|
|
149
|
+
ArrowUp: 38,
|
|
150
|
+
Backspace: 8,
|
|
151
|
+
CapsLock: 20,
|
|
152
|
+
Control: 17,
|
|
153
|
+
Delete: 46,
|
|
154
|
+
End: 35,
|
|
155
|
+
Enter: 13,
|
|
156
|
+
Escape: 27,
|
|
157
|
+
F1: 112,
|
|
158
|
+
F2: 113,
|
|
159
|
+
F3: 114,
|
|
160
|
+
F4: 115,
|
|
161
|
+
F5: 116,
|
|
162
|
+
F6: 117,
|
|
163
|
+
F7: 118,
|
|
164
|
+
F8: 119,
|
|
165
|
+
F9: 120,
|
|
166
|
+
F10: 121,
|
|
167
|
+
F11: 122,
|
|
168
|
+
F12: 123,
|
|
169
|
+
Home: 36,
|
|
170
|
+
Insert: 45,
|
|
171
|
+
Meta: 91,
|
|
172
|
+
PageDown: 34,
|
|
173
|
+
PageUp: 33,
|
|
174
|
+
Shift: 16,
|
|
175
|
+
Tab: 9
|
|
176
|
+
}));
|
|
177
|
+
var sidedCodes = new Map(Object.entries({
|
|
178
|
+
Alt: "AltLeft",
|
|
179
|
+
Control: "ControlLeft",
|
|
180
|
+
Meta: "MetaLeft",
|
|
181
|
+
Shift: "ShiftLeft"
|
|
182
|
+
}));
|
|
183
|
+
var standardLocation = 0;
|
|
184
|
+
var leftLocation = 1;
|
|
185
|
+
var namedCharCodes = new Map(Object.entries({ Enter: 13 }));
|
|
186
|
+
var noCharacter = 0;
|
|
187
|
+
var characterKeys = new Map(Object.entries({
|
|
188
|
+
" ": {
|
|
189
|
+
charCode: 32,
|
|
190
|
+
code: "Space",
|
|
191
|
+
key: " ",
|
|
192
|
+
keyCode: 32,
|
|
193
|
+
location: standardLocation
|
|
194
|
+
},
|
|
195
|
+
"!": {
|
|
196
|
+
charCode: 33,
|
|
197
|
+
code: "Digit1",
|
|
198
|
+
key: "!",
|
|
199
|
+
keyCode: 49,
|
|
200
|
+
location: standardLocation
|
|
201
|
+
},
|
|
202
|
+
"\"": {
|
|
203
|
+
charCode: 34,
|
|
204
|
+
code: "Quote",
|
|
205
|
+
key: "\"",
|
|
206
|
+
keyCode: 222,
|
|
207
|
+
location: standardLocation
|
|
208
|
+
},
|
|
209
|
+
"#": {
|
|
210
|
+
charCode: 35,
|
|
211
|
+
code: "Digit3",
|
|
212
|
+
key: "#",
|
|
213
|
+
keyCode: 51,
|
|
214
|
+
location: standardLocation
|
|
215
|
+
},
|
|
216
|
+
$: {
|
|
217
|
+
charCode: 36,
|
|
218
|
+
code: "Digit4",
|
|
219
|
+
key: "$",
|
|
220
|
+
keyCode: 52,
|
|
221
|
+
location: standardLocation
|
|
222
|
+
},
|
|
223
|
+
"%": {
|
|
224
|
+
charCode: 37,
|
|
225
|
+
code: "Digit5",
|
|
226
|
+
key: "%",
|
|
227
|
+
keyCode: 53,
|
|
228
|
+
location: standardLocation
|
|
229
|
+
},
|
|
230
|
+
"&": {
|
|
231
|
+
charCode: 38,
|
|
232
|
+
code: "Digit7",
|
|
233
|
+
key: "&",
|
|
234
|
+
keyCode: 55,
|
|
235
|
+
location: standardLocation
|
|
236
|
+
},
|
|
237
|
+
"'": {
|
|
238
|
+
charCode: 39,
|
|
239
|
+
code: "Quote",
|
|
240
|
+
key: "'",
|
|
241
|
+
keyCode: 222,
|
|
242
|
+
location: standardLocation
|
|
243
|
+
},
|
|
244
|
+
"(": {
|
|
245
|
+
charCode: 40,
|
|
246
|
+
code: "Digit9",
|
|
247
|
+
key: "(",
|
|
248
|
+
keyCode: 57,
|
|
249
|
+
location: standardLocation
|
|
250
|
+
},
|
|
251
|
+
")": {
|
|
252
|
+
charCode: 41,
|
|
253
|
+
code: "Digit0",
|
|
254
|
+
key: ")",
|
|
255
|
+
keyCode: 48,
|
|
256
|
+
location: standardLocation
|
|
257
|
+
},
|
|
258
|
+
"*": {
|
|
259
|
+
charCode: 42,
|
|
260
|
+
code: "Digit8",
|
|
261
|
+
key: "*",
|
|
262
|
+
keyCode: 56,
|
|
263
|
+
location: standardLocation
|
|
264
|
+
},
|
|
265
|
+
"+": {
|
|
266
|
+
charCode: 43,
|
|
267
|
+
code: "Equal",
|
|
268
|
+
key: "+",
|
|
269
|
+
keyCode: 187,
|
|
270
|
+
location: standardLocation
|
|
271
|
+
},
|
|
272
|
+
",": {
|
|
273
|
+
charCode: 44,
|
|
274
|
+
code: "Comma",
|
|
275
|
+
key: ",",
|
|
276
|
+
keyCode: 188,
|
|
277
|
+
location: standardLocation
|
|
278
|
+
},
|
|
279
|
+
"-": {
|
|
280
|
+
charCode: 45,
|
|
281
|
+
code: "Minus",
|
|
282
|
+
key: "-",
|
|
283
|
+
keyCode: 189,
|
|
284
|
+
location: standardLocation
|
|
285
|
+
},
|
|
286
|
+
".": {
|
|
287
|
+
charCode: 46,
|
|
288
|
+
code: "Period",
|
|
289
|
+
key: ".",
|
|
290
|
+
keyCode: 190,
|
|
291
|
+
location: standardLocation
|
|
292
|
+
},
|
|
293
|
+
"/": {
|
|
294
|
+
charCode: 47,
|
|
295
|
+
code: "Slash",
|
|
296
|
+
key: "/",
|
|
297
|
+
keyCode: 191,
|
|
298
|
+
location: standardLocation
|
|
299
|
+
},
|
|
300
|
+
":": {
|
|
301
|
+
charCode: 58,
|
|
302
|
+
code: "Semicolon",
|
|
303
|
+
key: ":",
|
|
304
|
+
keyCode: 186,
|
|
305
|
+
location: standardLocation
|
|
306
|
+
},
|
|
307
|
+
";": {
|
|
308
|
+
charCode: 59,
|
|
309
|
+
code: "Semicolon",
|
|
310
|
+
key: ";",
|
|
311
|
+
keyCode: 186,
|
|
312
|
+
location: standardLocation
|
|
313
|
+
},
|
|
314
|
+
"<": {
|
|
315
|
+
charCode: 60,
|
|
316
|
+
code: "Comma",
|
|
317
|
+
key: "<",
|
|
318
|
+
keyCode: 188,
|
|
319
|
+
location: standardLocation
|
|
320
|
+
},
|
|
321
|
+
"=": {
|
|
322
|
+
charCode: 61,
|
|
323
|
+
code: "Equal",
|
|
324
|
+
key: "=",
|
|
325
|
+
keyCode: 187,
|
|
326
|
+
location: standardLocation
|
|
327
|
+
},
|
|
328
|
+
">": {
|
|
329
|
+
charCode: 62,
|
|
330
|
+
code: "Period",
|
|
331
|
+
key: ">",
|
|
332
|
+
keyCode: 190,
|
|
333
|
+
location: standardLocation
|
|
334
|
+
},
|
|
335
|
+
"?": {
|
|
336
|
+
charCode: 63,
|
|
337
|
+
code: "Slash",
|
|
338
|
+
key: "?",
|
|
339
|
+
keyCode: 191,
|
|
340
|
+
location: standardLocation
|
|
341
|
+
},
|
|
342
|
+
"@": {
|
|
343
|
+
charCode: 64,
|
|
344
|
+
code: "Digit2",
|
|
345
|
+
key: "@",
|
|
346
|
+
keyCode: 50,
|
|
347
|
+
location: standardLocation
|
|
348
|
+
},
|
|
349
|
+
"[": {
|
|
350
|
+
charCode: 91,
|
|
351
|
+
code: "BracketLeft",
|
|
352
|
+
key: "[",
|
|
353
|
+
keyCode: 219,
|
|
354
|
+
location: standardLocation
|
|
355
|
+
},
|
|
356
|
+
"\\": {
|
|
357
|
+
charCode: 92,
|
|
358
|
+
code: "Backslash",
|
|
359
|
+
key: "\\",
|
|
360
|
+
keyCode: 220,
|
|
361
|
+
location: standardLocation
|
|
362
|
+
},
|
|
363
|
+
"]": {
|
|
364
|
+
charCode: 93,
|
|
365
|
+
code: "BracketRight",
|
|
366
|
+
key: "]",
|
|
367
|
+
keyCode: 221,
|
|
368
|
+
location: standardLocation
|
|
369
|
+
},
|
|
370
|
+
"^": {
|
|
371
|
+
charCode: 94,
|
|
372
|
+
code: "Digit6",
|
|
373
|
+
key: "^",
|
|
374
|
+
keyCode: 54,
|
|
375
|
+
location: standardLocation
|
|
376
|
+
},
|
|
377
|
+
_: {
|
|
378
|
+
charCode: 95,
|
|
379
|
+
code: "Minus",
|
|
380
|
+
key: "_",
|
|
381
|
+
keyCode: 189,
|
|
382
|
+
location: standardLocation
|
|
383
|
+
},
|
|
384
|
+
"`": {
|
|
385
|
+
charCode: 96,
|
|
386
|
+
code: "Backquote",
|
|
387
|
+
key: "`",
|
|
388
|
+
keyCode: 192,
|
|
389
|
+
location: standardLocation
|
|
390
|
+
},
|
|
391
|
+
"{": {
|
|
392
|
+
charCode: 123,
|
|
393
|
+
code: "BracketLeft",
|
|
394
|
+
key: "{",
|
|
395
|
+
keyCode: 219,
|
|
396
|
+
location: standardLocation
|
|
397
|
+
},
|
|
398
|
+
"|": {
|
|
399
|
+
charCode: 124,
|
|
400
|
+
code: "Backslash",
|
|
401
|
+
key: "|",
|
|
402
|
+
keyCode: 220,
|
|
403
|
+
location: standardLocation
|
|
404
|
+
},
|
|
405
|
+
"}": {
|
|
406
|
+
charCode: 125,
|
|
407
|
+
code: "BracketRight",
|
|
408
|
+
key: "}",
|
|
409
|
+
keyCode: 221,
|
|
410
|
+
location: standardLocation
|
|
411
|
+
},
|
|
412
|
+
"~": {
|
|
413
|
+
charCode: 126,
|
|
414
|
+
code: "Backquote",
|
|
415
|
+
key: "~",
|
|
416
|
+
keyCode: 192,
|
|
417
|
+
location: standardLocation
|
|
418
|
+
}
|
|
419
|
+
}));
|
|
420
|
+
function keyIdentity(key) {
|
|
421
|
+
const namedKeyCode = namedKeys.get(key);
|
|
422
|
+
if (namedKeyCode !== void 0) {
|
|
423
|
+
const sidedCode = sidedCodes.get(key);
|
|
424
|
+
return {
|
|
425
|
+
charCode: namedCharCodes.get(key) ?? noCharacter,
|
|
426
|
+
code: sidedCode ?? key,
|
|
427
|
+
key,
|
|
428
|
+
keyCode: namedKeyCode,
|
|
429
|
+
location: sidedCode === void 0 ? standardLocation : leftLocation
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (/^[a-z]$/iu.exec(key)) {
|
|
433
|
+
const upperCaseKey = key.toUpperCase();
|
|
434
|
+
return {
|
|
435
|
+
charCode: key.charCodeAt(0),
|
|
436
|
+
code: `Key${upperCaseKey}`,
|
|
437
|
+
key,
|
|
438
|
+
keyCode: upperCaseKey.charCodeAt(0),
|
|
439
|
+
location: standardLocation
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
if (/^\d$/u.exec(key)) return {
|
|
443
|
+
charCode: key.charCodeAt(0),
|
|
444
|
+
code: `Digit${key}`,
|
|
445
|
+
key,
|
|
446
|
+
keyCode: key.charCodeAt(0),
|
|
447
|
+
location: standardLocation
|
|
448
|
+
};
|
|
449
|
+
const characterKey = characterKeys.get(key);
|
|
450
|
+
if (characterKey !== void 0) return characterKey;
|
|
451
|
+
return {
|
|
452
|
+
charCode: key.length === 1 ? key.charCodeAt(0) : noCharacter,
|
|
453
|
+
code: "",
|
|
454
|
+
key,
|
|
455
|
+
keyCode: 0,
|
|
456
|
+
location: standardLocation
|
|
457
|
+
};
|
|
109
458
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
{
|
|
202
|
-
addedNodes: [textNode],
|
|
203
|
-
attributeName: null,
|
|
204
|
-
attributeNamespace: null,
|
|
205
|
-
nextSibling: brNode,
|
|
206
|
-
oldValue: null,
|
|
207
|
-
previousSibling: null,
|
|
208
|
-
removedNodes: [],
|
|
209
|
-
target: domNode,
|
|
210
|
-
type: "childList"
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
addedNodes: [],
|
|
214
|
-
attributeName: null,
|
|
215
|
-
attributeNamespace: null,
|
|
216
|
-
nextSibling: null,
|
|
217
|
-
oldValue: null,
|
|
218
|
-
previousSibling: textNode,
|
|
219
|
-
removedNodes: [brNode],
|
|
220
|
-
target: domNode,
|
|
221
|
-
type: "childList"
|
|
222
|
-
}
|
|
223
|
-
]);
|
|
224
|
-
} else {
|
|
225
|
-
const target = findLastCharacterDataNode(domNode);
|
|
226
|
-
if (target === null) {
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
const oldValue = target.data;
|
|
230
|
-
const domOffset = this.view.state.selection.from - this.view.posAtDOM(target, 0);
|
|
231
|
-
target.data = target.data.slice(0, domOffset) + character + target.data.slice(domOffset);
|
|
232
|
-
MutationObserverMock.createMutation(this.view.dom, [
|
|
233
|
-
{
|
|
234
|
-
addedNodes: [],
|
|
235
|
-
attributeName: null,
|
|
236
|
-
attributeNamespace: null,
|
|
237
|
-
nextSibling: null,
|
|
238
|
-
oldValue,
|
|
239
|
-
previousSibling: null,
|
|
240
|
-
removedNodes: [],
|
|
241
|
-
target,
|
|
242
|
-
type: "characterData"
|
|
243
|
-
}
|
|
244
|
-
]);
|
|
245
|
-
}
|
|
246
|
-
this.view.dispatchEvent(
|
|
247
|
-
new KeyboardEvent("keyup", {
|
|
248
|
-
bubbles: true,
|
|
249
|
-
charCode: character.charCodeAt(0),
|
|
250
|
-
key,
|
|
251
|
-
keyCode: character.charCodeAt(0),
|
|
252
|
-
...modifiers
|
|
253
|
-
})
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
selectText(selection) {
|
|
258
|
-
this.view.dispatch(
|
|
259
|
-
this.view.state.tr.setSelection(this.getSelection(selection))
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
getSelection(selection) {
|
|
263
|
-
if (selection === "all") {
|
|
264
|
-
return new AllSelection(this.doc);
|
|
265
|
-
}
|
|
266
|
-
if (typeof selection === "object" && "$anchor" in selection && "$head" in selection) {
|
|
267
|
-
return selection;
|
|
268
|
-
}
|
|
269
|
-
if (typeof selection === "object" && "from" in selection && "to" in selection) {
|
|
270
|
-
return TextSelection.between(
|
|
271
|
-
this.doc.resolve(selection.from),
|
|
272
|
-
this.doc.resolve(selection.to)
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
let pos = 0;
|
|
276
|
-
if (selection === "start") {
|
|
277
|
-
pos = 0;
|
|
278
|
-
} else if (selection === "end") {
|
|
279
|
-
pos = this.doc.nodeSize - 2;
|
|
280
|
-
} else {
|
|
281
|
-
pos = selection;
|
|
282
|
-
}
|
|
283
|
-
return TextSelection.near(this.doc.resolve(pos));
|
|
284
|
-
}
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/utils/typing.ts
|
|
461
|
+
var ignoredKeys = /* @__PURE__ */ new Set([
|
|
462
|
+
"Alt",
|
|
463
|
+
"CapsLock",
|
|
464
|
+
"Control",
|
|
465
|
+
"Meta",
|
|
466
|
+
"Shift",
|
|
467
|
+
"Tab"
|
|
468
|
+
]);
|
|
469
|
+
var backward = -1;
|
|
470
|
+
var forward = 1;
|
|
471
|
+
var hasModifiers = (modifiers) => modifiers !== void 0 && Object.values(modifiers).includes(true);
|
|
472
|
+
var suppressesCharacter = (modifiers) => modifiers?.altKey === true || modifiers?.ctrlKey === true || modifiers?.metaKey === true;
|
|
473
|
+
var isLetter = (key) => /^[a-z]$/iu.test(key);
|
|
474
|
+
function insertText(view, text, modifiers) {
|
|
475
|
+
for (const key of tokenizeKeyboardInput(text)) {
|
|
476
|
+
const character = isLetter(key) && modifiers?.shiftKey === true ? key.toUpperCase() : key;
|
|
477
|
+
const shiftKey = isLetter(character) ? character === character.toUpperCase() : modifiers?.shiftKey ?? false;
|
|
478
|
+
const identity = keyIdentity(character);
|
|
479
|
+
const eventInit = {
|
|
480
|
+
bubbles: true,
|
|
481
|
+
cancelable: true,
|
|
482
|
+
charCode: 0,
|
|
483
|
+
code: identity.code,
|
|
484
|
+
composed: true,
|
|
485
|
+
key: identity.key,
|
|
486
|
+
keyCode: identity.keyCode,
|
|
487
|
+
location: identity.location,
|
|
488
|
+
...modifiers,
|
|
489
|
+
shiftKey
|
|
490
|
+
};
|
|
491
|
+
const keydownEvent = new KeyboardEvent("keydown", eventInit);
|
|
492
|
+
view.dispatchEvent(keydownEvent);
|
|
493
|
+
if (!keydownEvent.defaultPrevented) {
|
|
494
|
+
const action = keyAction(character, modifiers);
|
|
495
|
+
if (action.type === "delete") deleteText(view, action.direction);
|
|
496
|
+
else if (action.type === "moveCaret") moveCaret(view, action.direction);
|
|
497
|
+
else if (action.type === "type") {
|
|
498
|
+
view.dispatchEvent(new KeyboardEvent("keypress", {
|
|
499
|
+
...eventInit,
|
|
500
|
+
charCode: identity.charCode,
|
|
501
|
+
keyCode: identity.charCode
|
|
502
|
+
}));
|
|
503
|
+
typeCharacter(view, action.character);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
view.dispatchEvent(new KeyboardEvent("keyup", eventInit));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function deleteText(view, direction) {
|
|
510
|
+
const { selection } = view.state;
|
|
511
|
+
const from = selection.empty && direction === backward ? selection.from - 1 : selection.from;
|
|
512
|
+
const to = selection.empty && direction === forward ? selection.to + 1 : selection.to;
|
|
513
|
+
const fromDOM = view.domAtPos(from, backward);
|
|
514
|
+
const target = characterDataAt(fromDOM.node, fromDOM.offset)?.target;
|
|
515
|
+
if (target === void 0) throw new Error("Cannot simulate deleting a range that is not inside a single text node");
|
|
516
|
+
const nodeStart = view.posAtDOM(target, 0);
|
|
517
|
+
const fromOffset = from - nodeStart;
|
|
518
|
+
const toOffset = to - nodeStart;
|
|
519
|
+
if (fromOffset < 0 || toOffset > target.data.length) throw new Error("Cannot simulate deleting a range that is not inside a single text node");
|
|
520
|
+
const oldValue = target.data;
|
|
521
|
+
target.data = target.data.slice(0, fromOffset) + target.data.slice(toOffset);
|
|
522
|
+
MutationObserverMock.createMutation(view.dom, [{
|
|
523
|
+
oldValue,
|
|
524
|
+
target,
|
|
525
|
+
type: "characterData"
|
|
526
|
+
}]);
|
|
527
|
+
}
|
|
528
|
+
function keyAction(key, modifiers) {
|
|
529
|
+
if (ignoredKeys.has(key)) return { type: "ignore" };
|
|
530
|
+
if (key === "Backspace") return {
|
|
531
|
+
direction: backward,
|
|
532
|
+
type: "delete"
|
|
533
|
+
};
|
|
534
|
+
if (key === "Delete") return {
|
|
535
|
+
direction: forward,
|
|
536
|
+
type: "delete"
|
|
537
|
+
};
|
|
538
|
+
if ((key === "ArrowLeft" || key === "ArrowRight") && !hasModifiers(modifiers)) return {
|
|
539
|
+
direction: key === "ArrowLeft" ? backward : forward,
|
|
540
|
+
type: "moveCaret"
|
|
541
|
+
};
|
|
542
|
+
if (key.length === 1) {
|
|
543
|
+
if (suppressesCharacter(modifiers)) return { type: "ignore" };
|
|
544
|
+
return {
|
|
545
|
+
character: key,
|
|
546
|
+
type: "type"
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
throw new Error(`Cannot simulate the "${key}" key`);
|
|
285
550
|
}
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const textNode = findLastCharacterDataNode(child);
|
|
292
|
-
if (textNode !== null) {
|
|
293
|
-
return textNode;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return null;
|
|
551
|
+
function moveCaret(view, direction) {
|
|
552
|
+
const { selection } = view.state;
|
|
553
|
+
let pos = direction === backward ? selection.from : selection.to;
|
|
554
|
+
if (selection.empty) pos += direction;
|
|
555
|
+
view.dispatch(view.state.tr.setSelection(Selection.near(view.state.doc.resolve(pos), direction)));
|
|
297
556
|
}
|
|
298
|
-
function
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
557
|
+
function typeCharacter(view, character) {
|
|
558
|
+
const { node, offset: domOffset } = view.domAtPos(view.state.selection.from, backward);
|
|
559
|
+
const point = characterDataAt(node, domOffset);
|
|
560
|
+
if (point !== null) {
|
|
561
|
+
const { offset, target } = point;
|
|
562
|
+
const oldValue = target.data;
|
|
563
|
+
target.data = target.data.slice(0, offset) + character + target.data.slice(offset);
|
|
564
|
+
MutationObserverMock.createMutation(view.dom, [{
|
|
565
|
+
oldValue,
|
|
566
|
+
target,
|
|
567
|
+
type: "characterData"
|
|
568
|
+
}]);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const textNode = new Text(character);
|
|
572
|
+
const nextSibling = node.childNodes.item(domOffset);
|
|
573
|
+
node.insertBefore(textNode, nextSibling);
|
|
574
|
+
MutationObserverMock.createMutation(view.dom, [{
|
|
575
|
+
addedNodes: [textNode],
|
|
576
|
+
nextSibling,
|
|
577
|
+
previousSibling: textNode.previousSibling,
|
|
578
|
+
target: node,
|
|
579
|
+
type: "childList"
|
|
580
|
+
}]);
|
|
306
581
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
};
|
|
336
|
-
return {
|
|
337
|
-
message,
|
|
338
|
-
pass
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
export {
|
|
343
|
-
ProseMirrorTester
|
|
582
|
+
//#endregion
|
|
583
|
+
//#region src/ProseMirrorTester.ts
|
|
584
|
+
var ProseMirrorTester = class {
|
|
585
|
+
get doc() {
|
|
586
|
+
return this.view.state.doc;
|
|
587
|
+
}
|
|
588
|
+
get schema() {
|
|
589
|
+
return this.view.state.schema;
|
|
590
|
+
}
|
|
591
|
+
view;
|
|
592
|
+
constructor(documentRoot, options = {}) {
|
|
593
|
+
if (typeof document === "undefined") throw new Error("TODO");
|
|
594
|
+
const element = document.createElement("div");
|
|
595
|
+
document.body.append(element);
|
|
596
|
+
const state = EditorState.create({
|
|
597
|
+
doc: documentRoot,
|
|
598
|
+
plugins: options.plugins ?? []
|
|
599
|
+
});
|
|
600
|
+
global.MutationObserver = MutationObserverMock;
|
|
601
|
+
mockRangeRects();
|
|
602
|
+
this.view = new EditorView(element, { state });
|
|
603
|
+
}
|
|
604
|
+
insertText(text, modifiers) {
|
|
605
|
+
insertText(this.view, text, modifiers);
|
|
606
|
+
}
|
|
607
|
+
selectText(selection) {
|
|
608
|
+
this.view.dispatch(this.view.state.tr.setSelection(resolveSelection(this.doc, selection)));
|
|
609
|
+
}
|
|
344
610
|
};
|
|
345
|
-
//#
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/index.ts
|
|
613
|
+
expect.extend({ toEqualProseMirrorNode(received, expected) {
|
|
614
|
+
const receivedDoc = `\n${stringifyProseMirrorNode(received)}\n`;
|
|
615
|
+
const expectedDoc = `\n${stringifyProseMirrorNode(expected)}\n`;
|
|
616
|
+
const pass = this.equals(receivedDoc, expectedDoc);
|
|
617
|
+
return {
|
|
618
|
+
message: pass ? () => `${this.utils.matcherHint(".not.toEqualProsemirrorNode")}\n\nExpected value of document to not equal:\n ${this.utils.printExpected(expectedDoc)}\nActual:\n ${this.utils.printReceived(receivedDoc)}` : () => {
|
|
619
|
+
const diffString = this.utils.diff(expectedDoc, receivedDoc);
|
|
620
|
+
return `${this.utils.matcherHint(".toEqualProsemirrorNode")}\n\nExpected value of document to equal:\n${this.utils.printExpected(expectedDoc)}\nActual:\n${this.utils.printReceived(receivedDoc)}${diffString === void 0 ? "" : `\n\nDifference:\n\n${diffString}`}`;
|
|
621
|
+
},
|
|
622
|
+
pass
|
|
623
|
+
};
|
|
624
|
+
} });
|
|
625
|
+
//#endregion
|
|
626
|
+
export { ProseMirrorTester };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-prosemirror",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "A plugin for Vitest that enables you to write tests using the ProseMirror editor",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vitest",
|
|
@@ -11,68 +11,89 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/marekdedic/vitest-prosemirror/issues"
|
|
13
13
|
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/marekdedic/vitest-prosemirror.git"
|
|
17
|
+
},
|
|
14
18
|
"license": "MIT",
|
|
15
19
|
"author": "Marek Dědič",
|
|
20
|
+
"sideEffects": true,
|
|
16
21
|
"type": "module",
|
|
17
|
-
"module": "dist/vitest-prosemirror.js",
|
|
18
|
-
"types": "dist/vitest-prosemirror.d.ts",
|
|
19
22
|
"exports": {
|
|
20
23
|
".": {
|
|
24
|
+
"types": "./dist/vitest-prosemirror.d.ts",
|
|
21
25
|
"import": "./dist/vitest-prosemirror.js"
|
|
22
26
|
}
|
|
23
27
|
},
|
|
24
|
-
"
|
|
28
|
+
"module": "dist/vitest-prosemirror.js",
|
|
29
|
+
"types": "dist/vitest-prosemirror.d.ts",
|
|
25
30
|
"files": [
|
|
26
|
-
"dist"
|
|
27
|
-
"LICENSE",
|
|
28
|
-
"README.md"
|
|
31
|
+
"dist"
|
|
29
32
|
],
|
|
30
|
-
"repository": {
|
|
31
|
-
"type": "git",
|
|
32
|
-
"url": "git+https://github.com/marekdedic/vitest-prosemirror.git"
|
|
33
|
-
},
|
|
34
33
|
"scripts": {
|
|
35
|
-
"clean": "rimraf dist/*",
|
|
36
34
|
"prebuild": "npm run clean",
|
|
37
35
|
"build": "vite build",
|
|
36
|
+
"check": "FORCE_COLOR=1 run-p -c --aggregate-output check:*",
|
|
37
|
+
"check:js-compat": "es-check es2023 --module --checkFeatures 'dist/**/*.js'",
|
|
38
|
+
"clean": "rimraf dist/*",
|
|
39
|
+
"lint": "FORCE_COLOR=1 run-p -c --aggregate-output lint:*",
|
|
40
|
+
"lint:eslint": "eslint",
|
|
41
|
+
"lint:ts": "FORCE_COLOR=1 run-p -c --aggregate-output lint:ts:*",
|
|
42
|
+
"lint:ts:attw": "attw --pack",
|
|
43
|
+
"lint:ts:typecheck": "tsc --noEmit",
|
|
44
|
+
"prepack": "npm run build",
|
|
38
45
|
"start": "vite build --watch",
|
|
39
|
-
"
|
|
40
|
-
"test": "vitest"
|
|
41
|
-
"test-coverage": "vitest run --coverage"
|
|
42
|
-
},
|
|
43
|
-
"peerDependencies": {
|
|
44
|
-
"vitest": "^2.1.9 || ^3.0.7"
|
|
45
|
-
},
|
|
46
|
-
"peerDependenciesMeta": {
|
|
47
|
-
"vitest": {
|
|
48
|
-
"optional": true
|
|
49
|
-
}
|
|
46
|
+
"test": "vitest run --coverage",
|
|
47
|
+
"test-watch": "vitest"
|
|
50
48
|
},
|
|
51
49
|
"dependencies": {
|
|
52
50
|
"prosemirror-model": "^1.24.1",
|
|
53
51
|
"prosemirror-state": "^1.4.3",
|
|
54
52
|
"prosemirror-view": "^1.38.0",
|
|
55
|
-
"stringify-object": "^
|
|
53
|
+
"stringify-object": "^7.0.0"
|
|
56
54
|
},
|
|
57
55
|
"devDependencies": {
|
|
56
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
58
57
|
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.0",
|
|
59
|
-
"@eslint/js": "^
|
|
60
|
-
"@
|
|
58
|
+
"@eslint/js": "^10.0.1",
|
|
59
|
+
"@eslint/json": "^2.0.0",
|
|
60
|
+
"@eslint/markdown": "^8.0.0",
|
|
61
|
+
"@microsoft/api-extractor": "^7.58.7",
|
|
62
|
+
"@types/node": "^26.0.0",
|
|
61
63
|
"@types/stringify-object": "^4.0.5",
|
|
62
|
-
"@vitest/coverage-v8": "^
|
|
63
|
-
"@vitest/eslint-plugin": "^1.
|
|
64
|
-
"
|
|
64
|
+
"@vitest/coverage-v8": "^4.0.4",
|
|
65
|
+
"@vitest/eslint-plugin": "^1.3.26",
|
|
66
|
+
"es-check": "^9.6.4",
|
|
67
|
+
"eslint": "^10.0.2",
|
|
65
68
|
"eslint-config-prettier": "^10.0.1",
|
|
66
|
-
"eslint-plugin-
|
|
67
|
-
"eslint-plugin-
|
|
69
|
+
"eslint-plugin-package-json": "^1.0.0",
|
|
70
|
+
"eslint-plugin-perfectionist": "^5.0.0",
|
|
71
|
+
"eslint-plugin-prefer-arrow-functions": "^3.9.1",
|
|
68
72
|
"eslint-plugin-prettier": "^5.1.3",
|
|
69
|
-
"jsdom": "^
|
|
73
|
+
"jsdom": "^30.0.0",
|
|
74
|
+
"npm-run-all2": "^9.0.3",
|
|
70
75
|
"prettier": "^3.3.0",
|
|
76
|
+
"prosemirror-commands": "^1.7.1",
|
|
77
|
+
"prosemirror-inputrules": "^1.5.0",
|
|
78
|
+
"prosemirror-keymap": "^1.2.3",
|
|
71
79
|
"prosemirror-schema-basic": "^1.2.3",
|
|
72
80
|
"rimraf": "^6.0.1",
|
|
73
|
-
"typescript": "^
|
|
74
|
-
"typescript-eslint": "^8.
|
|
75
|
-
"
|
|
76
|
-
"vite
|
|
81
|
+
"typescript": "^6.0.2",
|
|
82
|
+
"typescript-eslint": "^8.58.0",
|
|
83
|
+
"unplugin-dts": "^1.0.1",
|
|
84
|
+
"vite": "^8.0.0",
|
|
85
|
+
"vitest": "^4.0.4",
|
|
86
|
+
"w3c-keyname": "^2.2.8"
|
|
87
|
+
},
|
|
88
|
+
"peerDependencies": {
|
|
89
|
+
"vitest": "^2.1.9 || ^3.0.7 || ^4.0.4"
|
|
90
|
+
},
|
|
91
|
+
"peerDependenciesMeta": {
|
|
92
|
+
"vitest": {
|
|
93
|
+
"optional": true
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"engines": {
|
|
97
|
+
"node": ">=20.19.0"
|
|
77
98
|
}
|
|
78
99
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vitest-prosemirror.js","sources":["../src/stringifyProseMirrorNode.ts","../src/utils/keyboardInput.ts","../src/ProseMirrorTester.ts","../src/index.ts"],"sourcesContent":["import type { Mark, Node } from \"prosemirror-model\";\n\nimport stringifyObject from \"stringify-object\";\n\nfunction getMarks(marks: ReadonlyArray<Mark>, origContent: string): string {\n let content = `'${origContent}'`;\n\n for (const mark of [...marks].reverse()) {\n const hasAttrs = Object.keys(mark.attrs).length > 0;\n const items: Array<string> = [content];\n\n if (hasAttrs) {\n items.unshift(\n stringifyObject(mark.attrs, {\n indent: \" \",\n inlineCharacterLimit: 1000,\n }),\n );\n }\n\n content = `${mark.type.name}(${items.join(\", \")})`;\n }\n\n return content;\n}\n\nconst renamedTypes: Record<string, string> = {\n hardBreak: \"br\",\n heading: \"h\",\n horizontalRule: \"hr\",\n paragraph: \"p\",\n};\n\nexport function stringifyProseMirrorNode(node: Node, indentation = \"\"): string {\n const nextIndentation = `${indentation} `;\n\n if (node.type.name === \"text\") {\n return `${indentation}${getMarks(node.marks, node.text ?? \"\")}`;\n }\n\n const type = renamedTypes[node.type.name] ?? node.type.name;\n const content: Array<string> = [];\n const hasAttrs = Object.keys(node.attrs).length > 0;\n\n if (hasAttrs) {\n content.push(\n stringifyObject(node.attrs, { indent: \" \", inlineCharacterLimit: 1000 }),\n );\n }\n\n node.content.forEach((item) => {\n content.push(stringifyProseMirrorNode(item, nextIndentation));\n });\n\n if (\n !hasAttrs &&\n content.length === 1 &&\n node.content.firstChild?.type.name === \"text\"\n ) {\n return `${indentation}${type}(${stringifyProseMirrorNode(node.content.firstChild, \"\")})`;\n }\n\n const joiner = `,\\n`;\n const prefix = hasAttrs\n ? `\\n${nextIndentation}`\n : content.length > 0\n ? \"\\n\"\n : \"\";\n const postfix = content.length > 0 ? `\\n${indentation}` : \"\";\n\n return `${indentation}${type}(${prefix}${content.join(joiner)},${postfix})`;\n}\n","export function tokenizeKeyboardInput(input: string): Array<string> {\n const output = [];\n\n let currentGroupOpener: \"[\" | \"{\" | null = null;\n let group = \"\";\n for (const char of input) {\n if (currentGroupOpener !== null) {\n if ([\"]\", \"}\"].includes(char)) {\n if (\n group.endsWith(\"\\\\\") &&\n char === matchingBrace(currentGroupOpener)\n ) {\n group = group.slice(0, -2) + char;\n } else if (char === matchingBrace(currentGroupOpener)) {\n if (group.length === 4 && group.startsWith(\"Key\")) {\n output.push(group.slice(3).toLowerCase());\n } else {\n output.push(group);\n }\n currentGroupOpener = null;\n group = \"\";\n } else {\n group += char;\n }\n } else if (group === \"\" && currentGroupOpener === char) {\n output.push(char);\n currentGroupOpener = null;\n group = \"\";\n } else {\n group += char;\n }\n } else if ([\"[\", \"{\"].includes(char)) {\n currentGroupOpener = char as \"[\" | \"{\";\n } else {\n output.push(char);\n }\n }\n\n output.forEach(assertSupported);\n return output;\n}\n\nfunction assertSupported(character: string): never | void {\n if (/^\\/.+/u.exec(character) || /.+>[\\d]*\\/?$/u.exec(character)) {\n throw new Error(\"Unsupported keyboard input\");\n }\n}\n\nfunction matchingBrace(opener: \"[\" | \"{\"): \"]\" | \"}\" {\n return opener === \"{\" ? \"}\" : \"]\";\n}\n","import type { Node as ProseMirrorNode, Schema } from \"prosemirror-model\";\n\nimport {\n AllSelection,\n EditorState,\n type Plugin,\n type Selection,\n TextSelection,\n} from \"prosemirror-state\";\nimport { EditorView } from \"prosemirror-view\";\n\nimport { tokenizeKeyboardInput } from \"./utils/keyboardInput\";\n\nexport interface KeyboardModifiers {\n altKey?: boolean;\n ctrlKey?: boolean;\n metaKey?: boolean;\n shiftKey?: boolean;\n}\n\nexport interface Options {\n plugins: Array<Plugin>;\n}\n\nexport type TesterSelection =\n | \"all\"\n | \"end\"\n | \"start\"\n | { from: number; to: number }\n | Selection\n | number;\n\ntype UsableMutationRecord = Omit<\n MutationRecord,\n \"addedNodes\" | \"removedNodes\"\n> & {\n addedNodes: Array<Node>;\n removedNodes: Array<Node>;\n};\n\nclass KeyboardEventMock extends KeyboardEvent {\n private readonly onPreventDefault: () => void;\n\n public constructor(\n onPreventDefault: () => void,\n type: string,\n eventInitDict?: KeyboardEventInit,\n ) {\n super(type, eventInitDict);\n this.onPreventDefault = onPreventDefault;\n }\n public override preventDefault(): void {\n super.preventDefault();\n this.onPreventDefault();\n }\n}\n\nclass MutationObserverMock {\n private static readonly activeObservers: Map<Node, MutationObserverMock> =\n new Map<Node, MutationObserverMock>();\n\n private readonly callback: MutationCallback;\n private target: Node | undefined;\n\n public constructor(callback: MutationCallback) {\n this.callback = callback;\n this.target = undefined;\n }\n\n public static createMutation(\n target: Node,\n mutationRecords: Array<UsableMutationRecord>,\n ): void {\n const observer = MutationObserverMock.activeObservers.get(target);\n if (observer === undefined) {\n return;\n }\n observer.callback(\n mutationRecords as unknown as Array<MutationRecord>,\n observer,\n );\n }\n\n public disconnect(): void {\n if (this.target !== undefined) {\n MutationObserverMock.activeObservers.delete(this.target);\n }\n this.target = undefined;\n }\n\n public observe(target: Node): void {\n this.target = target;\n MutationObserverMock.activeObservers.set(target, this);\n }\n\n // eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Mocking another method\n public takeRecords(): Array<MutationRecord> {\n return [];\n }\n}\n\nexport class ProseMirrorTester {\n public get doc(): ProseMirrorNode {\n return this.view.state.doc;\n }\n\n public get schema(): Schema {\n return this.view.state.schema;\n }\n\n private readonly view: EditorView;\n\n public constructor(\n documentRoot: ProseMirrorNode,\n options: Partial<Options> = {},\n ) {\n if (typeof document === \"undefined\") {\n throw new Error(\"TODO\");\n }\n\n const element = document.createElement(\"div\");\n document.body.append(element);\n\n const state = EditorState.create({\n doc: documentRoot,\n plugins: options.plugins ?? [],\n });\n\n global.MutationObserver = MutationObserverMock;\n\n this.view = new EditorView(element, {\n state,\n });\n }\n\n public insertText(text: string, modifiers?: KeyboardModifiers): void {\n for (const key of tokenizeKeyboardInput(text)) {\n const character = keyToChar(key);\n\n let keydownPrevented = false;\n this.view.dispatchEvent(\n new KeyboardEventMock(\n () => {\n keydownPrevented = true;\n },\n \"keydown\",\n {\n bubbles: true,\n charCode: character.charCodeAt(0),\n key,\n ...modifiers,\n },\n ),\n );\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- False positive due to the value being set in a callback\n if (keydownPrevented) {\n continue;\n }\n\n this.view.dispatchEvent(\n new KeyboardEvent(\"keypress\", {\n bubbles: true,\n charCode: character.charCodeAt(0),\n key,\n keyCode: character.charCodeAt(0),\n ...modifiers,\n }),\n );\n\n const domNode = this.view.domAtPos(this.view.state.selection.from).node;\n if (\n domNode.childNodes.length === 1 &&\n domNode.firstChild instanceof HTMLBRElement &&\n domNode.firstChild.classList.contains(\"ProseMirror-trailingBreak\")\n ) {\n const brNode = domNode.firstChild;\n const textNode = new Text(character);\n domNode.removeChild(brNode);\n domNode.appendChild(textNode);\n MutationObserverMock.createMutation(this.view.dom, [\n {\n addedNodes: [textNode],\n attributeName: null,\n attributeNamespace: null,\n nextSibling: brNode,\n oldValue: null,\n previousSibling: null,\n removedNodes: [],\n target: domNode,\n type: \"childList\",\n },\n {\n addedNodes: [],\n attributeName: null,\n attributeNamespace: null,\n nextSibling: null,\n oldValue: null,\n previousSibling: textNode,\n removedNodes: [brNode],\n target: domNode,\n type: \"childList\",\n },\n ]);\n } else {\n const target = findLastCharacterDataNode(domNode);\n if (target === null) {\n continue;\n }\n const oldValue = target.data;\n const domOffset =\n this.view.state.selection.from - this.view.posAtDOM(target, 0);\n target.data =\n target.data.slice(0, domOffset) +\n character +\n target.data.slice(domOffset);\n MutationObserverMock.createMutation(this.view.dom, [\n {\n addedNodes: [],\n attributeName: null,\n attributeNamespace: null,\n nextSibling: null,\n oldValue,\n previousSibling: null,\n removedNodes: [],\n target,\n type: \"characterData\",\n },\n ]);\n }\n\n this.view.dispatchEvent(\n new KeyboardEvent(\"keyup\", {\n bubbles: true,\n charCode: character.charCodeAt(0),\n key,\n keyCode: character.charCodeAt(0),\n ...modifiers,\n }),\n );\n }\n }\n\n public selectText(selection: TesterSelection): void {\n this.view.dispatch(\n this.view.state.tr.setSelection(this.getSelection(selection)),\n );\n }\n\n private getSelection(selection: TesterSelection): Selection {\n if (selection === \"all\") {\n return new AllSelection(this.doc);\n }\n\n if (\n typeof selection === \"object\" &&\n \"$anchor\" in selection &&\n \"$head\" in selection\n ) {\n return selection;\n }\n\n if (\n typeof selection === \"object\" &&\n \"from\" in selection &&\n \"to\" in selection\n ) {\n return TextSelection.between(\n this.doc.resolve(selection.from),\n this.doc.resolve(selection.to),\n );\n }\n\n let pos = 0;\n if (selection === \"start\") {\n pos = 0;\n } else if (selection === \"end\") {\n pos = this.doc.nodeSize - 2;\n } else {\n pos = selection;\n }\n\n return TextSelection.near(this.doc.resolve(pos));\n }\n}\n\nfunction findLastCharacterDataNode(node: Node): CharacterData | null {\n if (node instanceof CharacterData) {\n return node;\n }\n for (const child of Array.from(node.childNodes).reverse()) {\n const textNode = findLastCharacterDataNode(child);\n if (textNode !== null) {\n return textNode;\n }\n }\n return null;\n}\n\nfunction keyToChar(key: string): string {\n if (key === \"Enter\") {\n return \"\\n\";\n }\n if (key === \"Tab\") {\n return \"\\t\";\n }\n return key;\n}\n","import type { Node } from \"prosemirror-model\";\n\nimport { expect } from \"vitest\";\n\nimport { stringifyProseMirrorNode } from \"./stringifyProseMirrorNode\";\n\nexport {\n type KeyboardModifiers,\n type Options,\n ProseMirrorTester,\n type TesterSelection,\n} from \"./ProseMirrorTester\";\n\nexport interface CustomMatchers<R = unknown> {\n toEqualProseMirrorNode(expected: Node): R;\n}\n\n/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-explicit-any -- These are overridest for vitest matchers */\n\ndeclare module \"vitest\" {\n interface Assertion<T = any> extends CustomMatchers<T> {}\n interface AsymmetricMatchersContaining extends CustomMatchers {}\n}\n\n/* eslint-enable */\n\nexpect.extend({\n toEqualProseMirrorNode(received: Node, expected: Node) {\n const receivedDoc = `\\n${stringifyProseMirrorNode(received)}\\n`;\n const expectedDoc = `\\n${stringifyProseMirrorNode(expected)}\\n`;\n const pass = this.equals(receivedDoc, expectedDoc);\n const message = pass\n ? (): string =>\n `${this.utils.matcherHint(\".not.toEqualProsemirrorNode\")}\\n\\n` +\n `Expected value of document to not equal:\\n ${this.utils.printExpected(expectedDoc)}\\n` +\n `Actual:\\n ${this.utils.printReceived(receivedDoc)}`\n : (): string => {\n const diffString = this.utils.diff(expectedDoc, receivedDoc, {\n expand: this.expand ?? false,\n });\n return `${this.utils.matcherHint(\".toEqualProsemirrorNode\")}\\n\\nExpected value of document to equal:\\n${this.utils.printExpected(expectedDoc)}\\nActual:\\n${this.utils.printReceived(receivedDoc)}${diffString !== undefined ? `\\n\\nDifference:\\n\\n${diffString}` : \"\"}`;\n };\n return {\n message,\n pass,\n };\n },\n});\n"],"names":[],"mappings":";;;;AAIA,SAAS,SAAS,OAA4B,aAA6B;AACrE,MAAA,UAAU,IAAI,WAAW;AAE7B,aAAW,QAAQ,CAAC,GAAG,KAAK,EAAE,WAAW;AACvC,UAAM,WAAW,OAAO,KAAK,KAAK,KAAK,EAAE,SAAS;AAC5C,UAAA,QAAuB,CAAC,OAAO;AAErC,QAAI,UAAU;AACN,YAAA;AAAA,QACJ,gBAAgB,KAAK,OAAO;AAAA,UAC1B,QAAQ;AAAA,UACR,sBAAsB;AAAA,QACvB,CAAA;AAAA,MACH;AAAA,IAAA;AAGQ,cAAA,GAAG,KAAK,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,CAAC;AAAA,EAAA;AAG1C,SAAA;AACT;AAEA,MAAM,eAAuC;AAAA,EAC3C,WAAW;AAAA,EACX,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,WAAW;AACb;AAEgB,SAAA,yBAAyB,MAAY,cAAc,IAAY;;AACvE,QAAA,kBAAkB,GAAG,WAAW;AAElC,MAAA,KAAK,KAAK,SAAS,QAAQ;AACtB,WAAA,GAAG,WAAW,GAAG,SAAS,KAAK,OAAO,KAAK,QAAQ,EAAE,CAAC;AAAA,EAAA;AAG/D,QAAM,OAAO,aAAa,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK;AACvD,QAAM,UAAyB,CAAC;AAChC,QAAM,WAAW,OAAO,KAAK,KAAK,KAAK,EAAE,SAAS;AAElD,MAAI,UAAU;AACJ,YAAA;AAAA,MACN,gBAAgB,KAAK,OAAO,EAAE,QAAQ,MAAM,sBAAsB,IAAM,CAAA;AAAA,IAC1E;AAAA,EAAA;AAGG,OAAA,QAAQ,QAAQ,CAAC,SAAS;AAC7B,YAAQ,KAAK,yBAAyB,MAAM,eAAe,CAAC;AAAA,EAAA,CAC7D;AAGC,MAAA,CAAC,YACD,QAAQ,WAAW,OACnB,UAAK,QAAQ,eAAb,mBAAyB,KAAK,UAAS,QACvC;AACO,WAAA,GAAG,WAAW,GAAG,IAAI,IAAI,yBAAyB,KAAK,QAAQ,YAAY,EAAE,CAAC;AAAA,EAAA;AAGvF,QAAM,SAAS;AAAA;AACf,QAAM,SAAS,WACX;AAAA,EAAK,eAAe,KACpB,QAAQ,SAAS,IACf,OACA;AACA,QAAA,UAAU,QAAQ,SAAS,IAAI;AAAA,EAAK,WAAW,KAAK;AAE1D,SAAO,GAAG,WAAW,GAAG,IAAI,IAAI,MAAM,GAAG,QAAQ,KAAK,MAAM,CAAC,IAAI,OAAO;AAC1E;ACvEO,SAAS,sBAAsB,OAA8B;AAClE,QAAM,SAAS,CAAC;AAEhB,MAAI,qBAAuC;AAC3C,MAAI,QAAQ;AACZ,aAAW,QAAQ,OAAO;AACxB,QAAI,uBAAuB,MAAM;AAC/B,UAAI,CAAC,KAAK,GAAG,EAAE,SAAS,IAAI,GAAG;AAC7B,YACE,MAAM,SAAS,IAAI,KACnB,SAAS,cAAc,kBAAkB,GACzC;AACA,kBAAQ,MAAM,MAAM,GAAG,EAAE,IAAI;AAAA,QACpB,WAAA,SAAS,cAAc,kBAAkB,GAAG;AACrD,cAAI,MAAM,WAAW,KAAK,MAAM,WAAW,KAAK,GAAG;AACjD,mBAAO,KAAK,MAAM,MAAM,CAAC,EAAE,aAAa;AAAA,UAAA,OACnC;AACL,mBAAO,KAAK,KAAK;AAAA,UAAA;AAEE,+BAAA;AACb,kBAAA;AAAA,QAAA,OACH;AACI,mBAAA;AAAA,QAAA;AAAA,MAEF,WAAA,UAAU,MAAM,uBAAuB,MAAM;AACtD,eAAO,KAAK,IAAI;AACK,6BAAA;AACb,gBAAA;AAAA,MAAA,OACH;AACI,iBAAA;AAAA,MAAA;AAAA,IACX,WACS,CAAC,KAAK,GAAG,EAAE,SAAS,IAAI,GAAG;AACf,2BAAA;AAAA,IAAA,OAChB;AACL,aAAO,KAAK,IAAI;AAAA,IAAA;AAAA,EAClB;AAGF,SAAO,QAAQ,eAAe;AACvB,SAAA;AACT;AAEA,SAAS,gBAAgB,WAAiC;AACxD,MAAI,SAAS,KAAK,SAAS,KAAK,gBAAgB,KAAK,SAAS,GAAG;AACzD,UAAA,IAAI,MAAM,4BAA4B;AAAA,EAAA;AAEhD;AAEA,SAAS,cAAc,QAA8B;AAC5C,SAAA,WAAW,MAAM,MAAM;AAChC;ACVA,MAAM,0BAA0B,cAAc;AAAA,EAGrC,YACL,kBACA,MACA,eACA;AACA,UAAM,MAAM,aAAa;AACzB,SAAK,mBAAmB;AAAA,EAAA;AAAA,EAEV,iBAAuB;AACrC,UAAM,eAAe;AACrB,SAAK,iBAAiB;AAAA,EAAA;AAE1B;AAEA,MAAM,wBAAN,MAAM,sBAAqB;AAAA,EAOlB,YAAY,UAA4B;AAC7C,SAAK,WAAW;AAChB,SAAK,SAAS;AAAA,EAAA;AAAA,EAGhB,OAAc,eACZ,QACA,iBACM;AACN,UAAM,WAAW,sBAAqB,gBAAgB,IAAI,MAAM;AAChE,QAAI,aAAa,QAAW;AAC1B;AAAA,IAAA;AAEO,aAAA;AAAA,MACP;AAAA,MACA;AAAA,IACF;AAAA,EAAA;AAAA,EAGK,aAAmB;AACpB,QAAA,KAAK,WAAW,QAAW;AACR,4BAAA,gBAAgB,OAAO,KAAK,MAAM;AAAA,IAAA;AAEzD,SAAK,SAAS;AAAA,EAAA;AAAA,EAGT,QAAQ,QAAoB;AACjC,SAAK,SAAS;AACO,0BAAA,gBAAgB,IAAI,QAAQ,IAAI;AAAA,EAAA;AAAA;AAAA,EAIhD,cAAqC;AAC1C,WAAO,CAAC;AAAA,EAAA;AAEZ;AAzC0B,sBAAA,sCAClB,IAAgC;AAFxC,IAAM,uBAAN;AA4CO,MAAM,kBAAkB;AAAA,EAC7B,IAAW,MAAuB;AACzB,WAAA,KAAK,KAAK,MAAM;AAAA,EAAA;AAAA,EAGzB,IAAW,SAAiB;AACnB,WAAA,KAAK,KAAK,MAAM;AAAA,EAAA;AAAA,EAKlB,YACL,cACA,UAA4B,IAC5B;AACI,QAAA,OAAO,aAAa,aAAa;AAC7B,YAAA,IAAI,MAAM,MAAM;AAAA,IAAA;AAGlB,UAAA,UAAU,SAAS,cAAc,KAAK;AACnC,aAAA,KAAK,OAAO,OAAO;AAEtB,UAAA,QAAQ,YAAY,OAAO;AAAA,MAC/B,KAAK;AAAA,MACL,SAAS,QAAQ,WAAW,CAAA;AAAA,IAAC,CAC9B;AAED,WAAO,mBAAmB;AAErB,SAAA,OAAO,IAAI,WAAW,SAAS;AAAA,MAClC;AAAA,IAAA,CACD;AAAA,EAAA;AAAA,EAGI,WAAW,MAAc,WAAqC;AACxD,eAAA,OAAO,sBAAsB,IAAI,GAAG;AACvC,YAAA,YAAY,UAAU,GAAG;AAE/B,UAAI,mBAAmB;AACvB,WAAK,KAAK;AAAA,QACR,IAAI;AAAA,UACF,MAAM;AACe,+BAAA;AAAA,UACrB;AAAA,UACA;AAAA,UACA;AAAA,YACE,SAAS;AAAA,YACT,UAAU,UAAU,WAAW,CAAC;AAAA,YAChC;AAAA,YACA,GAAG;AAAA,UAAA;AAAA,QACL;AAAA,MAEJ;AAGA,UAAI,kBAAkB;AACpB;AAAA,MAAA;AAGF,WAAK,KAAK;AAAA,QACR,IAAI,cAAc,YAAY;AAAA,UAC5B,SAAS;AAAA,UACT,UAAU,UAAU,WAAW,CAAC;AAAA,UAChC;AAAA,UACA,SAAS,UAAU,WAAW,CAAC;AAAA,UAC/B,GAAG;AAAA,QACJ,CAAA;AAAA,MACH;AAEM,YAAA,UAAU,KAAK,KAAK,SAAS,KAAK,KAAK,MAAM,UAAU,IAAI,EAAE;AACnE,UACE,QAAQ,WAAW,WAAW,KAC9B,QAAQ,sBAAsB,iBAC9B,QAAQ,WAAW,UAAU,SAAS,2BAA2B,GACjE;AACA,cAAM,SAAS,QAAQ;AACjB,cAAA,WAAW,IAAI,KAAK,SAAS;AACnC,gBAAQ,YAAY,MAAM;AAC1B,gBAAQ,YAAY,QAAQ;AACP,6BAAA,eAAe,KAAK,KAAK,KAAK;AAAA,UACjD;AAAA,YACE,YAAY,CAAC,QAAQ;AAAA,YACrB,eAAe;AAAA,YACf,oBAAoB;AAAA,YACpB,aAAa;AAAA,YACb,UAAU;AAAA,YACV,iBAAiB;AAAA,YACjB,cAAc,CAAC;AAAA,YACf,QAAQ;AAAA,YACR,MAAM;AAAA,UACR;AAAA,UACA;AAAA,YACE,YAAY,CAAC;AAAA,YACb,eAAe;AAAA,YACf,oBAAoB;AAAA,YACpB,aAAa;AAAA,YACb,UAAU;AAAA,YACV,iBAAiB;AAAA,YACjB,cAAc,CAAC,MAAM;AAAA,YACrB,QAAQ;AAAA,YACR,MAAM;AAAA,UAAA;AAAA,QACR,CACD;AAAA,MAAA,OACI;AACC,cAAA,SAAS,0BAA0B,OAAO;AAChD,YAAI,WAAW,MAAM;AACnB;AAAA,QAAA;AAEF,cAAM,WAAW,OAAO;AAClB,cAAA,YACJ,KAAK,KAAK,MAAM,UAAU,OAAO,KAAK,KAAK,SAAS,QAAQ,CAAC;AACxD,eAAA,OACL,OAAO,KAAK,MAAM,GAAG,SAAS,IAC9B,YACA,OAAO,KAAK,MAAM,SAAS;AACR,6BAAA,eAAe,KAAK,KAAK,KAAK;AAAA,UACjD;AAAA,YACE,YAAY,CAAC;AAAA,YACb,eAAe;AAAA,YACf,oBAAoB;AAAA,YACpB,aAAa;AAAA,YACb;AAAA,YACA,iBAAiB;AAAA,YACjB,cAAc,CAAC;AAAA,YACf;AAAA,YACA,MAAM;AAAA,UAAA;AAAA,QACR,CACD;AAAA,MAAA;AAGH,WAAK,KAAK;AAAA,QACR,IAAI,cAAc,SAAS;AAAA,UACzB,SAAS;AAAA,UACT,UAAU,UAAU,WAAW,CAAC;AAAA,UAChC;AAAA,UACA,SAAS,UAAU,WAAW,CAAC;AAAA,UAC/B,GAAG;AAAA,QACJ,CAAA;AAAA,MACH;AAAA,IAAA;AAAA,EACF;AAAA,EAGK,WAAW,WAAkC;AAClD,SAAK,KAAK;AAAA,MACR,KAAK,KAAK,MAAM,GAAG,aAAa,KAAK,aAAa,SAAS,CAAC;AAAA,IAC9D;AAAA,EAAA;AAAA,EAGM,aAAa,WAAuC;AAC1D,QAAI,cAAc,OAAO;AAChB,aAAA,IAAI,aAAa,KAAK,GAAG;AAAA,IAAA;AAGlC,QACE,OAAO,cAAc,YACrB,aAAa,aACb,WAAW,WACX;AACO,aAAA;AAAA,IAAA;AAGT,QACE,OAAO,cAAc,YACrB,UAAU,aACV,QAAQ,WACR;AACA,aAAO,cAAc;AAAA,QACnB,KAAK,IAAI,QAAQ,UAAU,IAAI;AAAA,QAC/B,KAAK,IAAI,QAAQ,UAAU,EAAE;AAAA,MAC/B;AAAA,IAAA;AAGF,QAAI,MAAM;AACV,QAAI,cAAc,SAAS;AACnB,YAAA;AAAA,IAAA,WACG,cAAc,OAAO;AACxB,YAAA,KAAK,IAAI,WAAW;AAAA,IAAA,OACrB;AACC,YAAA;AAAA,IAAA;AAGR,WAAO,cAAc,KAAK,KAAK,IAAI,QAAQ,GAAG,CAAC;AAAA,EAAA;AAEnD;AAEA,SAAS,0BAA0B,MAAkC;AACnE,MAAI,gBAAgB,eAAe;AAC1B,WAAA;AAAA,EAAA;AAET,aAAW,SAAS,MAAM,KAAK,KAAK,UAAU,EAAE,WAAW;AACnD,UAAA,WAAW,0BAA0B,KAAK;AAChD,QAAI,aAAa,MAAM;AACd,aAAA;AAAA,IAAA;AAAA,EACT;AAEK,SAAA;AACT;AAEA,SAAS,UAAU,KAAqB;AACtC,MAAI,QAAQ,SAAS;AACZ,WAAA;AAAA,EAAA;AAET,MAAI,QAAQ,OAAO;AACV,WAAA;AAAA,EAAA;AAEF,SAAA;AACT;ACzRA,OAAO,OAAO;AAAA,EACZ,uBAAuB,UAAgB,UAAgB;AACrD,UAAM,cAAc;AAAA,EAAK,yBAAyB,QAAQ,CAAC;AAAA;AAC3D,UAAM,cAAc;AAAA,EAAK,yBAAyB,QAAQ,CAAC;AAAA;AAC3D,UAAM,OAAO,KAAK,OAAO,aAAa,WAAW;AAC3C,UAAA,UAAU,OACZ,MACE,GAAG,KAAK,MAAM,YAAY,6BAA6B,CAAC;AAAA;AAAA;AAAA,IACT,KAAK,MAAM,cAAc,WAAW,CAAC;AAAA;AAAA,IACtE,KAAK,MAAM,cAAc,WAAW,CAAC,KACrD,MAAc;AACZ,YAAM,aAAa,KAAK,MAAM,KAAK,aAAa,aAAa;AAAA,QAC3D,QAAQ,KAAK,UAAU;AAAA,MAAA,CACxB;AACD,aAAO,GAAG,KAAK,MAAM,YAAY,yBAAyB,CAAC;AAAA;AAAA;AAAA,EAA6C,KAAK,MAAM,cAAc,WAAW,CAAC;AAAA;AAAA,EAAc,KAAK,MAAM,cAAc,WAAW,CAAC,GAAG,eAAe,SAAY;AAAA;AAAA;AAAA;AAAA,EAAsB,UAAU,KAAK,EAAE;AAAA,IACvQ;AACG,WAAA;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EAAA;AAEJ,CAAC;"}
|