@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/test/mocks.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { globals } from "../src/vode";
2
+
1
3
  const NodeConstants = {
2
4
  ELEMENT_NODE: 1,
3
5
  ATTRIBUTE_NODE: 2,
@@ -13,88 +15,208 @@ const NodeConstants = {
13
15
  NOTATION_NODE: 12,
14
16
  };
15
17
 
16
- export class MockElement {
18
+ class FakeNodeList implements NodeListOf<ChildNode> {
19
+ [index: number]: ChildNode;
20
+ public readonly data: ChildNode[] = [];
21
+
22
+ constructor() {
23
+ let self = this;
24
+
25
+ return new Proxy(this, {
26
+ get(target, prop) {
27
+ const key: string = typeof prop === "symbol" ? String(prop) : prop;
28
+
29
+ if (<any>Number(key) == key && !(prop in target)) {
30
+ return self.data[parseInt(key)];
31
+ }
32
+ return target[prop as any];
33
+ }
34
+ });
35
+ }
36
+
37
+
38
+ item(index: number): ChildNode {
39
+ return this.data[index] ?? null;
40
+ }
41
+ forEach(callbackfn: (value: ChildNode, key: number, parent: NodeListOf<ChildNode>) => void, thisArg?: any): void {
42
+ for (let i = 0; i < this.length; i++) {
43
+ callbackfn.bind(thisArg)(this.data[i], i, this);
44
+ }
45
+ }
46
+ entries(): ArrayIterator<[number, ChildNode]> {
47
+ return new Array(this.length).fill(0).map((_, i) => [i, this[i]] as [number, ChildNode])[Symbol.iterator]();
48
+ }
49
+ keys(): ArrayIterator<number> {
50
+ return new Array(this.data.length).fill(0).map((_, i) => i)[Symbol.iterator]();
51
+ }
52
+ values(): ArrayIterator<ChildNode> {
53
+ return new Array(this.data.length).fill(0).map((_, i) => this[i])[Symbol.iterator]();
54
+ }
55
+ [Symbol.iterator](): ArrayIterator<ChildNode> {
56
+ return new Array(this.data.length).fill(0).map((_, i) => this[i])[Symbol.iterator]();
57
+ }
58
+ get length() {
59
+ return this.data.length;
60
+ }
61
+ }
62
+
63
+ export class FakeElement {
64
+ public fakeAttributes: Record<string, string> = {};
65
+
17
66
  nodeType = NodeConstants.ELEMENT_NODE;
18
- childNodes: (MockElement | MockText)[] = [];
19
- children: (MockElement | MockText)[] = [];
20
- parentElement: MockElement | null = null;
21
- get attributes() {
22
- return Object.entries(this._attrs).map(([name, value]) => ({ name, value }));
67
+ parentElement: HTMLElement | null = null;
68
+ childNodes: NodeListOf<ChildNode> = new FakeNodeList();
69
+ get children(): HTMLCollection {
70
+ return this.childNodes as unknown as HTMLCollection;
23
71
  }
24
72
  style: { cssText: string } = { cssText: "" };
25
- tagName = "UNKNOWN";
26
- private _attrs: Record<string, string> = {};
73
+
74
+ readonly tagName: string;
27
75
 
28
76
  constructor(public tag?: string) {
29
- if (tag) this.tagName = tag.toUpperCase();
77
+ this.tagName = tag?.toUpperCase() || "???";
30
78
  }
31
79
 
32
80
  get firstChild() { return this.childNodes[0] ?? null; }
33
81
  get lastChild() { return this.childNodes[this.childNodes.length - 1] ?? null; }
34
82
  get nextSibling() { return null; }
83
+ get attributes() {
84
+ return Object.entries(this.fakeAttributes).map(([name, value]) => ({ name, value })) as any;
85
+ }
35
86
 
36
- hasAttributes() { return Object.keys(this._attrs).length > 0; }
87
+ hasAttributes() { return Object.keys(this.fakeAttributes).length > 0; }
37
88
  hasChildNodes() { return this.childNodes.length > 0; }
38
- setAttribute(name: string, value: string) { this._attrs[name] = value; }
39
- removeAttribute(name: string) { delete this._attrs[name]; }
40
- appendChild(child: MockElement | MockText) {
41
- this.childNodes.push(child); this.children.push(child); if (child.parentElement !== undefined) child.parentElement = this; return child;
89
+ setAttribute(name: string, value: string) { this.fakeAttributes[name] = value; }
90
+ removeAttribute(name: string) { delete this.fakeAttributes[name]; }
91
+
92
+ appendChild(child: FakeElement | FakeTextNode): FakeElement | FakeTextNode {
93
+ (this.childNodes as FakeNodeList).data.push(child as any);
94
+ (child as any).parentElement = this;
95
+ return child;
42
96
  }
43
97
  remove() {
44
- if (this.parentElement) { const i = this.parentElement.childNodes.indexOf(this); if (i >= 0) this.parentElement.childNodes.splice(i, 1); }
98
+ if (this.parentElement) {
99
+ const i = (this.parentElement.childNodes as FakeNodeList).data.indexOf(this as any);
100
+ if (i >= 0)
101
+ (this.parentElement.childNodes as FakeNodeList).data.splice(i, 1);
102
+ }
45
103
  }
46
- replaceWith(...nodes: (MockElement | MockText)[]) {
104
+ replaceWith(...nodes: (FakeElement | FakeTextNode)[]) {
47
105
  const parent = this.parentElement;
48
106
  if (parent) {
49
- const i = parent.childNodes.indexOf(this);
50
- if (i >= 0) { parent.childNodes.splice(i, 1, ...nodes); }
51
- for (const n of nodes) n.parentElement = parent;
107
+ const i = (<FakeNodeList>parent.childNodes).data.indexOf(this as any);
108
+ if (i >= 0) {
109
+ (<FakeNodeList>parent.childNodes).data.splice(i, 1, ...nodes as any);
110
+ }
111
+ for (const n of nodes) {
112
+ n.parentElement = parent;
113
+ }
52
114
  }
53
115
  }
54
- before(...nodes: (MockElement | MockText)[]) {
116
+ before(...nodes: (FakeElement | FakeTextNode)[]) {
55
117
  const parent = this.parentElement;
56
118
  if (parent) {
57
- const i = parent.childNodes.indexOf(this);
58
- if (i >= 0) { parent.childNodes.splice(i, 0, ...nodes); }
59
- for (const n of nodes) n.parentElement = parent;
119
+ const i = (<FakeNodeList>parent.childNodes).data.indexOf(this as any);
120
+ if (i >= 0) {
121
+ for (const n of nodes) {
122
+ if (n === this) continue;
123
+ if (n.parentElement) {
124
+ const ni = (<FakeNodeList>n.parentElement.childNodes).data.indexOf(n as any);
125
+ if (ni >= 0) (<FakeNodeList>n.parentElement.childNodes).data.splice(ni, 1);
126
+ }
127
+ }
128
+ const filtered = nodes.filter(n => n !== this);
129
+ (<FakeNodeList>parent.childNodes).data.splice(i, 0, ...filtered as any);
130
+ for (const n of filtered) {
131
+ n.parentElement = parent;
132
+ }
133
+ }
60
134
  }
61
135
  }
62
- get [Symbol.iterator]() { return Array.prototype[Symbol.iterator].bind(this.children); }
136
+ get [Symbol.iterator]() {
137
+ return Array.prototype[Symbol.iterator].bind(this.children);
138
+ }
63
139
  }
64
140
 
65
- export class MockText {
141
+ export class FakeTextNode {
66
142
  nodeType = NodeConstants.TEXT_NODE;
67
- parentElement: MockElement | null = null;
143
+ parentElement: HTMLElement | null = null;
68
144
  constructor(public nodeValue: string) { }
69
145
  get wholeText() { return this.nodeValue; }
70
- remove() { if (this.parentElement) { const i = this.parentElement.childNodes.indexOf(this); if (i >= 0) this.parentElement.childNodes.splice(i, 1); } }
71
- replaceWith(...nodes: (MockElement | MockText)[]) {
146
+ remove() {
147
+ if (this.parentElement) {
148
+ const i = (<FakeNodeList>this.parentElement.childNodes).data.indexOf(this as any);
149
+ if (i >= 0)
150
+ (<FakeNodeList>this.parentElement.childNodes).data.splice(i, 1);
151
+ }
152
+ }
153
+ replaceWith(...nodes: (FakeElement | FakeTextNode)[]) {
72
154
  const parent = this.parentElement;
73
155
  if (parent) {
74
- const i = parent.childNodes.indexOf(this);
75
- if (i >= 0) { parent.childNodes.splice(i, 1, ...nodes); }
76
- for (const n of nodes) n.parentElement = parent;
156
+ const i = (<FakeNodeList>parent.childNodes).data.indexOf(this as any);
157
+ if (i >= 0) { (<FakeNodeList>parent.childNodes).data.splice(i, 1, ...nodes as any); }
158
+ for (const n of nodes) {
159
+ n.parentElement = parent;
160
+ }
77
161
  }
78
162
  }
79
- before(...nodes: (MockElement | MockText)[]) {
163
+ before(...nodes: (FakeElement | FakeTextNode)[]) {
80
164
  const parent = this.parentElement;
81
165
  if (parent) {
82
- const i = parent.childNodes.indexOf(this);
83
- if (i >= 0) { parent.childNodes.splice(i, 0, ...nodes); }
84
- for (const n of nodes) n.parentElement = parent;
166
+ const i = (<FakeNodeList>parent.childNodes).data.indexOf(this as any);
167
+ if (i >= 0) {
168
+ for (const n of nodes) {
169
+ if (n === this) continue;
170
+ if (n.parentElement) {
171
+ const ni = (<FakeNodeList>n.parentElement.childNodes).data.indexOf(n as any);
172
+ if (ni >= 0) (<FakeNodeList>n.parentElement.childNodes).data.splice(ni, 1);
173
+ }
174
+ }
175
+ const filtered = nodes.filter(n => n !== this);
176
+ (<FakeNodeList>parent.childNodes).data.splice(i, 0, ...filtered as any);
177
+ for (const n of filtered) {
178
+ n.parentElement = parent;
179
+ }
180
+ }
85
181
  }
86
182
  }
87
183
  }
88
184
 
89
185
  export function resetMocks() {
186
+ let hidden = false;
187
+ let rafHandle = 0;
188
+ const rafQueue = new Map<number, FrameRequestCallback>();
189
+ let rafTimer: ReturnType<typeof setTimeout> | null = null;
190
+
191
+ function scheduleNextFrame() {
192
+ if (rafTimer !== null || hidden || rafQueue.size === 0) {
193
+ return;
194
+ }
195
+
196
+ rafTimer = setTimeout(() => {
197
+ rafTimer = null;
198
+
199
+ if (hidden || rafQueue.size === 0) {
200
+ scheduleNextFrame();
201
+ return;
202
+ }
203
+
204
+ const now = performance.now();
205
+ const callbacks = Array.from(rafQueue.values());
206
+ rafQueue.clear();
207
+
208
+ for (const cb of callbacks) {
209
+ cb(now);
210
+ }
211
+
212
+ scheduleNextFrame();
213
+ }, 16);
214
+ }
215
+
90
216
  const mockDoc: any = {
91
- createElement: (tag: string) => new MockElement(tag),
92
- createTextNode: (text: string) => new MockText(text),
93
- createElementNS: (ns: string, tag: string) => new MockElement(tag),
94
- hidden: false,
95
- };
96
- const mockWin: any = {
97
- requestAnimationFrame: (cb: any) => cb(Date.now()),
217
+ createElement: (tag: string) => new FakeElement(tag),
218
+ createTextNode: (text: string) => new FakeTextNode(text),
219
+ createElementNS: (ns: string, tag: string) => new FakeElement(tag),
98
220
  startViewTransition: (callbackOptions: any) => {
99
221
  return {
100
222
  finished: Promise.resolve(),
@@ -105,7 +227,41 @@ export function resetMocks() {
105
227
  }
106
228
  };
107
229
 
230
+ Object.defineProperty(mockDoc, "hidden", {
231
+ enumerable: true,
232
+ configurable: true,
233
+ get: () => hidden,
234
+ set: (value: boolean) => {
235
+ hidden = !!value;
236
+ if (!hidden) {
237
+ scheduleNextFrame();
238
+ }
239
+ },
240
+ });
241
+
242
+ const mockWin: any = {
243
+ requestAnimationFrame: (cb: FrameRequestCallback) => {
244
+ const id = ++rafHandle;
245
+ rafQueue.set(id, cb);
246
+ scheduleNextFrame();
247
+ return id;
248
+ },
249
+ cancelAnimationFrame: (id: number) => {
250
+ rafQueue.delete(id);
251
+ }
252
+ };
253
+
108
254
  globalThis.document ??= mockDoc as Document;
109
255
  globalThis.window ??= mockWin as (Window & typeof globalThis);
110
256
  globalThis.Node ??= NodeConstants as any;
111
- }
257
+
258
+ const raf = globalThis.window?.requestAnimationFrame;
259
+ if (typeof raf === "function") {
260
+ globals.requestAnimationFrame = raf.bind(globalThis.window);
261
+ }
262
+
263
+ const startViewTransition = (globalThis.document as any)?.startViewTransition;
264
+ globals.startViewTransition = typeof startViewTransition === "function"
265
+ ? startViewTransition.bind(globalThis.document)
266
+ : null;
267
+ }
@@ -0,0 +1,61 @@
1
+ import { tests } from ".";
2
+ import { expect, ExpectationError } from "./helper";
3
+ import { resetMocks } from "./mocks";
4
+
5
+ const count = {
6
+ total: 0,
7
+ passed: 0,
8
+ failed: <string[]>[],
9
+ }
10
+ const line = "----------------------------------";
11
+
12
+ async function runTest(test: [string, () => any]) {
13
+ count.total++;
14
+ resetMocks();
15
+ const start = performance.now();
16
+ try {
17
+ expect(document).toBeNotHidden();
18
+ const result = test[1]();
19
+ if (result && typeof (result as any)?.then === "function") {
20
+ await result;
21
+ }
22
+ count.passed++;
23
+ const time = (performance.now() - start).toFixed(3) + " ms";
24
+ console.log(`#${count.total} ${test[0]}\n-> 🟢 passed ${time}\n${line}`);
25
+ } catch (err: any) {
26
+ const time = (performance.now() - start).toFixed(3) + " ms";
27
+ console.error(`#${count.total} ${test[0]}\n-> 🔴 failed ${time}`);
28
+ if (err instanceof ExpectationError) {
29
+ count.failed.push(`#${count.total} ${test[0]}\n-> 🔴 failed:\n${err.message}\n${line}`);
30
+ }
31
+ else {
32
+ count.failed.push(`#${count.total} ${test[0]}\n-> 🔴 failed:\n${err.message}\n${err.stack}\n${line}`);
33
+ }
34
+ }
35
+ }
36
+
37
+ const sw = performance.now();
38
+ (async () => {
39
+ for (const test of Object.entries(tests)) {
40
+ await runTest(test);
41
+ }
42
+
43
+ const time = (performance.now() - sw).toFixed(3) + " ms";
44
+
45
+ console.log(`
46
+ total: ${count.total}
47
+ passed: ${count.passed}
48
+ failed: ${count.failed.length}
49
+
50
+ time: ${time}
51
+ `);
52
+
53
+ if (count.passed === count.total) {
54
+ console.log("\n\nall tests passed\n");
55
+ }
56
+ else {
57
+ console.error(`${line.replaceAll("-", "=")}\nError summary:\n\n${count.failed.join(`\n${line}\n`)}`);
58
+
59
+ throw "\n\nsome tests failed (see output)\n";
60
+ }
61
+ })();