vitest-prosemirror 0.3.1 → 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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/vitest-prosemirror?logo=npm)](https://www.npmjs.com/package/vitest-prosemirror)
4
4
  [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/marekdedic/vitest-prosemirror/CI.yml?branch=master&logo=github)](https://github.com/marekdedic/vitest-prosemirror/actions)
5
- [![Coveralls](https://img.shields.io/coverallsCoverage/github/marekdedic/vitest-prosemirror?branch=master&logo=coveralls)](https://coveralls.io/github/marekdedic/vitest-prosemirror)
5
+ [![Codecov (with branch)](https://img.shields.io/codecov/c/github/marekdedic/vitest-prosemirror/master?logo=codecov)](https://app.codecov.io/gh/marekdedic/vitest-prosemirror)
6
6
  [![NPM Downloads](https://img.shields.io/npm/dm/vitest-prosemirror?logo=npm)](https://www.npmjs.com/package/vitest-prosemirror)
7
7
  [![NPM License](https://img.shields.io/npm/l/vitest-prosemirror)](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,342 +1,626 @@
1
1
  import { expect } from "vitest";
2
2
  import stringifyObject from "stringify-object";
3
- import { EditorState, AllSelection, TextSelection } from "prosemirror-state";
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
- let content = `'${origContent}'`;
7
- for (const mark of [...marks].reverse()) {
8
- const hasAttrs = Object.keys(mark.attrs).length > 0;
9
- const items = [content];
10
- if (hasAttrs) {
11
- items.unshift(
12
- stringifyObject(mark.attrs, {
13
- indent: " ",
14
- inlineCharacterLimit: 1e3
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
- const renamedTypes = {
23
- hardBreak: "br",
24
- heading: "h",
25
- horizontalRule: "hr",
26
- paragraph: "p"
19
+ var renamedTypes = {
20
+ hardBreak: "br",
21
+ heading: "h",
22
+ horizontalRule: "hr",
23
+ paragraph: "p"
27
24
  };
28
25
  function stringifyProseMirrorNode(node, indentation = "") {
29
- if (node.type.name === "text") {
30
- return `${indentation}${getMarks(node.marks, node.text ?? "")}`;
31
- }
32
- const type = renamedTypes[node.type.name] ?? node.type.name;
33
- const content = [];
34
- const nextIndentation = `${indentation} `;
35
- if (Object.keys(node.attrs).length > 0) {
36
- content.push(
37
- `${nextIndentation}${stringifyObject(node.attrs, { indent: " ", inlineCharacterLimit: 1e3 })},`
38
- );
39
- }
40
- node.content.forEach((item) => {
41
- content.push(`${stringifyProseMirrorNode(item, nextIndentation)},`);
42
- });
43
- if (content.length === 0) {
44
- return `${indentation}${type}()`;
45
- }
46
- if (content.length === 1 && node.content.firstChild?.type.name === "text") {
47
- return `${indentation}${type}(${stringifyProseMirrorNode(node.content.firstChild, "")})`;
48
- }
49
- return `${indentation}${type}(
50
- ${content.join("\n")}
51
- ${indentation})`;
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})`;
52
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
53
113
  function tokenizeKeyboardInput(input) {
54
- const output = [];
55
- let currentGroupOpener = null;
56
- let group = "";
57
- for (const char of input) {
58
- if (currentGroupOpener !== null) {
59
- if (["]", "}"].includes(char)) {
60
- if (group.endsWith("\\") && char === matchingBrace(currentGroupOpener)) {
61
- group = group.slice(0, -2) + char;
62
- } else if (char === matchingBrace(currentGroupOpener)) {
63
- if (group.length === 4 && group.startsWith("Key")) {
64
- output.push(group.slice(3).toLowerCase());
65
- } else {
66
- output.push(group);
67
- }
68
- currentGroupOpener = null;
69
- group = "";
70
- } else {
71
- group += char;
72
- }
73
- } else if (group === "" && currentGroupOpener === char) {
74
- output.push(char);
75
- currentGroupOpener = null;
76
- group = "";
77
- } else {
78
- group += char;
79
- }
80
- } else if (["[", "{"].includes(char)) {
81
- currentGroupOpener = char;
82
- } else {
83
- output.push(char);
84
- }
85
- }
86
- output.forEach(assertSupported);
87
- 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;
88
135
  }
89
136
  function assertSupported(character) {
90
- if (/^\/.+/u.exec(character) || /.+>[\d]*\/?$/u.exec(character)) {
91
- throw new Error("Unsupported keyboard input");
92
- }
137
+ if (/^\/.+/u.exec(character) || /.+>[\d]*\/?$/u.exec(character)) throw new Error("Unsupported keyboard input");
93
138
  }
94
139
  function matchingBrace(opener) {
95
- return opener === "{" ? "}" : "]";
140
+ return opener === "{" ? "}" : "]";
96
141
  }
97
- class KeyboardEventMock extends KeyboardEvent {
98
- constructor(onPreventDefault, type, eventInitDict) {
99
- super(type, eventInitDict);
100
- this.onPreventDefault = onPreventDefault;
101
- }
102
- preventDefault() {
103
- super.preventDefault();
104
- this.onPreventDefault();
105
- }
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
+ };
106
458
  }
107
- const _MutationObserverMock = class _MutationObserverMock {
108
- constructor(callback) {
109
- this.callback = callback;
110
- this.target = void 0;
111
- }
112
- static createMutation(target, mutationRecords) {
113
- const observer = _MutationObserverMock.activeObservers.get(target);
114
- if (observer === void 0) {
115
- return;
116
- }
117
- observer.callback(
118
- mutationRecords,
119
- observer
120
- );
121
- }
122
- disconnect() {
123
- if (this.target !== void 0) {
124
- _MutationObserverMock.activeObservers.delete(this.target);
125
- }
126
- this.target = void 0;
127
- }
128
- observe(target) {
129
- this.target = target;
130
- _MutationObserverMock.activeObservers.set(target, this);
131
- }
132
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Mocking another method
133
- takeRecords() {
134
- return [];
135
- }
136
- };
137
- _MutationObserverMock.activeObservers = /* @__PURE__ */ new Map();
138
- let MutationObserverMock = _MutationObserverMock;
139
- class ProseMirrorTester {
140
- get doc() {
141
- return this.view.state.doc;
142
- }
143
- get schema() {
144
- return this.view.state.schema;
145
- }
146
- constructor(documentRoot, options = {}) {
147
- if (typeof document === "undefined") {
148
- throw new Error("TODO");
149
- }
150
- const element = document.createElement("div");
151
- document.body.append(element);
152
- const state = EditorState.create({
153
- doc: documentRoot,
154
- plugins: options.plugins ?? []
155
- });
156
- global.MutationObserver = MutationObserverMock;
157
- this.view = new EditorView(element, {
158
- state
159
- });
160
- }
161
- insertText(text, modifiers) {
162
- for (const key of tokenizeKeyboardInput(text)) {
163
- const character = keyToChar(key);
164
- let keydownPrevented = false;
165
- this.view.dispatchEvent(
166
- new KeyboardEventMock(
167
- () => {
168
- keydownPrevented = true;
169
- },
170
- "keydown",
171
- {
172
- bubbles: true,
173
- charCode: character.charCodeAt(0),
174
- key,
175
- ...modifiers
176
- }
177
- )
178
- );
179
- if (keydownPrevented) {
180
- continue;
181
- }
182
- this.view.dispatchEvent(
183
- new KeyboardEvent("keypress", {
184
- bubbles: true,
185
- charCode: character.charCodeAt(0),
186
- key,
187
- keyCode: character.charCodeAt(0),
188
- ...modifiers
189
- })
190
- );
191
- const domNode = this.view.domAtPos(this.view.state.selection.from).node;
192
- if (domNode.childNodes.length === 1 && domNode.firstChild instanceof HTMLBRElement && domNode.firstChild.classList.contains("ProseMirror-trailingBreak")) {
193
- const brNode = domNode.firstChild;
194
- const textNode = new Text(character);
195
- domNode.removeChild(brNode);
196
- domNode.appendChild(textNode);
197
- MutationObserverMock.createMutation(this.view.dom, [
198
- {
199
- addedNodes: [textNode],
200
- attributeName: null,
201
- attributeNamespace: null,
202
- nextSibling: brNode,
203
- oldValue: null,
204
- previousSibling: null,
205
- removedNodes: [],
206
- target: domNode,
207
- type: "childList"
208
- },
209
- {
210
- addedNodes: [],
211
- attributeName: null,
212
- attributeNamespace: null,
213
- nextSibling: null,
214
- oldValue: null,
215
- previousSibling: textNode,
216
- removedNodes: [brNode],
217
- target: domNode,
218
- type: "childList"
219
- }
220
- ]);
221
- } else {
222
- const target = findLastCharacterDataNode(domNode);
223
- if (target === null) {
224
- continue;
225
- }
226
- const oldValue = target.data;
227
- const domOffset = this.view.state.selection.from - this.view.posAtDOM(target, 0);
228
- target.data = target.data.slice(0, domOffset) + character + target.data.slice(domOffset);
229
- MutationObserverMock.createMutation(this.view.dom, [
230
- {
231
- addedNodes: [],
232
- attributeName: null,
233
- attributeNamespace: null,
234
- nextSibling: null,
235
- oldValue,
236
- previousSibling: null,
237
- removedNodes: [],
238
- target,
239
- type: "characterData"
240
- }
241
- ]);
242
- }
243
- this.view.dispatchEvent(
244
- new KeyboardEvent("keyup", {
245
- bubbles: true,
246
- charCode: character.charCodeAt(0),
247
- key,
248
- keyCode: character.charCodeAt(0),
249
- ...modifiers
250
- })
251
- );
252
- }
253
- }
254
- selectText(selection) {
255
- this.view.dispatch(
256
- this.view.state.tr.setSelection(this.getSelection(selection))
257
- );
258
- }
259
- getSelection(selection) {
260
- if (selection === "all") {
261
- return new AllSelection(this.doc);
262
- }
263
- if (typeof selection === "object" && "$anchor" in selection && "$head" in selection) {
264
- return selection;
265
- }
266
- if (typeof selection === "object" && "from" in selection && "to" in selection) {
267
- return TextSelection.between(
268
- this.doc.resolve(selection.from),
269
- this.doc.resolve(selection.to)
270
- );
271
- }
272
- let pos = 0;
273
- if (selection === "start") {
274
- pos = 0;
275
- } else if (selection === "end") {
276
- pos = this.doc.nodeSize - 2;
277
- } else {
278
- pos = selection;
279
- }
280
- return TextSelection.near(this.doc.resolve(pos));
281
- }
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`);
282
550
  }
283
- function findLastCharacterDataNode(node) {
284
- if (node instanceof CharacterData) {
285
- return node;
286
- }
287
- for (const child of Array.from(node.childNodes).reverse()) {
288
- const textNode = findLastCharacterDataNode(child);
289
- if (textNode !== null) {
290
- return textNode;
291
- }
292
- }
293
- 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)));
294
556
  }
295
- function keyToChar(key) {
296
- if (key === "Enter") {
297
- return "\n";
298
- }
299
- if (key === "Tab") {
300
- return " ";
301
- }
302
- return key;
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
+ }]);
303
581
  }
304
- expect.extend({
305
- toEqualProseMirrorNode(received, expected) {
306
- const receivedDoc = `
307
- ${stringifyProseMirrorNode(received)}
308
- `;
309
- const expectedDoc = `
310
- ${stringifyProseMirrorNode(expected)}
311
- `;
312
- const pass = this.equals(receivedDoc, expectedDoc);
313
- const message = pass ? () => `${this.utils.matcherHint(".not.toEqualProsemirrorNode")}
314
-
315
- Expected value of document to not equal:
316
- ${this.utils.printExpected(expectedDoc)}
317
- Actual:
318
- ${this.utils.printReceived(receivedDoc)}` : () => {
319
- const diffString = this.utils.diff(expectedDoc, receivedDoc, {
320
- expand: this.expand ?? false
321
- });
322
- return `${this.utils.matcherHint(".toEqualProsemirrorNode")}
323
-
324
- Expected value of document to equal:
325
- ${this.utils.printExpected(expectedDoc)}
326
- Actual:
327
- ${this.utils.printReceived(receivedDoc)}${diffString !== void 0 ? `
328
-
329
- Difference:
330
-
331
- ${diffString}` : ""}`;
332
- };
333
- return {
334
- message,
335
- pass
336
- };
337
- }
338
- });
339
- export {
340
- 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
+ }
341
610
  };
342
- //# sourceMappingURL=vitest-prosemirror.js.map
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.1",
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",
@@ -21,6 +21,7 @@
21
21
  "type": "module",
22
22
  "exports": {
23
23
  ".": {
24
+ "types": "./dist/vitest-prosemirror.d.ts",
24
25
  "import": "./dist/vitest-prosemirror.js"
25
26
  }
26
27
  },
@@ -32,44 +33,57 @@
32
33
  "scripts": {
33
34
  "prebuild": "npm run clean",
34
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'",
35
38
  "clean": "rimraf dist/*",
36
- "lint": "eslint",
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",
37
45
  "start": "vite build --watch",
38
- "test": "vitest",
39
- "test-coverage": "vitest run --coverage"
46
+ "test": "vitest run --coverage",
47
+ "test-watch": "vitest"
40
48
  },
41
49
  "dependencies": {
42
50
  "prosemirror-model": "^1.24.1",
43
51
  "prosemirror-state": "^1.4.3",
44
52
  "prosemirror-view": "^1.38.0",
45
- "stringify-object": "^6.0.0"
53
+ "stringify-object": "^7.0.0"
46
54
  },
47
55
  "devDependencies": {
56
+ "@arethetypeswrong/cli": "^0.18.2",
48
57
  "@eslint-community/eslint-plugin-eslint-comments": "^4.4.0",
49
- "@eslint/js": "^9.9.1",
50
- "@eslint/json": "^0.13.0",
51
- "@eslint/markdown": "^7.0.0",
52
- "@types/node": "^24.0.2",
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",
53
63
  "@types/stringify-object": "^4.0.5",
54
64
  "@vitest/coverage-v8": "^4.0.4",
55
65
  "@vitest/eslint-plugin": "^1.3.26",
56
- "eslint": "^9.9.0",
66
+ "es-check": "^9.6.4",
67
+ "eslint": "^10.0.2",
57
68
  "eslint-config-prettier": "^10.0.1",
58
- "eslint-plugin-package-json": "^0.59.0",
59
- "eslint-plugin-perfectionist": "^4.1.2",
69
+ "eslint-plugin-package-json": "^1.0.0",
70
+ "eslint-plugin-perfectionist": "^5.0.0",
60
71
  "eslint-plugin-prefer-arrow-functions": "^3.9.1",
61
72
  "eslint-plugin-prettier": "^5.1.3",
62
- "jsdom": "^27.0.0",
73
+ "jsdom": "^30.0.0",
74
+ "npm-run-all2": "^9.0.3",
63
75
  "prettier": "^3.3.0",
64
76
  "prosemirror-commands": "^1.7.1",
65
77
  "prosemirror-inputrules": "^1.5.0",
66
78
  "prosemirror-keymap": "^1.2.3",
67
79
  "prosemirror-schema-basic": "^1.2.3",
68
80
  "rimraf": "^6.0.1",
69
- "typescript": "^5.3.3",
70
- "typescript-eslint": "^8.2.0",
71
- "vite": "^7.0.0",
72
- "vite-plugin-dts": "^4.5.1"
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"
73
87
  },
74
88
  "peerDependencies": {
75
89
  "vitest": "^2.1.9 || ^3.0.7 || ^4.0.4"
@@ -78,5 +92,8 @@
78
92
  "vitest": {
79
93
  "optional": true
80
94
  }
95
+ },
96
+ "engines": {
97
+ "node": ">=20.19.0"
81
98
  }
82
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 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 nextIndentation = `${indentation} `;\n\n if (Object.keys(node.attrs).length > 0) {\n content.push(\n `${nextIndentation}${stringifyObject(node.attrs, { indent: \" \", inlineCharacterLimit: 1000 })},`,\n );\n }\n\n node.content.forEach((item) => {\n content.push(`${stringifyProseMirrorNode(item, nextIndentation)},`);\n });\n\n if (content.length === 0) {\n return `${indentation}${type}()`;\n }\n\n if (content.length === 1 && node.content.firstChild?.type.name === \"text\") {\n return `${indentation}${type}(${stringifyProseMirrorNode(node.content.firstChild, \"\")})`;\n }\n\n return `${indentation}${type}(\\n${content.join(\"\\n\")}\\n${indentation})`;\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":"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"}