vitest-prosemirror 0.3.1 → 0.4.0

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
 
@@ -1,45 +1,42 @@
1
1
  import 'vitest';
2
+ import { EditorState } from 'prosemirror-state';
2
3
  import { Node as Node_2 } from 'prosemirror-model';
3
4
  import { Plugin as Plugin_2 } from 'prosemirror-state';
4
- import { Schema } from 'prosemirror-model';
5
5
  import { Selection as Selection_2 } from 'prosemirror-state';
6
6
 
7
7
  export declare interface CustomMatchers<R = unknown> {
8
8
  toEqualProseMirrorNode(expected: Node_2): R;
9
9
  }
10
10
 
11
- export declare interface KeyboardModifiers {
12
- altKey?: boolean;
13
- ctrlKey?: boolean;
14
- metaKey?: boolean;
15
- shiftKey?: boolean;
16
- }
17
-
18
11
  export declare interface Options {
12
+ autoCleanup: boolean;
19
13
  plugins: Array<Plugin_2>;
20
14
  }
21
15
 
22
16
  export declare class ProseMirrorTester {
23
17
  get doc(): Node_2;
24
- get schema(): Schema;
18
+ get html(): string;
19
+ get state(): EditorState;
20
+ get text(): string;
21
+ private destroyed;
22
+ private readonly element;
25
23
  private readonly view;
26
24
  constructor(documentRoot: Node_2, options?: Partial<Options>);
27
- insertText(text: string, modifiers?: KeyboardModifiers): void;
25
+ destroy(): void;
26
+ insertText(text: string): void;
28
27
  selectText(selection: TesterSelection): void;
29
- private getSelection;
28
+ private assertAlive;
30
29
  }
31
30
 
32
31
  export declare type TesterSelection = "all" | "end" | "start" | {
33
- from: number;
34
- to: number;
32
+ anchor: number;
33
+ head: number;
35
34
  } | Selection_2 | number;
36
35
 
37
36
  export { }
38
37
 
39
38
 
