@ryupold/vode 1.8.8 → 1.8.11

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,358 @@
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>(failMessage?: string): Result {
147
+ const failSuffix = failMessage ? `\n\n${failMessage}` : "";
148
+ if (typeof this.what !== "function") {
149
+ throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}${failSuffix}`);
46
150
  }
151
+ return this.what();
152
+ }
47
153
 
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
- }
154
+ toFail(failMessage?: string): Error {
155
+ const failSuffix = failMessage ? `\n\n${failMessage}` : "";
156
+ if (typeof this.what !== "function") {
157
+ throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}${failSuffix}`);
53
158
  }
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
- }
159
+
160
+ let r: any;
161
+ try {
162
+ r = this.what();
163
+ } catch (err: any) {
164
+ return err;
58
165
  }
166
+ throw new ExpectationError(this, `expected function to fail\n\nbut it succeeded with a result of type ${typeof r}\n\n${r}${failSuffix}`);
59
167
  }
60
168
 
61
- toSucceed<Result>(...args: any): Result {
169
+ toSucceedAsync<Result>(failMessage?: string, waitTime: number = 100): Promise<Result> {
170
+ const failSuffix = failMessage ? `\n\n${failMessage}` : "";
62
171
  if (typeof this.what !== "function") {
63
- throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}`);
172
+ throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}${failSuffix}`);
64
173
  }
65
- return this.what(...args);
174
+ return retry<Result>(() => this.what(), waitTime);
66
175
  }
67
176
 
68
- toFail(...args: any): Error {
177
+ async toFailAsync(failMessage?: string): Promise<Error> {
178
+ const failSuffix = failMessage ? `\n\n${failMessage}` : "";
179
+
69
180
  if (typeof this.what !== "function") {
70
- throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}`);
181
+ throw new ExpectationError(this, `expected a function\n\nbut it is a ${typeof this.what}${failSuffix}`);
71
182
  }
72
183
 
73
184
  let r: any;
74
185
  try {
75
- r = this.what(...args);
186
+ if(typeof this.what === "function")
187
+ r = await this.what();
188
+ else
189
+ r = await this.what;
76
190
  } catch (err: any) {
77
191
  return err;
78
192
  }
79
- throw new ExpectationError(this, `expected function to fail\n\nbut it succeeded with a result of type ${typeof r}\n\n${r}`);
193
+ throw new ExpectationError(this, `expected function to fail\n\nbut it succeeded with a result of type ${typeof r}\n\n${r}${failSuffix}`);
80
194
  }
81
195
 
82
- toMatch(v: ChildVode, state?: PatchableState | null, failMessage?: string) {
83
- const failSuffix = failMessage ? `\n\n${failMessage}` : "";
196
+ async toMatch(v: ChildVode,
197
+ state?: PatchableState | null,
198
+ failMessage?: string,
199
+ waitTimeMs: number = 100
200
+ ) {
201
+ return await retry(
202
+ async () => {
203
+ const failSuffix = failMessage ? `\n\n${failMessage}` : "";
84
204
 
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;
205
+ // Support FakeElement, FakeTextNode, real HTMLElement, real Text nodes, strings, arrays, functions
206
+ if (this.what instanceof FakeElement || this.what instanceof FakeTextNode ||
207
+ isRealElement(this.what) || isRealTextNode(this.what) ||
208
+ typeof this.what === "string" || Array.isArray(this.what) || typeof this.what === "function") {
209
+ const that = this;
87
210
 
88
- function deepCompare(e: FakeElement | FakeTextNode | ChildVode, cv: ChildVode, path: string[]): string[] | null {
211
+ function deepCompare(e: FakeElement | FakeTextNode | HTMLElement | Text | ChildVode, cv: ChildVode, path: string[]): string[] | null {
89
212
 
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
- }
213
+ // unwrap component
214
+ while (typeof cv === "function") {
215
+ if (!state) {
216
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na Component\n\nbut got no state passed in [toMatch]${failSuffix}`);
217
+ }
218
+ cv = cv(state);
219
+ }
220
+ while (typeof e === "function") {
221
+ if (!state) {
222
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na Component\n\nbut got no state passed in [toMatch]${failSuffix}`);
223
+ }
224
+ e = e(state);
225
+ }
103
226
 
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
- }
227
+ // string matches TextNode (fake or real)
228
+ if (typeof cv === "string" && (e instanceof FakeTextNode || isRealTextNode(e))) {
229
+ const text = e instanceof FakeTextNode ? e.wholeText : (e as Text).wholeText;
230
+ if (cv !== text) {
231
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node with\n${cv}\n\nbut text was\n${text}${failSuffix}`);
232
+ }
233
+ }
234
+ // string matches string
235
+ else if (typeof cv === "string" && typeof e === "string") {
236
+ if (cv !== e) {
237
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node with\n${cv}\n\nbut text was\n${e}${failSuffix}`);
238
+ }
239
+ }
116
240
 
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
- }
241
+ // vode matches element (fake or real)
242
+ else if (Array.isArray(cv) && (e instanceof FakeElement || isRealElement(e))) {
243
+ const tagName = e instanceof FakeElement ? e.tagName : (e as HTMLElement).tagName;
244
+ if (tag(cv)?.toUpperCase() !== tagName.toUpperCase()) {
245
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nbut got <${tagName.toUpperCase()}>${failSuffix}`);
246
+ }
122
247
 
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}`);
248
+ // compare attributes/props
249
+ const properties = props(cv);
250
+ if (properties) {
251
+ for (const [k, val] of Object.entries(properties)) {
252
+ let attributeValue: string | null;
253
+ if (e instanceof FakeElement) {
254
+ attributeValue = e.fakeAttributes[k] ?? null;
255
+ } else {
256
+ attributeValue = (e as HTMLElement).getAttribute(k);
257
+ }
258
+ if (!attributeValue) {
259
+ 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}`);
260
+ }
261
+ if (attributeValue !== val) {
262
+ 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}`);
263
+ }
264
+ }
130
265
  }
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}`);
266
+
267
+ // compare children - handle browser text node normalization
268
+ const kids = children(cv) || [];
269
+ const childNodes = e instanceof FakeElement ? e.children : (e as HTMLElement).childNodes;
270
+
271
+ // Check if all kids are text (strings) - browsers may merge these
272
+ const allKidsAreText = kids.every(k => typeof k === "string");
273
+ if (allKidsAreText && isBrowser && kids.length > 1) {
274
+ // Browser likely merged text nodes - compare concatenated text
275
+ const expectedText = kids.join("");
276
+ const actualText = (e as HTMLElement).textContent || "";
277
+ if (expectedText !== actualText) {
278
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\ntext content "${expectedText}"\n\nbut got "${actualText}"${failSuffix}`);
279
+ }
280
+ } else {
281
+ // Normal child-by-child comparison
282
+ for (let i = 0; i < kids.length; i++) {
283
+ const childNode = e instanceof FakeElement
284
+ ? childNodes.item(i) as any
285
+ : childNodes.item(i) as any;
286
+ deepCompare(childNode, kids[i], [...path, `[${i}]${tag(kids[i] as Vode)?.toUpperCase() || "#text"}`]);
287
+ }
288
+ const childCount = e instanceof FakeElement ? e.children.length : (e as HTMLElement).childNodes.length;
289
+ if (kids.length !== childCount) {
290
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\n${kids.length} children\n\nbut <${tagName.toUpperCase()}> has ${childCount} children${failSuffix}`);
291
+ }
133
292
  }
