@ryupold/vode 1.8.7 → 1.8.10
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 +34 -56
- package/dist/vode.cjs.min.js +2 -2
- package/dist/vode.d.ts +10 -10
- package/dist/vode.es5.min.js +7 -7
- package/dist/vode.js +97 -113
- package/dist/vode.min.js +1 -1
- package/dist/vode.min.mjs +1 -1
- package/dist/vode.mjs +97 -113
- package/dist/vode.tests.mjs +5303 -0
- package/package.json +5 -5
- package/src/state-context.ts +6 -4
- package/src/vode.ts +114 -126
- package/test/helper.ts +304 -113
- package/test/index.ts +10 -47
- package/test/mocks.ts +199 -43
- package/test/run-tests.ts +61 -0
- package/test/tests-app.ts +154 -38
- package/test/tests-catch.ts +160 -0
- package/test/tests-children.ts +31 -31
- package/test/tests-createPatch.ts +12 -12
- package/test/tests-createState.ts +11 -11
- package/test/tests-defuse.ts +35 -14
- package/test/tests-examples.ts +991 -0
- package/test/tests-hydrate.ts +59 -25
- package/test/tests-memo.ts +106 -64
- package/test/tests-mergeClass.ts +31 -31
- package/test/tests-mergeProps.ts +19 -19
- package/test/tests-mergeStyle.ts +28 -14
- package/test/tests-mount-unmount.ts +177 -154
- package/test/tests-patch-advanced.ts +86 -0
- package/test/tests-patch-merge.ts +66 -0
- package/test/tests-props.ts +15 -15
- package/test/tests-state-context.ts +56 -25
- package/test/tests-tag.ts +14 -14
- package/test/tests-vode.ts +6 -6
package/test/helper.ts
CHANGED
|
@@ -1,159 +1,350 @@
|
|
|
1
|
-
import { children, ChildVode, PatchableState, tag, Vode } from "../src/vode";
|
|
2
|
-
import {
|
|
1
|
+
import { children, ChildVode, PatchableState, props, tag, Vode } from "../src/vode";
|
|
2
|
+
import { FakeElement, FakeTextNode } from "./mocks";
|
|
3
|
+
|
|
4
|
+
// Helper to detect if we're in a browser environment with real DOM
|
|
5
|
+
const isBrowser = typeof window !== 'undefined' && typeof HTMLElement !== 'undefined';
|
|
6
|
+
|
|
7
|
+
// Type guards for real DOM elements
|
|
8
|
+
function isRealElement(node: any): node is HTMLElement {
|
|
9
|
+
return isBrowser && node instanceof HTMLElement;
|
|
10
|
+
}
|
|
11
|
+
function isRealTextNode(node: any): node is Text {
|
|
12
|
+
return isBrowser && node instanceof Text;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
16
|
+
|
|
17
|
+
function retry<T = void>(fn: () => Promise<T>, waitTime: number): Promise<T> {
|
|
18
|
+
const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
19
|
+
|
|
20
|
+
function retryInternal(timeLeft: number) {
|
|
21
|
+
const start = performance.now();
|
|
22
|
+
try {
|
|
23
|
+
const prom = fn();
|
|
24
|
+
if (typeof prom?.then === "function") {
|
|
25
|
+
prom.then(resolve).catch((err) => {
|
|
26
|
+
if (timeLeft >= 0) {
|
|
27
|
+
setTimeout(
|
|
28
|
+
() => retryInternal(timeLeft - (performance.now() - start)),
|
|
29
|
+
10
|
|
30
|
+
);
|
|
31
|
+
} else {
|
|
32
|
+
reject(err);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
resolve(prom);
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (timeLeft >= 0) {
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
retryInternal(timeLeft - (performance.now() - start))
|
|
42
|
+
}, 10);
|
|
43
|
+
} else {
|
|
44
|
+
reject(err);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
retryInternal(waitTime);
|
|
50
|
+
|
|
51
|
+
return promise;
|
|
52
|
+
}
|
|
3
53
|
|
|
4
54
|
export class Expectation {
|
|
5
55
|
constructor(public readonly what: any) { }
|
|
6
56
|
|
|
7
|
-
|
|
57
|
+
toBeNotHidden() {
|
|
58
|
+
if (document.hidden) {
|
|
59
|
+
throw new ExpectationError(this, `expect the document to be not hidden. if you run this in a real browser this means the window must be in focus in order for the tests to work.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
toBeA(type: "undefined" | "object" | "function" | "bigint" | "boolean" | "number" | "string" | "symbol", failMessage?: string) {
|
|
8
64
|
if (typeof this.what !== type) {
|
|
9
|
-
throw new ExpectationError(this, `expected \n\ntypeof ${this.what}\n\nto be \n\n${type}`);
|
|
65
|
+
throw new ExpectationError(this, `expected \n\ntypeof ${this.what}\n\nto be \n\n${type}${failMessage ? `\n\n${failMessage}` : ""}`);
|
|
10
66
|
}
|
|
11
67
|
}
|
|
12
68
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return path;
|
|
19
|
-
}
|
|
69
|
+
toBeGreaterThan(other: number, failMessage?: string) {
|
|
70
|
+
if (!(this.what > other)) {
|
|
71
|
+
throw new ExpectationError(this, `expected \n\n${this.what}\n\nto be >\n\n${other}${failMessage ? `\n\n${failMessage}` : ""}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
20
74
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
75
|
+
toBeGreaterOrEqualThan(other: number, failMessage?: string) {
|
|
76
|
+
if (!(this.what >= other)) {
|
|
77
|
+
throw new ExpectationError(this, `expected \n\n${this.what}\n\nto be >= \n\n${other}${failMessage ? `\n\n${failMessage}` : ""}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
toBeSmallerThan(other: number, failMessage?: string) {
|
|
82
|
+
if (!(this.what < other)) {
|
|
83
|
+
throw new ExpectationError(this, `expected \n\n${this.what}\n\nto be <\n\n${other}${failMessage ? `\n\n${failMessage}` : ""}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
toBeSmallerOrEqual(other: number, failMessage?: string) {
|
|
88
|
+
if (!(this.what <= other)) {
|
|
89
|
+
throw new ExpectationError(this, `expected \n\n${this.what}\n\nto be <= \n\n${other}${failMessage ? `\n\n${failMessage}` : ""}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async toEqual(other: any, failMessage?: string, waitTimeMs: number = 100) {
|
|
94
|
+
return await retry(
|
|
95
|
+
async () => {
|
|
96
|
+
const failSuffix = failMessage ? `\n\n${failMessage}` : "";
|
|
26
97
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
98
|
+
function deepCompare(a: any, b: any, path: string[]): string[] | null {
|
|
99
|
+
if (typeof a !== typeof b) {
|
|
100
|
+
if (path.length === 0) path.push(``);
|
|
101
|
+
path[path.length - 1] += ` (type: ${typeof a} != ${typeof b})`;
|
|
102
|
+
return path;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (typeof a !== "object" || a === null) {
|
|
106
|
+
if (path.length === 0) path.push(``);
|
|
107
|
+
path[path.length - 1] += ` (value: ${a} != ${b})`;
|
|
108
|
+
return a !== b ? path : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const prop of Object.entries(a)) {
|
|
112
|
+
const [k, v] = prop;
|
|
113
|
+
const result = deepCompare(v, b[k], [...path, k]);
|
|
114
|
+
if (result) {
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const prop of Object.entries(b)) {
|
|
120
|
+
const [k, v] = prop;
|
|
121
|
+
const result = deepCompare(a[k], v, [...path, k]);
|
|
122
|
+
if (result) {
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null;
|
|
32
128
|
}
|
|
33
|
-
}
|
|
34
129
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
130
|
+
if (typeof this.what === "object" && typeof other === "object" && this.what !== null && other !== null) {
|
|
131
|
+
const unequal = deepCompare(this.what, other, []);
|
|
132
|
+
if (unequal) {
|
|
133
|
+
throw new ExpectationError(this, `expected \n\n${JSON.stringify(this.what, null, 2)}\n\n to equal \n\n${JSON.stringify(other, null, 2)}\n\nThey differ in: ${unequal.join(".")}${failSuffix}`);
|
|
134
|
+
}
|
|
40
135
|
}
|
|
41
|
-
|
|
136
|
+
else {
|
|
137
|
+
if (this.what !== other) {
|
|
138
|
+
throw new ExpectationError(this, `expected (${typeof this.what})\n\n${this.what}\n\nto equal (${typeof other})\n\n${other}${failSuffix}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
waitTimeMs
|
|
143
|
+
);
|
|
144
|
+
}
|
|
42
145
|
|
|
43
|
-
|
|
146
|
+
toSucceed<Result>(): Result {
|
|
147
|
+
if (typeof this.what !== "function") {
|
|
148
|
+
throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}`);
|
|
44
149
|
}
|
|
150
|
+
return this.what();
|
|
151
|
+
}
|
|
45
152
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
throw new ExpectationError(this, `expected \n\n${JSON.stringify(this.what, null, 2)}\n\n to equal \n\n${JSON.stringify(other, null, 2)}\n\nThey differ in: ${unequal.join(".")}`);
|
|
50
|
-
}
|
|
153
|
+
toFail(): Error {
|
|
154
|
+
if (typeof this.what !== "function") {
|
|
155
|
+
throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}`);
|
|
51
156
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
157
|
+
|
|
158
|
+
let r: any;
|
|
159
|
+
try {
|
|
160
|
+
r = this.what();
|
|
161
|
+
} catch (err: any) {
|
|
162
|
+
return err;
|
|
56
163
|
}
|
|
164
|
+
throw new ExpectationError(this, `expected function to fail\n\nbut it succeeded with a result of type ${typeof r}\n\n${r}`);
|
|
57
165
|
}
|
|
58
166
|
|
|
59
|
-
|
|
167
|
+
toSucceedAsync<Result>(waitTime: number = 100): Promise<Result> {
|
|
60
168
|
if (typeof this.what !== "function") {
|
|
61
169
|
throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}`);
|
|
62
170
|
}
|
|
63
|
-
return this.what(
|
|
171
|
+
return retry<Result>(() => this.what(), waitTime);
|
|
64
172
|
}
|
|
65
173
|
|
|
66
|
-
|
|
174
|
+
async toFailAsync(): Promise<Error> {
|
|
67
175
|
if (typeof this.what !== "function") {
|
|
68
176
|
throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}`);
|
|
69
177
|
}
|
|
70
178
|
|
|
71
179
|
let r: any;
|
|
72
180
|
try {
|
|
73
|
-
r = this.what(
|
|
181
|
+
r = await this.what();
|
|
74
182
|
} catch (err: any) {
|
|
75
183
|
return err;
|
|
76
184
|
}
|
|
77
185
|
throw new ExpectationError(this, `expected function to fail\n\nbut it succeeded with a result of type ${typeof r}\n\n${r}`);
|
|
78
186
|
}
|
|
79
187
|
|
|
80
|
-
toMatch(v: ChildVode,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
188
|
+
async toMatch(v: ChildVode,
|
|
189
|
+
state?: PatchableState | null,
|
|
190
|
+
failMessage?: string,
|
|
191
|
+
waitTimeMs: number = 100
|
|
192
|
+
) {
|
|
193
|
+
return await retry(
|
|
194
|
+
async () => {
|
|
195
|
+
const failSuffix = failMessage ? `\n\n${failMessage}` : "";
|
|
84
196
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
cv = cv(state);
|
|
91
|
-
}
|
|
92
|
-
while (typeof e === "function") {
|
|
93
|
-
if (!state) {
|
|
94
|
-
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na Component\n\nbut got no state passed in [toMatch]`);
|
|
95
|
-
}
|
|
96
|
-
e = e(state);
|
|
97
|
-
}
|
|
197
|
+
// Support FakeElement, FakeTextNode, real HTMLElement, real Text nodes, strings, arrays, functions
|
|
198
|
+
if (this.what instanceof FakeElement || this.what instanceof FakeTextNode ||
|
|
199
|
+
isRealElement(this.what) || isRealTextNode(this.what) ||
|
|
200
|
+
typeof this.what === "string" || Array.isArray(this.what) || typeof this.what === "function") {
|
|
201
|
+
const that = this;
|
|
98
202
|
|
|
99
|
-
|
|
100
|
-
if (cv !== e.wholeText) {
|
|
101
|
-
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node with\n${cv}\n\nbut text was\n${e.wholeText}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
else if (typeof cv === "string" && typeof e === "string") {
|
|
105
|
-
if (cv !== e) {
|
|
106
|
-
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node with\n${cv}\n\nbut text was\n${e}`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
203
|
+
function deepCompare(e: FakeElement | FakeTextNode | HTMLElement | Text | ChildVode, cv: ChildVode, path: string[]): string[] | null {
|
|
109
204
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (tag(cv)?.toLocaleUpperCase() !== tag(e)?.toUpperCase()) {
|
|
124
|
-
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element [${tag(cv)}]\n\nbut got [${tag(e)}]`);
|
|
125
|
-
}
|
|
126
|
-
const kids = children(cv) || [];
|
|
127
|
-
const otherKids = children(e) || [];
|
|
128
|
-
for (let i = 0; i < kids.length; i++) {
|
|
129
|
-
deepCompare(otherKids[i], kids[i], [...path, `${tag(kids[i] as Vode) || "#text"}`]);
|
|
130
|
-
}
|
|
131
|
-
if (kids.length !== otherKids.length) {
|
|
132
|
-
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\n${kids.length} children\n\nbut [${tag(e)}] has ${otherKids.length} children`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
205
|
+
// unwrap component
|
|
206
|
+
while (typeof cv === "function") {
|
|
207
|
+
if (!state) {
|
|
208
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na Component\n\nbut got no state passed in [toMatch]${failSuffix}`);
|
|
209
|
+
}
|
|
210
|
+
cv = cv(state);
|
|
211
|
+
}
|
|
212
|
+
while (typeof e === "function") {
|
|
213
|
+
if (!state) {
|
|
214
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na Component\n\nbut got no state passed in [toMatch]${failSuffix}`);
|
|
215
|
+
}
|
|
216
|
+
e = e(state);
|
|
217
|
+
}
|
|
135
218
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
219
|
+
// string matches TextNode (fake or real)
|
|
220
|
+
if (typeof cv === "string" && (e instanceof FakeTextNode || isRealTextNode(e))) {
|
|
221
|
+
const text = e instanceof FakeTextNode ? e.wholeText : (e as Text).wholeText;
|
|
222
|
+
if (cv !== text) {
|
|
223
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node with\n${cv}\n\nbut text was\n${text}${failSuffix}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// string matches string
|
|
227
|
+
else if (typeof cv === "string" && typeof e === "string") {
|
|
228
|
+
if (cv !== e) {
|
|
229
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node with\n${cv}\n\nbut text was\n${e}${failSuffix}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
142
232
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
233
|
+
// vode matches element (fake or real)
|
|
234
|
+
else if (Array.isArray(cv) && (e instanceof FakeElement || isRealElement(e))) {
|
|
235
|
+
const tagName = e instanceof FakeElement ? e.tagName : (e as HTMLElement).tagName;
|
|
236
|
+
if (tag(cv)?.toUpperCase() !== tagName.toUpperCase()) {
|
|
237
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nbut got <${tagName.toUpperCase()}>${failSuffix}`);
|
|
238
|
+
}
|
|
149
239
|
|
|
150
|
-
|
|
151
|
-
|
|
240
|
+
// compare attributes/props
|
|
241
|
+
const properties = props(cv);
|
|
242
|
+
if (properties) {
|
|
243
|
+
for (const [k, val] of Object.entries(properties)) {
|
|
244
|
+
let attributeValue: string | null;
|
|
245
|
+
if (e instanceof FakeElement) {
|
|
246
|
+
attributeValue = e.fakeAttributes[k] ?? null;
|
|
247
|
+
} else {
|
|
248
|
+
attributeValue = (e as HTMLElement).getAttribute(k);
|
|
249
|
+
}
|
|
250
|
+
if (!attributeValue) {
|
|
251
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nwith attribute [${k}="${val}"]\n\nbut it was not found${failSuffix}`);
|
|
252
|
+
}
|
|
253
|
+
if (attributeValue !== val) {
|
|
254
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nwith attribute [${k}="${val}"]\n\nbut it was [${k}="${attributeValue}"]${failSuffix}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
152
258
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
259
|
+
// compare children - handle browser text node normalization
|
|
260
|
+
const kids = children(cv) || [];
|
|
261
|
+
const childNodes = e instanceof FakeElement ? e.children : (e as HTMLElement).childNodes;
|
|
262
|
+
|
|
263
|
+
// Check if all kids are text (strings) - browsers may merge these
|
|
264
|
+
const allKidsAreText = kids.every(k => typeof k === "string");
|
|
265
|
+
if (allKidsAreText && isBrowser && kids.length > 1) {
|
|
266
|
+
// Browser likely merged text nodes - compare concatenated text
|
|
267
|
+
const expectedText = kids.join("");
|
|
268
|
+
const actualText = (e as HTMLElement).textContent || "";
|
|
269
|
+
if (expectedText !== actualText) {
|
|
270
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\ntext content "${expectedText}"\n\nbut got "${actualText}"${failSuffix}`);
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
// Normal child-by-child comparison
|
|
274
|
+
for (let i = 0; i < kids.length; i++) {
|
|
275
|
+
const childNode = e instanceof FakeElement
|
|
276
|
+
? childNodes.item(i) as any
|
|
277
|
+
: childNodes.item(i) as any;
|
|
278
|
+
deepCompare(childNode, kids[i], [...path, `[${i}]${tag(kids[i] as Vode)?.toUpperCase() || "#text"}`]);
|
|
279
|
+
}
|
|
280
|
+
const childCount = e instanceof FakeElement ? e.children.length : (e as HTMLElement).childNodes.length;
|
|
281
|
+
if (kids.length !== childCount) {
|
|
282
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\n${kids.length} children\n\nbut <${tagName.toUpperCase()}> has ${childCount} children${failSuffix}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// vode matches vode
|
|
287
|
+
else if (Array.isArray(cv) && Array.isArray(e)) {
|
|
288
|
+
if (tag(cv)?.toUpperCase() !== tag(e)?.toUpperCase()) {
|
|
289
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na vode [${tag(cv)?.toUpperCase()}]\n\nbut got [${tag(e)?.toUpperCase()}]${failSuffix}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// compare attributes/props
|
|
293
|
+
const properties = props(cv);
|
|
294
|
+
const otherProperties = props(e) || {};
|
|
295
|
+
if (properties) {
|
|
296
|
+
for (const [k, val] of Object.entries(properties)) {
|
|
297
|
+
const attributeValue = otherProperties[k];
|
|
298
|
+
if (!attributeValue) {
|
|
299
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na vode [${tag(cv)?.toUpperCase()}]\n\nwith attribute [${k}="${val}"]\n\nbut it was not found${failSuffix}`);
|
|
300
|
+
}
|
|
301
|
+
if (attributeValue !== val) {
|
|
302
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na vode [${tag(cv)?.toUpperCase()}]\n\nwith attribute [${k}="${val}"]\n\nbut its value was [${k}="${attributeValue}"]${failSuffix}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// compare children
|
|
308
|
+
const kids = children(cv) || [];
|
|
309
|
+
const otherKids = children(e) || [];
|
|
310
|
+
for (let i = 0; i < kids.length; i++) {
|
|
311
|
+
deepCompare(otherKids[i], kids[i], [...path, `[${i}]${tag(kids[i] as Vode)?.toUpperCase() || "#text"}`]);
|
|
312
|
+
}
|
|
313
|
+
if (kids.length !== otherKids.length) {
|
|
314
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\n${kids.length} children\n\nbut [${tag(e)?.toUpperCase()}] has ${otherKids.length} children${failSuffix}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// mismatch between text and element (fake or real)
|
|
319
|
+
else if (typeof cv === "string" && (e instanceof FakeElement || isRealElement(e))) {
|
|
320
|
+
const tagName = e instanceof FakeElement ? e.tagName : (e as HTMLElement).tagName;
|
|
321
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node\n\nbut got <${tagName.toUpperCase()}>${failSuffix}`);
|
|
322
|
+
}
|
|
323
|
+
// mismatch between text and vode
|
|
324
|
+
else if (typeof cv === "string" && Array.isArray(e)) {
|
|
325
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node\n\nbut got [${tag(e)?.toUpperCase()}]${failSuffix}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// mismatch between vode and text node (fake or real)
|
|
329
|
+
else if (Array.isArray(cv) && (e instanceof FakeTextNode || isRealTextNode(e))) {
|
|
330
|
+
const text = e instanceof FakeTextNode ? e.wholeText : (e as Text).wholeText;
|
|
331
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nbut got #text (${text})${failSuffix}`);
|
|
332
|
+
}
|
|
333
|
+
// mismatch between vode and text
|
|
334
|
+
else if (Array.isArray(cv) && typeof e === "string") {
|
|
335
|
+
throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nbut got #text (${e})${failSuffix}`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
deepCompare(this.what, v, [`${tag(v as Vode)?.toUpperCase() || "#text"}`]);
|
|
342
|
+
} else {
|
|
343
|
+
throw new ExpectationError(this, `expected an element or text node\n\nbut it is a ${typeof this.what}\n${this.what}${failSuffix}`);
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
waitTimeMs
|
|
347
|
+
);
|
|
157
348
|
}
|
|
158
349
|
};
|
|
159
350
|
|
|
@@ -165,4 +356,4 @@ export class ExpectationError extends Error {
|
|
|
165
356
|
|
|
166
357
|
export function expect(what: any) {
|
|
167
358
|
return new Expectation(what);
|
|
168
|
-
}
|
|
359
|
+
}
|
package/test/index.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { resetMocks } from "./mocks";
|
|
2
|
-
import { ExpectationError } from "./helper";
|
|
3
|
-
|
|
4
|
-
//=== REGISTERED TESTS =========================================
|
|
5
1
|
import vodeTests from "./tests-vode";
|
|
6
2
|
import appTests from "./tests-app";
|
|
7
3
|
import defuseTests from "./tests-defuse";
|
|
@@ -17,8 +13,12 @@ import mergeStyleTests from "./tests-mergeStyle";
|
|
|
17
13
|
import mergePropsTests from "./tests-mergeProps";
|
|
18
14
|
import stateContextTests from "./tests-state-context";
|
|
19
15
|
import mountUnmountTests from "./tests-mount-unmount";
|
|
16
|
+
import exampleTests from "./tests-examples";
|
|
17
|
+
import catchTests from "./tests-catch";
|
|
18
|
+
import patchAdvancedTests from "./tests-patch-advanced";
|
|
19
|
+
import patchMergeTests from "./tests-patch-merge";
|
|
20
20
|
|
|
21
|
-
const tests = {
|
|
21
|
+
export const tests = {
|
|
22
22
|
...vodeTests,
|
|
23
23
|
...appTests,
|
|
24
24
|
...defuseTests,
|
|
@@ -38,45 +38,8 @@ const tests = {
|
|
|
38
38
|
...mergePropsTests,
|
|
39
39
|
|
|
40
40
|
...stateContextTests,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
passed: 0,
|
|
47
|
-
failed: <string[]>[],
|
|
48
|
-
}
|
|
49
|
-
const line = "----------------------------------";
|
|
50
|
-
|
|
51
|
-
for (const test of Object.entries(tests)) {
|
|
52
|
-
count.total++;
|
|
53
|
-
resetMocks();
|
|
54
|
-
try {
|
|
55
|
-
test[1]()
|
|
56
|
-
count.passed++;
|
|
57
|
-
console.log(`#${count.total} ${test[0]}\n-> 🟢 passed\n${line}`);
|
|
58
|
-
} catch (err: any) {
|
|
59
|
-
console.error(`#${count.total} ${test[0]}\n-> 🔴 failed`);
|
|
60
|
-
if (err instanceof ExpectationError) {
|
|
61
|
-
count.failed.push(`#${count.total} ${test[0]}\n-> 🔴 failed:\n${err.message}\n${line}`);
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
count.failed.push(`#${count.total} ${test[0]}\n-> 🔴 failed:\n${err.message}\n${err.stack}\n${line}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
console.log(`
|
|
70
|
-
total: ${count.total}
|
|
71
|
-
passed: ${count.passed}
|
|
72
|
-
failed: ${count.failed.length}
|
|
73
|
-
`);
|
|
74
|
-
|
|
75
|
-
if (count.passed === count.total) {
|
|
76
|
-
console.log("\n\nall tests passed\n");
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
console.error(`${line.replaceAll("-", "=")}\nError summary:\n\n${count.failed.join(`\n${line}\n`)}`);
|
|
80
|
-
|
|
81
|
-
throw "\n\nsome tests failed (see output)\n";
|
|
82
|
-
}
|
|
41
|
+
...exampleTests,
|
|
42
|
+
...catchTests,
|
|
43
|
+
...patchAdvancedTests,
|
|
44
|
+
...patchMergeTests,
|
|
45
|
+
};
|