40
39
  declare module "vitest" {
41
- interface Assertion<T = any> extends CustomMatchers<T> {
42
- }
43
- interface AsymmetricMatchersContaining extends CustomMatchers {
40
+ interface Matchers<R extends Promise<void> | void = Promise<void> | void, T = unknown> extends CustomMatchers<R> {
44
41
  }
45
42
  }
@@ -1,342 +1,692 @@
1
- import { expect } from "vitest";
2
- import stringifyObject from "stringify-object";
3
- import { EditorState, AllSelection, TextSelection } from "prosemirror-state";
1
+ import { afterEach, expect } from "vitest";
2
+ import { AllSelection, EditorState, Selection, TextSelection } from "prosemirror-state";
4
3
  import { EditorView } from "prosemirror-view";
5
- 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;
21
- }
22
- const renamedTypes = {
23
- hardBreak: "br",
24
- heading: "h",
25
- horizontalRule: "hr",
26
- paragraph: "p"
4
+ import stringifyObject from "stringify-object";
5
+ //#region src/mockRangeRects.ts
6
+ var mockRangeRects = () => {
7
+ const zeroRect = () => new DOMRect(0, 0, 0, 0);
8
+ const emptyRectList = () => [];
9
+ const rangePrototype = Range.prototype;
10
+ if (typeof rangePrototype.getBoundingClientRect !== "function") Range.prototype.getBoundingClientRect = zeroRect;
11
+ if (typeof rangePrototype.getClientRects !== "function") Range.prototype.getClientRects = emptyRectList;
27
12
  };
28
- 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})`;
13
+ //#endregion
14
+ //#region src/MutationObserverMock.ts
15
+ var MutationObserverMock = class MutationObserverMock {
16
+ static activeObservers = /* @__PURE__ */ new Map();
17
+ callback;
18
+ target;
19
+ constructor(callback) {
20
+ this.callback = callback;
21
+ this.target = void 0;
22
+ }
23
+ static createMutation(target, mutationRecords) {
24
+ const observer = MutationObserverMock.activeObservers.get(target);
25
+ if (observer === void 0) return;
26
+ observer.callback(mutationRecords.map((record) => ({
27
+ addedNodes: [],
28
+ attributeName: null,
29
+ attributeNamespace: null,
30
+ nextSibling: null,
31
+ oldValue: null,
32
+ previousSibling: null,
33
+ removedNodes: [],
34
+ ...record
35
+ })), observer);
36
+ }
37
+ disconnect() {
38
+ if (this.target !== void 0) MutationObserverMock.activeObservers.delete(this.target);
39
+ this.target = void 0;
40
+ }
41
+ observe(target) {
42
+ this.target = target;
43
+ MutationObserverMock.activeObservers.set(target, this);
44
+ }
45
+ takeRecords() {
46
+ return [];
47
+ }
48
+ };
49
+ //#endregion
50
+ //#region src/selection.ts
51
+ var resolveSelection = (doc, selection) => {
52
+ if (selection === "all") return new AllSelection(doc);
53
+ if (typeof selection === "object" && "$anchor" in selection && "$head" in selection) return selection;
54
+ if (typeof selection === "object" && "anchor" in selection && "head" in selection) return TextSelection.between(doc.resolve(selection.anchor), doc.resolve(selection.head));
55
+ if (typeof selection === "object" && "from" in selection && "to" in selection) throw new Error("selectText no longer accepts { from, to } — use { anchor, head } instead. The values map directly: { from: a, to: b } becomes { anchor: a, head: b }.");
56
+ let pos = 0;
57
+ if (selection === "end") pos = doc.nodeSize - 2;
58
+ else if (selection !== "start") pos = selection;
59
+ return TextSelection.near(doc.resolve(pos));
60
+ };
61
+ //#endregion
62
+ //#region src/typing/dom.ts
63
+ var characterDataAt = (node, offset) => {
64
+ if (node instanceof CharacterData) return {
65
+ offset,
66
+ target: node
67
+ };
68
+ const after = node.childNodes[offset];
69
+ if (after instanceof CharacterData) return {
70
+ offset: 0,
71
+ target: after
72
+ };
73
+ return null;
74
+ };
75
+ function deleteText(view, direction) {
76
+ const { selection } = view.state;
77
+ const from = selection.empty && direction === -1 ? selection.from - 1 : selection.from;
78
+ const to = selection.empty && direction === 1 ? selection.to + 1 : selection.to;
79
+ const fromDOM = view.domAtPos(from, -1);
80
+ const target = characterDataAt(fromDOM.node, fromDOM.offset)?.target;
81
+ if (target === void 0) throw new Error("Cannot simulate deleting a range that is not inside a single text node");
82
+ const nodeStart = view.posAtDOM(target, 0);
83
+ const fromOffset = from - nodeStart;
84
+ const toOffset = to - nodeStart;
85
+ if (fromOffset < 0 || toOffset > target.data.length) throw new Error("Cannot simulate deleting a range that is not inside a single text node");
86
+ const oldValue = target.data;
87
+ target.data = target.data.slice(0, fromOffset) + target.data.slice(toOffset);
88
+ MutationObserverMock.createMutation(view.dom, [{
89
+ oldValue,
90
+ target,
91
+ type: "characterData"
92
+ }]);
93
+ }
94
+ function moveCaret(view, direction) {
95
+ const { selection } = view.state;
96
+ let pos = direction === -1 ? selection.from : selection.to;
97
+ if (selection.empty) pos += direction;
98
+ view.dispatch(view.state.tr.setSelection(Selection.near(view.state.doc.resolve(pos), direction)));
52
99
  }
100
+ function typeCharacter(view, character) {
101
+ const { node, offset: domOffset } = view.domAtPos(view.state.selection.from, -1);
102
+ const point = characterDataAt(node, domOffset);
103
+ if (point !== null) {
104
+ const { offset, target } = point;
105
+ const oldValue = target.data;
106
+ target.data = target.data.slice(0, offset) + character + target.data.slice(offset);
107
+ MutationObserverMock.createMutation(view.dom, [{
108
+ oldValue,
109
+ target,
110
+ type: "characterData"
111
+ }]);
112
+ return;
113
+ }
114
+ const textNode = new Text(character);
115
+ const nextSibling = node.childNodes.item(domOffset);
116
+ node.insertBefore(textNode, nextSibling);
117
+ MutationObserverMock.createMutation(view.dom, [{
118
+ addedNodes: [textNode],
119
+ nextSibling,
120
+ previousSibling: textNode.previousSibling,
121
+ target: node,
122
+ type: "childList"
123
+ }]);
124
+ }
125
+ //#endregion
126
+ //#region src/typing/keyboardInput.ts
53
127
  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;
128
+ const output = [];
129
+ let currentGroupOpener = null;
130
+ let group = "";
131
+ for (const char of input) if (currentGroupOpener !== null) {
132
+ if (["]", "}"].includes(char)) {
133
+ if (group.endsWith("\\") && char === matchingBrace(currentGroupOpener)) group = group.slice(0, -2) + char;
134
+ else if (char === matchingBrace(currentGroupOpener)) {
135
+ if (group.length === 4 && group.startsWith("Key")) output.push(group.slice(3).toLowerCase());
136
+ else output.push(group);
137
+ currentGroupOpener = null;
138
+ group = "";
139
+ } else group += char;
140
+ } else if (group === "" && currentGroupOpener === char) {
141
+ output.push(char);
142
+ currentGroupOpener = null;
143
+ group = "";
144
+ } else group += char;
145
+ } else if (["[", "{"].includes(char)) currentGroupOpener = char;
146
+ else output.push(char);
147
+ output.forEach(assertSupported);
148
+ return output;
88
149
  }
89
150
  function assertSupported(character) {
90
- if (/^\/.+/u.exec(character) || /.+>[\d]*\/?$/u.exec(character)) {
91
- throw new Error("Unsupported keyboard input");
92
- }
151
+ if (/^\/.+/u.exec(character) || /.+>[\d]*\/?$/u.exec(character)) throw new Error("Unsupported keyboard input");
93
152
  }
94
153
  function matchingBrace(opener) {
95
- return opener === "{" ? "}" : "]";
154
+ return opener === "{" ? "}" : "]";
96
155
  }
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
- }
156
+ //#endregion
157
+ //#region src/typing/keyChord.ts
158
+ function parseKeyChord(token) {
159
+ const parts = token.split(/-(?!$)/u);
160
+ const key = parts[parts.length - 1] === "Space" ? " " : parts[parts.length - 1];
161
+ const modifiers = {};
162
+ for (const modifier of parts.slice(0, -1)) if (/^(cmd|meta|m)$/iu.test(modifier)) modifiers.metaKey = true;
163
+ else if (/^a(lt)?$/iu.test(modifier)) modifiers.altKey = true;
164
+ else if (/^(c|ctrl|control)$/iu.test(modifier)) modifiers.ctrlKey = true;
165
+ else if (/^s(hift)?$/iu.test(modifier)) modifiers.shiftKey = true;
166
+ else if (/^mod$/iu.test(modifier)) modifiers.ctrlKey = true;
167
+ else throw new Error(`Unrecognized modifier name: ${modifier}`);
168
+ return {
169
+ key,
170
+ modifiers
171
+ };
106
172
  }
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
- }
173
+ //#endregion
174
+ //#region src/typing/keyIdentity.ts
175
+ var namedKeys = new Map(Object.entries({
176
+ Alt: 18,
177
+ ArrowDown: 40,
178
+ ArrowLeft: 37,
179
+ ArrowRight: 39,
180
+ ArrowUp: 38,
181
+ Backspace: 8,
182
+ CapsLock: 20,
183
+ Control: 17,
184
+ Delete: 46,
185
+ End: 35,
186
+ Enter: 13,
187
+ Escape: 27,
188
+ F1: 112,
189
+ F2: 113,
190
+ F3: 114,
191
+ F4: 115,
192
+ F5: 116,
193
+ F6: 117,
194
+ F7: 118,
195
+ F8: 119,
196
+ F9: 120,
197
+ F10: 121,
198
+ F11: 122,
199
+ F12: 123,
200
+ Home: 36,
201
+ Insert: 45,
202
+ Meta: 91,
203
+ PageDown: 34,
204
+ PageUp: 33,
205
+ Shift: 16,
206
+ Tab: 9
207
+ }));
208
+ var sidedCodes = new Map(Object.entries({
209
+ Alt: "AltLeft",
210
+ Control: "ControlLeft",
211
+ Meta: "MetaLeft",
212
+ Shift: "ShiftLeft"
213
+ }));
214
+ var standardLocation = 0;
215
+ var leftLocation = 1;
216
+ var namedCharCodes = new Map(Object.entries({ Enter: 13 }));
217
+ var noCharacter = 0;
218
+ var characterKeys = new Map(Object.entries({
219
+ " ": {
220
+ charCode: 32,
221
+ code: "Space",
222
+ key: " ",
223
+ keyCode: 32,
224
+ location: standardLocation
225
+ },
226
+ "!": {
227
+ charCode: 33,
228
+ code: "Digit1",
229
+ key: "!",
230
+ keyCode: 49,
231
+ location: standardLocation
232
+ },
233
+ "\"": {
234
+ charCode: 34,
235
+ code: "Quote",
236
+ key: "\"",
237
+ keyCode: 222,
238
+ location: standardLocation
239
+ },
240
+ "#": {
241
+ charCode: 35,
242
+ code: "Digit3",
243
+ key: "#",
244
+ keyCode: 51,
245
+ location: standardLocation
246
+ },
247
+ $: {
248
+ charCode: 36,
249
+ code: "Digit4",
250
+ key: "$",
251
+ keyCode: 52,
252
+ location: standardLocation
253
+ },
254
+ "%": {
255
+ charCode: 37,
256
+ code: "Digit5",
257
+ key: "%",
258
+ keyCode: 53,
259
+ location: standardLocation
260
+ },
261
+ "&": {
262
+ charCode: 38,
263
+ code: "Digit7",
264
+ key: "&",
265
+ keyCode: 55,
266
+ location: standardLocation
267
+ },
268
+ "'": {
269
+ charCode: 39,
270
+ code: "Quote",
271
+ key: "'",
272
+ keyCode: 222,
273
+ location: standardLocation
274
+ },
275
+ "(": {
276
+ charCode: 40,
277
+ code: "Digit9",
278
+ key: "(",
279
+ keyCode: 57,
280
+ location: standardLocation
281
+ },
282
+ ")": {
283
+ charCode: 41,
284
+ code: "Digit0",
285
+ key: ")",
286
+ keyCode: 48,
287
+ location: standardLocation
288
+ },
289
+ "*": {
290
+ charCode: 42,
291
+ code: "Digit8",
292
+ key: "*",
293
+ keyCode: 56,
294
+ location: standardLocation
295
+ },
296
+ "+": {
297
+ charCode: 43,
298
+ code: "Equal",
299
+ key: "+",
300
+ keyCode: 187,
301
+ location: standardLocation
302
+ },
303
+ ",": {
304
+ charCode: 44,
305
+ code: "Comma",
306
+ key: ",",
307
+ keyCode: 188,
308
+ location: standardLocation
309
+ },
310
+ "-": {
311
+ charCode: 45,
312
+ code: "Minus",
313
+ key: "-",
314
+ keyCode: 189,
315
+ location: standardLocation
316
+ },
317
+ ".": {
318
+ charCode: 46,
319
+ code: "Period",
320
+ key: ".",
321
+ keyCode: 190,
322
+ location: standardLocation
323
+ },
324
+ "/": {
325
+ charCode: 47,
326
+ code: "Slash",
327
+ key: "/",
328
+ keyCode: 191,
329
+ location: standardLocation
330
+ },
331
+ ":": {
332
+ charCode: 58,
333
+ code: "Semicolon",
334
+ key: ":",
335
+ keyCode: 186,
336
+ location: standardLocation
337
+ },
338
+ ";": {
339
+ charCode: 59,
340
+ code: "Semicolon",
341
+ key: ";",
342
+ keyCode: 186,
343
+ location: standardLocation
344
+ },
345
+ "<": {
346
+ charCode: 60,
347
+ code: "Comma",
348
+ key: "<",
349
+ keyCode: 188,
350
+ location: standardLocation
351
+ },
352
+ "=": {
353
+ charCode: 61,
354
+ code: "Equal",
355
+ key: "=",
356
+ keyCode: 187,
357
+ location: standardLocation
358
+ },
359
+ ">": {
360
+ charCode: 62,
361
+ code: "Period",
362
+ key: ">",
363
+ keyCode: 190,
364
+ location: standardLocation
365
+ },
366
+ "?": {
367
+ charCode: 63,
368
+ code: "Slash",
369
+ key: "?",
370
+ keyCode: 191,
371
+ location: standardLocation
372
+ },
373
+ "@": {
374
+ charCode: 64,
375
+ code: "Digit2",
376
+ key: "@",
377
+ keyCode: 50,
378
+ location: standardLocation
379
+ },
380
+ "[": {
381
+ charCode: 91,
382
+ code: "BracketLeft",
383
+ key: "[",
384
+ keyCode: 219,
385
+ location: standardLocation
386
+ },
387
+ "\\": {
388
+ charCode: 92,
389
+ code: "Backslash",
390
+ key: "\\",
391
+ keyCode: 220,
392
+ location: standardLocation
393
+ },
394
+ "]": {
395
+ charCode: 93,
396
+ code: "BracketRight",
397
+ key: "]",
398
+ keyCode: 221,
399
+ location: standardLocation
400
+ },
401
+ "^": {
402
+ charCode: 94,
403
+ code: "Digit6",
404
+ key: "^",
405
+ keyCode: 54,
406
+ location: standardLocation
407
+ },
408
+ _: {
409
+ charCode: 95,
410
+ code: "Minus",
411
+ key: "_",
412
+ keyCode: 189,
413
+ location: standardLocation
414
+ },
415
+ "`": {
416
+ charCode: 96,
417
+ code: "Backquote",
418
+ key: "`",
419
+ keyCode: 192,
420
+ location: standardLocation
421
+ },
422
+ "{": {
423
+ charCode: 123,
424
+ code: "BracketLeft",
425
+ key: "{",
426
+ keyCode: 219,
427
+ location: standardLocation
428
+ },
429
+ "|": {
430
+ charCode: 124,
431
+ code: "Backslash",
432
+ key: "|",
433
+ keyCode: 220,
434
+ location: standardLocation
435
+ },
436
+ "}": {
437
+ charCode: 125,
438
+ code: "BracketRight",
439
+ key: "}",
440
+ keyCode: 221,
441
+ location: standardLocation
442
+ },
443
+ "~": {
444
+ charCode: 126,
445
+ code: "Backquote",
446
+ key: "~",
447
+ keyCode: 192,
448
+ location: standardLocation
449
+ }
450
+ }));
451
+ function keyIdentity(key) {
452
+ const namedKeyCode = namedKeys.get(key);
453
+ if (namedKeyCode !== void 0) {
454
+ const sidedCode = sidedCodes.get(key);
455
+ return {
456
+ charCode: namedCharCodes.get(key) ?? noCharacter,
457
+ code: sidedCode ?? key,
458
+ key,
459
+ keyCode: namedKeyCode,
460
+ location: sidedCode === void 0 ? standardLocation : leftLocation
461
+ };
462
+ }
463
+ if (/^[a-z]$/iu.exec(key)) {
464
+ const upperCaseKey = key.toUpperCase();
465
+ return {
466
+ charCode: key.charCodeAt(0),
467
+ code: `Key${upperCaseKey}`,
468
+ key,
469
+ keyCode: upperCaseKey.charCodeAt(0),
470
+ location: standardLocation
471
+ };
472
+ }
473
+ if (/^\d$/u.exec(key)) return {
474
+ charCode: key.charCodeAt(0),
475
+ code: `Digit${key}`,
476
+ key,
477
+ keyCode: key.charCodeAt(0),
478
+ location: standardLocation
479
+ };
480
+ const characterKey = characterKeys.get(key);
481
+ if (characterKey !== void 0) return characterKey;
482
+ return {
483
+ charCode: key.length === 1 ? key.charCodeAt(0) : noCharacter,
484
+ code: "",
485
+ key,
486
+ keyCode: 0,
487
+ location: standardLocation
488
+ };
282
489
  }
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;
490
+ //#endregion
491
+ //#region src/typing/typing.ts
492
+ var ignoredKeys = /* @__PURE__ */ new Set([
493
+ "Alt",
494
+ "CapsLock",
495
+ "Control",
496
+ "Meta",
497
+ "Shift",
498
+ "Tab"
499
+ ]);
500
+ var hasModifiers = (modifiers) => modifiers !== void 0 && Object.values(modifiers).includes(true);
501
+ var suppressesCharacter = (modifiers) => modifiers?.altKey === true || modifiers?.ctrlKey === true || modifiers?.metaKey === true;
502
+ var isLetter = (key) => /^[a-z]$/iu.test(key);
503
+ function insertText(view, text) {
504
+ for (const token of tokenizeKeyboardInput(text)) {
505
+ const { key, modifiers } = parseKeyChord(token);
506
+ const character = isLetter(key) && modifiers.shiftKey === true ? key.toUpperCase() : key;
507
+ const shiftKey = isLetter(character) ? character === character.toUpperCase() : modifiers.shiftKey ?? false;
508
+ const identity = keyIdentity(character);
509
+ const eventInit = {
510
+ bubbles: true,
511
+ cancelable: true,
512
+ charCode: 0,
513
+ code: identity.code,
514
+ composed: true,
515
+ key: identity.key,
516
+ keyCode: identity.keyCode,
517
+ location: identity.location,
518
+ ...modifiers,
519
+ shiftKey
520
+ };
521
+ const keydownEvent = new KeyboardEvent("keydown", eventInit);
522
+ view.dispatchEvent(keydownEvent);
523
+ if (!keydownEvent.defaultPrevented) {
524
+ const action = keyAction(character, modifiers);
525
+ if (action.type === "delete") deleteText(view, action.direction);
526
+ else if (action.type === "moveCaret") moveCaret(view, action.direction);
527
+ else if (action.type === "type") {
528
+ view.dispatchEvent(new KeyboardEvent("keypress", {
529
+ ...eventInit,
530
+ charCode: identity.charCode,
531
+ keyCode: identity.charCode
532
+ }));
533
+ typeCharacter(view, action.character);
534
+ }
535
+ }
536
+ view.dispatchEvent(new KeyboardEvent("keyup", eventInit));
537
+ }
294
538
  }
