@ryupold/vode 1.8.8 → 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/test/helper.ts CHANGED
@@ -1,205 +1,350 @@
1
1
  import { children, ChildVode, PatchableState, props, tag, Vode } from "../src/vode";
2
2
  import { FakeElement, FakeTextNode } from "./mocks";
3
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
+ }
53
+
4
54
  export class Expectation {
5
55
  constructor(public readonly what: any) { }
6
56
 
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
+
7
63
  toBeA(type: "undefined" | "object" | "function" | "bigint" | "boolean" | "number" | "string" | "symbol", failMessage?: string) {
8
64
  if (typeof this.what !== type) {
9
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
- toEqual(other: any, failMessage?: string) {
14
- const failSuffix = failMessage ? `\n\n${failMessage}` : "";
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
+ }
15
74
 
16
- function deepCompare(a: any, b: any, path: string[]): string[] | null {
17
- if (typeof a !== typeof b) {
18
- if (path.length === 0) path.push(``);
19
- path[path.length - 1] += ` (type: ${typeof a} != ${typeof b})`;
20
- return path;
21
- }
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
+ }
22
80
 
23
- if (typeof a !== "object" || a === null) {
24
- if (path.length === 0) path.push(``);
25
- path[path.length - 1] += ` (value: ${a} != ${b})`;
26
- return a !== b ? path : null;
27
- }
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
+ }
28
86
 
29
- for (const prop of Object.entries(a)) {
30
- const [k, v] = prop;
31
- const result = deepCompare(v, b[k], [...path, k]);
32
- if (result) {
33
- return result;
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}` : "";
97
+
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;
34
128
  }
35
- }
36
129
 
37
- for (const prop of Object.entries(b)) {
38
- const [k, v] = prop;
39
- const result = deepCompare(a[k], v, [...path, k]);
40
- if (result) {
41
- return result;
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
+ }
42
135
  }
43
- }
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
+ }
44
145
 
45
- return null;
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}`);
46
149
  }
150
+ return this.what();
151
+ }
47
152
 
48
- if (typeof this.what === "object" && typeof other === "object" && this.what !== null && other !== null) {
49
- const unequal = deepCompare(this.what, other, []);
50
- if (unequal) {
51
- 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}`);
52
- }
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}`);
53
156
  }
54
- else {
55
- if (this.what !== other) {
56
- throw new ExpectationError(this, `expected (${typeof this.what})\n\n${this.what}\n\nto equal (${typeof other})\n\n${other}${failSuffix}`);
57
- }
157
+
158
+ let r: any;
159
+ try {
160
+ r = this.what();
161
+ } catch (err: any) {
162
+ return err;
58
163
  }
164
+ throw new ExpectationError(this, `expected function to fail\n\nbut it succeeded with a result of type ${typeof r}\n\n${r}`);
59
165
  }
60
166
 
61
- toSucceed<Result>(...args: any): Result {
167
+ toSucceedAsync<Result>(waitTime: number = 100): Promise<Result> {
62
168
  if (typeof this.what !== "function") {
63
169
  throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}`);
64
170
  }
65
- return this.what(...args);
171
+ return retry<Result>(() => this.what(), waitTime);
66
172
  }
67
173
 
68
- toFail(...args: any): Error {
174
+ async toFailAsync(): Promise<Error> {
69
175
  if (typeof this.what !== "function") {
70
176
  throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}`);
71
177
  }
72
178
 
73
179
  let r: any;
74
180
  try {
75
- r = this.what(...args);
181
+ r = await this.what();
76
182
  } catch (err: any) {
77
183
  return err;
78
184
  }
79
185
  throw new ExpectationError(this, `expected function to fail\n\nbut it succeeded with a result of type ${typeof r}\n\n${r}`);
80
186
  }
81
187
 