134
293
  }
135
- }
294
+ // vode matches vode
295
+ else if (Array.isArray(cv) && Array.isArray(e)) {
296
+ if (tag(cv)?.toUpperCase() !== tag(e)?.toUpperCase()) {
297
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na vode [${tag(cv)?.toUpperCase()}]\n\nbut got [${tag(e)?.toUpperCase()}]${failSuffix}`);
298
+ }
136
299
 
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
- }
300
+ // compare attributes/props
301
+ const properties = props(cv);
302
+ const otherProperties = props(e) || {};
303
+ if (properties) {
304
+ for (const [k, val] of Object.entries(properties)) {
305
+ const attributeValue = otherProperties[k];
306
+ if (!attributeValue) {
307
+ 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}`);
308
+ }
309
+ if (attributeValue !== val) {
310
+ 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}`);
311
+ }
312
+ }
313
+ }
151
314
 
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}`);
315
+ // compare children
316
+ const kids = children(cv) || [];
317
+ const otherKids = children(e) || [];
318
+ for (let i = 0; i < kids.length; i++) {
319
+ deepCompare(otherKids[i], kids[i], [...path, `[${i}]${tag(kids[i] as Vode)?.toUpperCase() || "#text"}`]);
160
320
  }
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}`);
321
+ if (kids.length !== otherKids.length) {
322
+ 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
323
  }
164
324
  }
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
325
 
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
- }
326
+ // mismatch between text and element (fake or real)
327
+ else if (typeof cv === "string" && (e instanceof FakeElement || isRealElement(e))) {
328
+ const tagName = e instanceof FakeElement ? e.tagName : (e as HTMLElement).tagName;
329
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node\n\nbut got <${tagName.toUpperCase()}>${failSuffix}`);
330
+ }
331
+ // mismatch between text and vode
332
+ else if (typeof cv === "string" && Array.isArray(e)) {
333
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\na text node\n\nbut got [${tag(e)?.toUpperCase()}]${failSuffix}`);
334
+ }
186
335
 
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
- }
336
+ // mismatch between vode and text node (fake or real)
337
+ else if (Array.isArray(cv) && (e instanceof FakeTextNode || isRealTextNode(e))) {
338
+ const text = e instanceof FakeTextNode ? e.wholeText : (e as Text).wholeText;
339
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nbut got #text (${text})${failSuffix}`);
340
+ }
341
+ // mismatch between vode and text
342
+ else if (Array.isArray(cv) && typeof e === "string") {
343
+ throw new ExpectationError(that, `expected at\n${path.join(" > ")}\n\nan element <${tag(cv)?.toUpperCase()}>\n\nbut got #text (${e})${failSuffix}`);
344
+ }
195
345
 
196
- return null;
197
- }
346
+ return null;
347
+ }
198
348
 
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
- }
349
+ deepCompare(this.what, v, [`${tag(v as Vode)?.toUpperCase() || "#text"}`]);
350
+ } else {
351
+ throw new ExpectationError(this, `expected an element or text node\n\nbut it is a ${typeof this.what}\n${this.what}${failSuffix}`);
352
+ }
353
+ },
354
+ waitTimeMs
355
+ );
203
356
  }
204
357
  };
205
358
 
@@ -211,4 +364,4 @@ export class ExpectationError extends Error {
211
364
 
212
365
  export function expect(what: any) {
213
366
  return new Expectation(what);
214
- }
367
+ }
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
+ };