295
- function keyToChar(key) {
296
- if (key === "Enter") {
297
- return "\n";
298
- }
299
- if (key === "Tab") {
300
- return " ";
301
- }
302
- return key;
539
+ function keyAction(key, modifiers) {
540
+ if (ignoredKeys.has(key)) return { type: "ignore" };
541
+ if (key === "Backspace") return {
542
+ direction: -1,
543
+ type: "delete"
544
+ };
545
+ if (key === "Delete") return {
546
+ direction: 1,
547
+ type: "delete"
548
+ };
549
+ if ((key === "ArrowLeft" || key === "ArrowRight") && !hasModifiers(modifiers)) return {
550
+ direction: key === "ArrowLeft" ? -1 : 1,
551
+ type: "moveCaret"
552
+ };
553
+ if (key.length === 1) {
554
+ if (suppressesCharacter(modifiers)) return { type: "ignore" };
555
+ return {
556
+ character: key,
557
+ type: "type"
558
+ };
559
+ }
560
+ throw new Error(`Cannot simulate the "${key}" key`);
303
561
  }
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
562
+ //#endregion
563
+ //#region src/ProseMirrorTester.ts
564
+ var originalMutationObserver = global.MutationObserver;
565
+ var liveTesters = /* @__PURE__ */ new Set();
566
+ var autoCleanupTesters = /* @__PURE__ */ new Set();
567
+ var cleanupTesters = () => {
568
+ for (const tester of [...autoCleanupTesters]) tester.destroy();
341
569
  };
342
- //# sourceMappingURL=vitest-prosemirror.js.map
570
+ var ProseMirrorTester = class {
571
+ get doc() {
572
+ this.assertAlive();
573
+ return this.view.state.doc;
574
+ }
575
+ get html() {
576
+ this.assertAlive();
577
+ return this.view.dom.innerHTML;
578
+ }
579
+ get state() {
580
+ this.assertAlive();
581
+ return this.view.state;
582
+ }
583
+ get text() {
584
+ this.assertAlive();
585
+ return this.view.dom.textContent;
586
+ }
587
+ destroyed = false;
588
+ element;
589
+ view;
590
+ constructor(documentRoot, options = {}) {
591
+ if (typeof document === "undefined") throw new Error("TODO");
592
+ this.element = document.createElement("div");
593
+ document.body.append(this.element);
594
+ const state = EditorState.create({
595
+ doc: documentRoot,
596
+ plugins: options.plugins ?? []
597
+ });
598
+ global.MutationObserver = MutationObserverMock;
599
+ mockRangeRects();
600
+ this.view = new EditorView(this.element, { state });
601
+ liveTesters.add(this);
602
+ if (options.autoCleanup ?? true) autoCleanupTesters.add(this);
603
+ }
604
+ destroy() {
605
+ if (this.destroyed) return;
606
+ this.destroyed = true;
607
+ this.view.destroy();
608
+ this.element.remove();
609
+ document.getSelection()?.removeAllRanges();
610
+ autoCleanupTesters.delete(this);
611
+ liveTesters.delete(this);
612
+ if (liveTesters.size === 0) global.MutationObserver = originalMutationObserver;
613
+ }
614
+ insertText(text) {
615
+ this.assertAlive();
616
+ insertText(this.view, text);
617
+ }
618
+ selectText(selection) {
619
+ this.assertAlive();
620
+ this.view.dispatch(this.view.state.tr.setSelection(resolveSelection(this.doc, selection)));
621
+ }
622
+ assertAlive() {
623
+ if (this.destroyed) throw new Error("This ProseMirrorTester has been destroyed. Testers are destroyed automatically after each test; pass { autoCleanup: false } to keep one alive (e.g. across a beforeAll).");
624
+ }
625
+ };
626
+ //#endregion
627
+ //#region src/stringifyProseMirrorNode.ts
628
+ function stringifyProseMirrorNode(node, indentation = "") {
629
+ if (node.type.spec.toDebugString !== void 0) return `${indentation}${wrapMarks(node.marks, node.type.spec.toDebugString(node))}`;
630
+ if (node.type.name === "text") return `${indentation}${wrapMarks(node.marks, escapeText(node.textContent))}`;
631
+ const type = node.type.name;
632
+ const content = [];
633
+ const nextIndentation = `${indentation} `;
634
+ if (Object.keys(node.attrs).length > 0) content.push(`${nextIndentation}${stringifyObject(node.attrs, {
635
+ indent: " ",
636
+ inlineCharacterLimit: 1e3
637
+ })},`);
638
+ node.content.forEach((item) => {
639
+ content.push(`${stringifyProseMirrorNode(item, nextIndentation)},`);
640
+ });
641
+ let body;
642
+ if (content.length === 0) body = `${type}()`;
643
+ else if (content.length === 1 && node.content.firstChild?.type.name === "text") body = `${type}(${stringifyProseMirrorNode(node.content.firstChild, "")})`;
644
+ else body = `${type}(\n${content.join("\n")}\n${indentation})`;
645
+ return `${indentation}${wrapMarks(node.marks, body)}`;
646
+ }
647
+ function escapeText(text) {
648
+ return `'${text.replace(/[\p{Cc}'\\]/gu, (char) => {
649
+ switch (char) {
650
+ case "\f": return "\\f";
651
+ case "\n": return "\\n";
652
+ case "\r": return "\\r";
653
+ case " ": return "\\t";
654
+ case "\b": return "\\b";
655
+ case "'": return "\\'";
656
+ case "\\": return "\\\\";
657
+ default: return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
658
+ }
659
+ })}'`;
660
+ }
661
+ function wrapMarks(marks, origContent) {
662
+ let content = origContent;
663
+ for (const mark of [...marks].reverse()) {
664
+ const hasAttrs = Object.keys(mark.attrs).length > 0;
665
+ const items = [content];
666
+ if (hasAttrs) items.unshift(stringifyObject(mark.attrs, {
667
+ indent: " ",
668
+ inlineCharacterLimit: 1e3
669
+ }));
670
+ content = `${mark.type.name}(${items.join(", ")})`;
671
+ }
672
+ return content;
673
+ }
674
+ //#endregion
675
+ //#region src/index.ts
676
+ afterEach(cleanupTesters);
677
+ expect.extend({ toEqualProseMirrorNode(received, expected) {
678
+ const receivedDoc = `\n${stringifyProseMirrorNode(received)}\n`;
679
+ const expectedDoc = `\n${stringifyProseMirrorNode(expected)}\n`;
680
+ const sameSchema = received.type.schema === expected.type.schema;
681
+ const pass = sameSchema && this.equals(receivedDoc, expectedDoc);
682
+ return {
683
+ 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)}` : () => {
684
+ if (!sameSchema && this.equals(receivedDoc, expectedDoc)) return `${this.utils.matcherHint(".toEqualProsemirrorNode")}\n\nThe documents stringify identically but come from different schemas:\n${this.utils.printReceived(receivedDoc)}`;
685
+ const diffString = this.utils.diff(expectedDoc, receivedDoc);
686
+ 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}`}`;
687
+ },
688
+ pass
689
+ };
690
+ } });
691
+ //#endregion
692
+ 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.4.0",
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,51 +33,67 @@
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
- "@vitest/coverage-v8": "^4.0.4",
64
+ "@vitest/coverage-v8": "^5.0.0",
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": "^5.0.0",
86
+ "w3c-keyname": "^2.2.8"
73
87
  },
74
88
  "peerDependencies": {
75
- "vitest": "^2.1.9 || ^3.0.7 || ^4.0.4"
89
+ "vitest": "^2.1.9 || ^3.0.7 || ^4.0.4 || ^5.0.0"
76
90
  },
77
91
  "peerDependenciesMeta": {
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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"}