82
- toMatch(v: ChildVode, state?: PatchableState | null, failMessage?: string) {
83
- const failSuffix = failMessage ? `\n\n${failMessage}` : "";
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
- if (this.what instanceof FakeElement || this.what instanceof FakeTextNode || typeof this.what === "string" || Array.isArray(this.what) || typeof this.what === "function") {
86
- const that = this;
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;
87
202
 
88
- function deepCompare(e: FakeElement | FakeTextNode | ChildVode, cv: ChildVode, path: string[]): string[] | null {
203
+ function deepCompare(e: FakeElement | FakeTextNode | HTMLElement | Text | ChildVode, cv: ChildVode, path: string[]): string[] | null {
89
204
 
90
- // unwrap component
91
- while (typeof cv === "function") {
92
- if (!state) {
93
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na Component\n\nbut got no state passed in [toMatch]${failSuffix}`);
94
- }
95
- cv = cv(state);
96
- }
97
- while (typeof e === "function") {
98
- if (!state) {
99
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na Component\n\nbut got no state passed in [toMatch]${failSuffix}`);
100
- }
101
- e = e(state);
102
- }
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
+ }
103
218
 
104
- // string matches TextNode
105
- if (typeof cv === "string" && e instanceof FakeTextNode) {
106
- if (cv !== e.wholeText) {
107
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node with\n${cv}\n\nbut text was\n${e.wholeText}${failSuffix}`);
108
- }
109
- }
110
- // string matches string
111
- else if (typeof cv === "string" && typeof e === "string") {
112
- if (cv !== e) {
113
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node with\n${cv}\n\nbut text was\n${e}${failSuffix}`);
114
- }
115
- }
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
+ }
116
232
 
117
- // vode matches element
118
- else if (Array.isArray(cv) && e instanceof FakeElement) {
119
- if (tag(cv)?.toUpperCase() !== e.tagName.toUpperCase()) {
120
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nbut got <${e.tagName.toUpperCase()}>${failSuffix}`);
121
- }
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
+ }
122
239
 
123
- // compare attributes/props
124
- const properties = props(cv);
125
- if (properties) {
126
- for (const [k, v] of Object.entries(properties)) {
127
- const attributeValue = e.fakeAttributes[k];
128
- if (!attributeValue) {
129
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nwith attribute [${k}="${v}"]\n\nbut it was not found${failSuffix}`);
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
+ }
130
257
  }
131
- if (attributeValue !== v) {
132
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nwith attribute [${k}="${v}"]\n\nbut it was [${k}="${attributeValue}"]${failSuffix}`);
258
+
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
+ }
133
284
  }
134
285
  }
135
- }
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
+ }
136
291
 
137
- // compare children
138
- const kids = children(cv) || [];
139
- for (let i = 0; i < kids.length; i++) {
140
- deepCompare(e.children.item(i) as any, kids[i], [...path, `[${i}]${tag(kids[i] as Vode)?.toUpperCase() || "#text"}`]);
141
- }
142
- if (kids.length !== e.children.length) {
143
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\n${kids.length} children\n\nbut <${e.tagName.toUpperCase()}> has ${e.children.length} children${failSuffix}`);
144
- }
145
- }
146
- // vode matches vode
147
- else if (Array.isArray(cv) && Array.isArray(e)) {
148
- if (tag(cv)?.toUpperCase() !== tag(e)?.toUpperCase()) {
149
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na vode [${tag(cv)?.toUpperCase()}]\n\nbut got [${tag(e)?.toUpperCase()}]${failSuffix}`);
150
- }
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
+ }
151
306
 
152
- // compare attributes/props
153
- const properties = props(cv);
154
- const otherProperties = props(e) || {};
155
- if (properties) {
156
- for (const [k, v] of Object.entries(properties)) {
157
- const attributeValue = otherProperties[k];
158
- if (!attributeValue) {
159
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na vode [${tag(cv)?.toUpperCase()}]\n\nwith attribute [${k}="${v}"]\n\nbut it was not found${failSuffix}`);
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"}`]);
160
312
  }
161
- if (attributeValue !== v) {
162
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na vode [${tag(cv)?.toUpperCase()}]\n\nwith attribute [${k}="${v}"]\n\nbut its value was [${k}="${attributeValue}"]${failSuffix}`);
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}`);
163
315
  }
164
316
  }
