vitest-prosemirror 0.3.2 → 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/dist/vitest-prosemirror.d.ts +13 -15
- package/dist/vitest-prosemirror.js +180 -114
- package/package.json +4 -4
|
@@ -1,44 +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
|
|
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
|
-
|
|
25
|
+
destroy(): void;
|
|
26
|
+
insertText(text: string): void;
|
|
28
27
|
selectText(selection: TesterSelection): void;
|
|
28
|
+
private assertAlive;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export declare type TesterSelection = "all" | "end" | "start" | {
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
anchor: number;
|
|
33
|
+
head: number;
|
|
34
34
|
} | Selection_2 | number;
|
|
35
35
|
|
|
36
36
|
export { }
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
declare module "vitest" {
|
|
40
|
-
interface
|
|
41
|
-
}
|
|
42
|
-
interface AsymmetricMatchersContaining extends CustomMatchers {
|
|
40
|
+
interface Matchers<R extends Promise<void> | void = Promise<void> | void, T = unknown> extends CustomMatchers<R> {
|
|
43
41
|
}
|
|
44
42
|
}
|
|
@@ -1,45 +1,8 @@
|
|
|
1
|
-
import { expect } from "vitest";
|
|
2
|
-
import stringifyObject from "stringify-object";
|
|
1
|
+
import { afterEach, expect } from "vitest";
|
|
3
2
|
import { AllSelection, EditorState, Selection, TextSelection } from "prosemirror-state";
|
|
4
3
|
import { EditorView } from "prosemirror-view";
|
|
5
|
-
|
|
6
|
-
|
|
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;
|
|
18
|
-
}
|
|
19
|
-
var renamedTypes = {
|
|
20
|
-
hardBreak: "br",
|
|
21
|
-
heading: "h",
|
|
22
|
-
horizontalRule: "hr",
|
|
23
|
-
paragraph: "p"
|
|
24
|
-
};
|
|
25
|
-
function stringifyProseMirrorNode(node, 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})`;
|
|
40
|
-
}
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region src/utils/mockRangeRects.ts
|
|
4
|
+
import stringifyObject from "stringify-object";
|
|
5
|
+
//#region src/mockRangeRects.ts
|
|
43
6
|
var mockRangeRects = () => {
|
|
44
7
|
const zeroRect = () => new DOMRect(0, 0, 0, 0);
|
|
45
8
|
const emptyRectList = () => [];
|
|
@@ -48,7 +11,7 @@ var mockRangeRects = () => {
|
|
|
48
11
|
if (typeof rangePrototype.getClientRects !== "function") Range.prototype.getClientRects = emptyRectList;
|
|
49
12
|
};
|
|
50
13
|
//#endregion
|
|
51
|
-
//#region src/
|
|
14
|
+
//#region src/MutationObserverMock.ts
|
|
52
15
|
var MutationObserverMock = class MutationObserverMock {
|
|
53
16
|
static activeObservers = /* @__PURE__ */ new Map();
|
|
54
17
|
callback;
|
|
@@ -84,18 +47,19 @@ var MutationObserverMock = class MutationObserverMock {
|
|
|
84
47
|
}
|
|
85
48
|
};
|
|
86
49
|
//#endregion
|
|
87
|
-
//#region src/
|
|
50
|
+
//#region src/selection.ts
|
|
88
51
|
var resolveSelection = (doc, selection) => {
|
|
89
52
|
if (selection === "all") return new AllSelection(doc);
|
|
90
53
|
if (typeof selection === "object" && "$anchor" in selection && "$head" in selection) return selection;
|
|
91
|
-
if (typeof selection === "object" && "
|
|
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 }.");
|
|
92
56
|
let pos = 0;
|
|
93
57
|
if (selection === "end") pos = doc.nodeSize - 2;
|
|
94
58
|
else if (selection !== "start") pos = selection;
|
|
95
59
|
return TextSelection.near(doc.resolve(pos));
|
|
96
60
|
};
|
|
97
61
|
//#endregion
|
|
98
|
-
//#region src/
|
|
62
|
+
//#region src/typing/dom.ts
|
|
99
63
|
var characterDataAt = (node, offset) => {
|
|
100
64
|
if (node instanceof CharacterData) return {
|
|
101
65
|
offset,
|
|
@@ -108,8 +72,58 @@ var characterDataAt = (node, offset) => {
|
|
|
108
72
|
};
|
|
109
73
|
return null;
|
|
110
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)));
|
|
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
|
+
}
|
|
111
125
|
//#endregion
|
|
112
|
-
//#region src/
|
|
126
|
+
//#region src/typing/keyboardInput.ts
|
|
113
127
|
function tokenizeKeyboardInput(input) {
|
|
114
128
|
const output = [];
|
|
115
129
|
let currentGroupOpener = null;
|
|
@@ -140,7 +154,24 @@ function matchingBrace(opener) {
|
|
|
140
154
|
return opener === "{" ? "}" : "]";
|
|
141
155
|
}
|
|
142
156
|
//#endregion
|
|
143
|
-
//#region src/
|
|
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
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/typing/keyIdentity.ts
|
|
144
175
|
var namedKeys = new Map(Object.entries({
|
|
145
176
|
Alt: 18,
|
|
146
177
|
ArrowDown: 40,
|
|
@@ -457,7 +488,7 @@ function keyIdentity(key) {
|
|
|
457
488
|
};
|
|
458
489
|
}
|
|
459
490
|
//#endregion
|
|
460
|
-
//#region src/
|
|
491
|
+
//#region src/typing/typing.ts
|
|
461
492
|
var ignoredKeys = /* @__PURE__ */ new Set([
|
|
462
493
|
"Alt",
|
|
463
494
|
"CapsLock",
|
|
@@ -466,15 +497,14 @@ var ignoredKeys = /* @__PURE__ */ new Set([
|
|
|
466
497
|
"Shift",
|
|
467
498
|
"Tab"
|
|
468
499
|
]);
|
|
469
|
-
var backward = -1;
|
|
470
|
-
var forward = 1;
|
|
471
500
|
var hasModifiers = (modifiers) => modifiers !== void 0 && Object.values(modifiers).includes(true);
|
|
472
501
|
var suppressesCharacter = (modifiers) => modifiers?.altKey === true || modifiers?.ctrlKey === true || modifiers?.metaKey === true;
|
|
473
502
|
var isLetter = (key) => /^[a-z]$/iu.test(key);
|
|
474
|
-
function insertText(view, text
|
|
475
|
-
for (const
|
|
476
|
-
const
|
|
477
|
-
const
|
|
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;
|
|
478
508
|
const identity = keyIdentity(character);
|
|
479
509
|
const eventInit = {
|
|
480
510
|
bubbles: true,
|
|
@@ -506,37 +536,18 @@ function insertText(view, text, modifiers) {
|
|
|
506
536
|
view.dispatchEvent(new KeyboardEvent("keyup", eventInit));
|
|
507
537
|
}
|
|
508
538
|
}
|
|
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
539
|
function keyAction(key, modifiers) {
|
|
529
540
|
if (ignoredKeys.has(key)) return { type: "ignore" };
|
|
530
541
|
if (key === "Backspace") return {
|
|
531
|
-
direction:
|
|
542
|
+
direction: -1,
|
|
532
543
|
type: "delete"
|
|
533
544
|
};
|
|
534
545
|
if (key === "Delete") return {
|
|
535
|
-
direction:
|
|
546
|
+
direction: 1,
|
|
536
547
|
type: "delete"
|
|
537
548
|
};
|
|
538
549
|
if ((key === "ArrowLeft" || key === "ArrowRight") && !hasModifiers(modifiers)) return {
|
|
539
|
-
direction: key === "ArrowLeft" ?
|
|
550
|
+
direction: key === "ArrowLeft" ? -1 : 1,
|
|
540
551
|
type: "moveCaret"
|
|
541
552
|
};
|
|
542
553
|
if (key.length === 1) {
|
|
@@ -548,74 +559,129 @@ function keyAction(key, modifiers) {
|
|
|
548
559
|
}
|
|
549
560
|
throw new Error(`Cannot simulate the "${key}" key`);
|
|
550
561
|
}
|
|
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)));
|
|
556
|
-
}
|
|
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
|
-
}]);
|
|
581
|
-
}
|
|
582
562
|
//#endregion
|
|
583
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();
|
|
569
|
+
};
|
|
584
570
|
var ProseMirrorTester = class {
|
|
585
571
|
get doc() {
|
|
572
|
+
this.assertAlive();
|
|
586
573
|
return this.view.state.doc;
|
|
587
574
|
}
|
|
588
|
-
get
|
|
589
|
-
|
|
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;
|
|
590
586
|
}
|
|
587
|
+
destroyed = false;
|
|
588
|
+
element;
|
|
591
589
|
view;
|
|
592
590
|
constructor(documentRoot, options = {}) {
|
|
593
591
|
if (typeof document === "undefined") throw new Error("TODO");
|
|
594
|
-
|
|
595
|
-
document.body.append(element);
|
|
592
|
+
this.element = document.createElement("div");
|
|
593
|
+
document.body.append(this.element);
|
|
596
594
|
const state = EditorState.create({
|
|
597
595
|
doc: documentRoot,
|
|
598
596
|
plugins: options.plugins ?? []
|
|
599
597
|
});
|
|
600
598
|
global.MutationObserver = MutationObserverMock;
|
|
601
599
|
mockRangeRects();
|
|
602
|
-
this.view = new EditorView(element, { state });
|
|
600
|
+
this.view = new EditorView(this.element, { state });
|
|
601
|
+
liveTesters.add(this);
|
|
602
|
+
if (options.autoCleanup ?? true) autoCleanupTesters.add(this);
|
|
603
603
|
}
|
|
604
|
-
|
|
605
|
-
|
|
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);
|
|
606
617
|
}
|
|
607
618
|
selectText(selection) {
|
|
619
|
+
this.assertAlive();
|
|
608
620
|
this.view.dispatch(this.view.state.tr.setSelection(resolveSelection(this.doc, selection)));
|
|
609
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
|
+
}
|
|
610
625
|
};
|
|
611
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
|
|
612
675
|
//#region src/index.ts
|
|
676
|
+
afterEach(cleanupTesters);
|
|
613
677
|
expect.extend({ toEqualProseMirrorNode(received, expected) {
|
|
614
678
|
const receivedDoc = `\n${stringifyProseMirrorNode(received)}\n`;
|
|
615
679
|
const expectedDoc = `\n${stringifyProseMirrorNode(expected)}\n`;
|
|
616
|
-
const
|
|
680
|
+
const sameSchema = received.type.schema === expected.type.schema;
|
|
681
|
+
const pass = sameSchema && this.equals(receivedDoc, expectedDoc);
|
|
617
682
|
return {
|
|
618
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)}`;
|
|
619
685
|
const diffString = this.utils.diff(expectedDoc, receivedDoc);
|
|
620
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}`}`;
|
|
621
687
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-prosemirror",
|
|
3
|
-
"version": "0.
|
|
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",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@microsoft/api-extractor": "^7.58.7",
|
|
62
62
|
"@types/node": "^26.0.0",
|
|
63
63
|
"@types/stringify-object": "^4.0.5",
|
|
64
|
-
"@vitest/coverage-v8": "^
|
|
64
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
65
65
|
"@vitest/eslint-plugin": "^1.3.26",
|
|
66
66
|
"es-check": "^9.6.4",
|
|
67
67
|
"eslint": "^10.0.2",
|
|
@@ -82,11 +82,11 @@
|
|
|
82
82
|
"typescript-eslint": "^8.58.0",
|
|
83
83
|
"unplugin-dts": "^1.0.1",
|
|
84
84
|
"vite": "^8.0.0",
|
|
85
|
-
"vitest": "^
|
|
85
|
+
"vitest": "^5.0.0",
|
|
86
86
|
"w3c-keyname": "^2.2.8"
|
|
87
87
|
},
|
|
88
88
|
"peerDependencies": {
|
|
89
|
-
"vitest": "^2.1.9 || ^3.0.7 || ^4.0.4"
|
|
89
|
+
"vitest": "^2.1.9 || ^3.0.7 || ^4.0.4 || ^5.0.0"
|
|
90
90
|
},
|
|
91
91
|
"peerDependenciesMeta": {
|
|
92
92
|
"vitest": {
|