165
- }
166
-
167
- // compare children
168
- const kids = children(cv) || [];
169
- const otherKids = children(e) || [];
170
- for (let i = 0; i < kids.length; i++) {
171
- deepCompare(otherKids[i], kids[i], [...path, `[${i}]${tag(kids[i] as Vode)?.toUpperCase() || "#text"}`]);
172
- }
173
- if (kids.length !== otherKids.length) {
174
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\n${kids.length} children\n\nbut [${tag(e)?.toUpperCase()}] has ${otherKids.length} children${failSuffix}`);
175
- }
176
- }
177
317
 
178
- // mismatch between text and element
179
- else if (typeof cv === "string" && e instanceof FakeElement) {
180
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node\n\nbut got <${e.tagName.toUpperCase()}>${failSuffix}`);
181
- }
182
- // mismatch between text and vode
183
- else if (typeof cv === "string" && Array.isArray(e)) {
184
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node\n\nbut got [${tag(e)?.toUpperCase()}]${failSuffix}`);
185
- }
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
+ }
186
327
 
187
- // mismatch between vode and text node
188
- else if (Array.isArray(cv) && e instanceof FakeTextNode) {
189
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nbut got #text (${e.wholeText})${failSuffix}`);
190
- }
191
- // mismatch between vode and text
192
- else if (Array.isArray(cv) && typeof e === "string") {
193
- throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nbut got #text (${e})${failSuffix}`);
194
- }
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
+ }
195
337
 
196
- return null;
197
- }
338
+ return null;
339
+ }
198
340
 
199
- deepCompare(this.what, v, [`${tag(v as Vode)?.toUpperCase() || "#text"}`]);
200
- } else {
201
- throw new ExpectationError(this, `expected an element or text node\n\nbut it is a ${typeof this.what}\n${this.what}${failSuffix}`);
202
- }
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
+ );
203
348
  }
204
349
  };
205
350
 
@@ -211,4 +356,4 @@ export class ExpectationError extends Error {
211
356
 
212
357
  export function expect(what: any) {
213
358
  return new Expectation(what);
214
- }
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";
@@ -22,7 +18,7 @@ import catchTests from "./tests-catch";
22
18
  import patchAdvancedTests from "./tests-patch-advanced";
23
19
  import patchMergeTests from "./tests-patch-merge";
24
20
 
25
- const tests = {
21
+ export const tests = {
26
22
  ...vodeTests,
27
23
  ...appTests,
28
24
  ...defuseTests,
@@ -46,62 +42,4 @@ const tests = {
46
42
  ...catchTests,
47
43
  ...patchAdvancedTests,
48
44
  ...patchMergeTests,
49
- };
50
- //===================================================
51
-
52
- const count = {
53
- total: 0,
54
- passed: 0,
55
- failed: <string[]>[],
56
- }
57
- const line = "----------------------------------";
58
-
59
- async function runTest(test: [string, () => any]) {
60
- count.total++;
61
- resetMocks();
62
- const start = performance.now();
63
- try {
64
- const result = test[1]();
65
- if (result && typeof (result as any)?.then === "function") {
66
- await result;
67
- }
68
- count.passed++;
69
- const time = (performance.now() - start).toFixed(3) + " ms";
70
- console.log(`#${count.total} ${test[0]}\n-> 🟢 passed ${time}\n${line}`);
71
- } catch (err: any) {
72
- const time = (performance.now() - start).toFixed(3) + " ms";
73
- console.error(`#${count.total} ${test[0]}\n-> 🔴 failed ${time}`);
74
- if (err instanceof ExpectationError) {
75
- count.failed.push(`#${count.total} ${test[0]}\n-> 🔴 failed:\n${err.message}\n${line}`);
76
- }
77
- else {
78
- count.failed.push(`#${count.total} ${test[0]}\n-> 🔴 failed:\n${err.message}\n${err.stack}\n${line}`);
79
- }
80
- }
81
- }
82
-
83
- const sw = performance.now();
84
- (async () => {
85
- for (const test of Object.entries(tests)) {
86
- await runTest(test);
87
- }
88
-
89
- const time = (performance.now() - sw).toFixed(3) + " ms";
90
-
91
- console.log(`
92
- total: ${count.total}
93
- passed: ${count.passed}
94
- failed: ${count.failed.length}
95
-
96
- time: ${time}
97
- `);
98
-
99
- if (count.passed === count.total) {
100
- console.log("\n\nall tests passed\n");
101
- }
102
- else {
103
- console.error(`${line.replaceAll("-", "=")}\nError summary:\n\n${count.failed.join(`\n${line}\n`)}`);
104
-
105
- throw "\n\nsome tests failed (see output)\n";
106
- }
107
- })();
45
+ };