onejs-react 0.1.8 → 0.1.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.
@@ -0,0 +1,581 @@
1
+ /**
2
+ * Tests for C# interop hooks and utilities
3
+ *
4
+ * Tests cover:
5
+ * - toArray: Converting C# collections (List<T>, arrays) to JS arrays
6
+ * - useFrameSync (simple mode): Object.is comparison for primitives
7
+ * - useFrameSync (selector mode): Deps-based change detection for C# proxy objects
8
+ * - useFrameSyncWith (deprecated): Custom equality comparison
9
+ */
10
+
11
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
12
+ import React from "react"
13
+ import { useFrameSync, useFrameSyncWith, toArray } from "../hooks"
14
+ import { render, unmount } from "../renderer"
15
+ import { createMockContainer, flushMicrotasks } from "./mocks"
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // RAF mock — needed because hooks use requestAnimationFrame for polling
19
+ // ---------------------------------------------------------------------------
20
+
21
+ type RafCallback = (time: number) => void
22
+
23
+ let rafQueue: Array<{ id: number; callback: RafCallback }> = []
24
+ let nextRafId = 0
25
+
26
+ /** Flush all pending requestAnimationFrame callbacks (one round). */
27
+ function flushRaf() {
28
+ const queue = [...rafQueue]
29
+ rafQueue = []
30
+ const now = Date.now()
31
+ for (const { callback } of queue) {
32
+ callback(now)
33
+ }
34
+ }
35
+
36
+ /** Flush RAF then microtasks — simulates a full frame tick for the hook. */
37
+ async function advanceFrame() {
38
+ flushRaf()
39
+ await flushMicrotasks()
40
+ }
41
+
42
+ beforeEach(() => {
43
+ rafQueue = []
44
+ nextRafId = 0
45
+ ;(globalThis as any).requestAnimationFrame = vi.fn((cb: RafCallback) => {
46
+ const id = ++nextRafId
47
+ rafQueue.push({ id, callback: cb })
48
+ return id
49
+ })
50
+ ;(globalThis as any).cancelAnimationFrame = vi.fn((id: number) => {
51
+ rafQueue = rafQueue.filter((e) => e.id !== id)
52
+ })
53
+ })
54
+
55
+ afterEach(() => {
56
+ delete (globalThis as any).requestAnimationFrame
57
+ delete (globalThis as any).cancelAnimationFrame
58
+ })
59
+
60
+ // ===========================================================================
61
+ // toArray
62
+ // ===========================================================================
63
+
64
+ describe("toArray", () => {
65
+ it("returns empty array for null", () => {
66
+ expect(toArray(null)).toEqual([])
67
+ })
68
+
69
+ it("returns empty array for undefined", () => {
70
+ expect(toArray(undefined)).toEqual([])
71
+ })
72
+
73
+ it("converts a C# List (Count property)", () => {
74
+ const mockList: Record<string, unknown> = {
75
+ Count: 3,
76
+ 0: "alpha",
77
+ 1: "beta",
78
+ 2: "gamma",
79
+ }
80
+ expect(toArray(mockList)).toEqual(["alpha", "beta", "gamma"])
81
+ })
82
+
83
+ it("converts a C# array (Length property)", () => {
84
+ const mockArray: Record<string, unknown> = {
85
+ Length: 2,
86
+ 0: 10,
87
+ 1: 20,
88
+ }
89
+ expect(toArray(mockArray)).toEqual([10, 20])
90
+ })
91
+
92
+ it("returns empty array for empty collection with Count=0", () => {
93
+ expect(toArray({ Count: 0 })).toEqual([])
94
+ })
95
+
96
+ it("returns empty array for empty collection with Length=0", () => {
97
+ expect(toArray({ Length: 0 })).toEqual([])
98
+ })
99
+
100
+ it("prefers Count over Length when both exist", () => {
101
+ const mock: Record<string, unknown> = {
102
+ Count: 2,
103
+ Length: 3,
104
+ 0: "x",
105
+ 1: "y",
106
+ 2: "z",
107
+ }
108
+ // Should use Count (2), not Length (3)
109
+ expect(toArray(mock)).toEqual(["x", "y"])
110
+ })
111
+
112
+ it("returns empty array for objects without Count or Length", () => {
113
+ expect(toArray({})).toEqual([])
114
+ expect(toArray({ foo: "bar" })).toEqual([])
115
+ })
116
+
117
+ it("returns empty array for non-numeric Count/Length", () => {
118
+ expect(toArray({ Count: "not a number" })).toEqual([])
119
+ expect(toArray({ Length: null })).toEqual([])
120
+ expect(toArray({ Count: true })).toEqual([])
121
+ })
122
+
123
+ it("handles collection with object elements", () => {
124
+ const mockList: Record<string, unknown> = {
125
+ Count: 2,
126
+ 0: { id: 1, name: "Sword" },
127
+ 1: { id: 2, name: "Shield" },
128
+ }
129
+ const result = toArray<{ id: number; name: string }>(mockList)
130
+ expect(result).toHaveLength(2)
131
+ expect(result[0].id).toBe(1)
132
+ expect(result[0].name).toBe("Sword")
133
+ expect(result[1].id).toBe(2)
134
+ expect(result[1].name).toBe("Shield")
135
+ })
136
+
137
+ it("handles single-element collection", () => {
138
+ expect(toArray({ Count: 1, 0: "only" })).toEqual(["only"])
139
+ })
140
+
141
+ it("handles large collections", () => {
142
+ const mock: Record<string, unknown> = { Count: 100 }
143
+ for (let i = 0; i < 100; i++) {
144
+ mock[i] = i * 2
145
+ }
146
+ const result = toArray<number>(mock)
147
+ expect(result).toHaveLength(100)
148
+ expect(result[0]).toBe(0)
149
+ expect(result[50]).toBe(100)
150
+ expect(result[99]).toBe(198)
151
+ })
152
+ })
153
+
154
+ // ===========================================================================
155
+ // useFrameSync — simple mode (no selector)
156
+ // ===========================================================================
157
+
158
+ describe("useFrameSync (simple mode)", () => {
159
+ let container: ReturnType<typeof createMockContainer>
160
+
161
+ beforeEach(() => {
162
+ container = createMockContainer()
163
+ })
164
+
165
+ afterEach(() => {
166
+ unmount(container as any)
167
+ })
168
+
169
+ function renderHookCapture<T>(
170
+ getter: () => T,
171
+ deps?: readonly unknown[]
172
+ ) {
173
+ let capturedValue: T = undefined as T
174
+ let renderCount = 0
175
+
176
+ function TestComponent() {
177
+ const value = useFrameSync(getter, deps)
178
+ capturedValue = value
179
+ renderCount++
180
+ return null
181
+ }
182
+
183
+ render(<TestComponent />, container as any)
184
+
185
+ return {
186
+ get value() { return capturedValue },
187
+ get renderCount() { return renderCount },
188
+ }
189
+ }
190
+
191
+ it("returns initial value from getter", async () => {
192
+ const capture = renderHookCapture(() => 42)
193
+ await flushMicrotasks()
194
+ expect(capture.value).toBe(42)
195
+ })
196
+
197
+ it("updates when primitive value changes", async () => {
198
+ let counter = 0
199
+ const capture = renderHookCapture(() => counter)
200
+ await flushMicrotasks()
201
+ await advanceFrame()
202
+ const initialRenders = capture.renderCount
203
+
204
+ counter = 1
205
+ await advanceFrame()
206
+ expect(capture.renderCount).toBeGreaterThan(initialRenders)
207
+ expect(capture.value).toBe(1)
208
+ })
209
+
210
+ it("does not re-render when value is unchanged", async () => {
211
+ const capture = renderHookCapture(() => "stable")
212
+ await flushMicrotasks()
213
+ await advanceFrame()
214
+ const initialRenders = capture.renderCount
215
+
216
+ await advanceFrame()
217
+ await advanceFrame()
218
+ expect(capture.renderCount).toBe(initialRenders)
219
+ })
220
+
221
+ it("handles null return from getter", async () => {
222
+ const capture = renderHookCapture(() => null)
223
+ await flushMicrotasks()
224
+ expect(capture.value).toBeNull()
225
+ })
226
+
227
+ it("handles getter that throws", async () => {
228
+ const capture = renderHookCapture(() => { throw new Error("boom") })
229
+ await flushMicrotasks()
230
+ expect(capture.value).toBeUndefined()
231
+ })
232
+
233
+ it("stops polling after unmount", async () => {
234
+ const getter = vi.fn(() => 42)
235
+ renderHookCapture(getter)
236
+ await flushMicrotasks()
237
+ await advanceFrame()
238
+
239
+ const callsBeforeUnmount = getter.mock.calls.length
240
+
241
+ unmount(container as any)
242
+ await flushMicrotasks()
243
+
244
+ await advanceFrame()
245
+ await advanceFrame()
246
+ await advanceFrame()
247
+
248
+ // At most one extra call from the in-flight RAF callback
249
+ expect(getter.mock.calls.length).toBeLessThanOrEqual(callsBeforeUnmount + 1)
250
+ })
251
+
252
+ it("uses Object.is for comparison (NaN === NaN)", async () => {
253
+ let val = NaN
254
+ const capture = renderHookCapture(() => val)
255
+ await flushMicrotasks()
256
+ await advanceFrame()
257
+ const initialRenders = capture.renderCount
258
+
259
+ // NaN should be Object.is equal to NaN — no re-render
260
+ val = NaN
261
+ await advanceFrame()
262
+ expect(capture.renderCount).toBe(initialRenders)
263
+ })
264
+ })
265
+
266
+ // ===========================================================================
267
+ // useFrameSync — selector mode
268
+ // ===========================================================================
269
+
270
+ describe("useFrameSync (selector mode)", () => {
271
+ let container: ReturnType<typeof createMockContainer>
272
+
273
+ beforeEach(() => {
274
+ container = createMockContainer()
275
+ })
276
+
277
+ afterEach(() => {
278
+ unmount(container as any)
279
+ })
280
+
281
+ function renderHookCapture<T>(
282
+ getter: () => T,
283
+ select: (v: T) => readonly unknown[],
284
+ deps?: readonly unknown[]
285
+ ) {
286
+ let capturedValue: T = undefined as T
287
+ let renderCount = 0
288
+
289
+ function TestComponent() {
290
+ const value = useFrameSync(getter, select, deps)
291
+ capturedValue = value
292
+ renderCount++
293
+ return null
294
+ }
295
+
296
+ render(<TestComponent />, container as any)
297
+
298
+ return {
299
+ get value() { return capturedValue },
300
+ get renderCount() { return renderCount },
301
+ }
302
+ }
303
+
304
+ it("returns initial value from getter on first render", async () => {
305
+ const capture = renderHookCapture(
306
+ () => ({ name: "Town", population: 100 }),
307
+ (v) => [v.name, v.population]
308
+ )
309
+ await flushMicrotasks()
310
+
311
+ expect(capture.value).toEqual({ name: "Town", population: 100 })
312
+ })
313
+
314
+ it("returns null when getter returns null", async () => {
315
+ const capture = renderHookCapture(
316
+ () => null,
317
+ (v) => [v]
318
+ )
319
+ await flushMicrotasks()
320
+
321
+ expect(capture.value).toBeNull()
322
+ })
323
+
324
+ it("returns undefined when getter throws", async () => {
325
+ const capture = renderHookCapture(
326
+ () => { throw new Error("destroyed") },
327
+ (v) => [v]
328
+ )
329
+ await flushMicrotasks()
330
+
331
+ expect(capture.value).toBeUndefined()
332
+ })
333
+
334
+ it("re-renders when a selected dep changes", async () => {
335
+ const data = { name: "Town A", count: 1, untracked: "foo" }
336
+
337
+ const capture = renderHookCapture(
338
+ () => data,
339
+ (v) => [v.name, v.count]
340
+ )
341
+ await flushMicrotasks()
342
+ const initialRenders = capture.renderCount
343
+
344
+ // Advance one frame with no changes — should not re-render
345
+ await advanceFrame()
346
+ expect(capture.renderCount).toBe(initialRenders)
347
+
348
+ // Change a tracked dep
349
+ data.name = "Town B"
350
+ await advanceFrame()
351
+
352
+ expect(capture.renderCount).toBeGreaterThan(initialRenders)
353
+ expect(capture.value.name).toBe("Town B")
354
+ })
355
+
356
+ it("does not re-render when untracked properties change", async () => {
357
+ const data = { name: "Town", count: 1, untracked: "a" }
358
+
359
+ const capture = renderHookCapture(
360
+ () => data,
361
+ (v) => [v.name, v.count]
362
+ )
363
+ await flushMicrotasks()
364
+ // Let the effect run and schedule the first RAF check
365
+ await advanceFrame()
366
+ const afterInit = capture.renderCount
367
+
368
+ // Change only an untracked property
369
+ data.untracked = "b"
370
+ await advanceFrame()
371
+
372
+ expect(capture.renderCount).toBe(afterInit)
373
+ })
374
+
375
+ it("detects changes in the number of selected deps", async () => {
376
+ let depCount = 2
377
+
378
+ const capture = renderHookCapture(
379
+ () => "value",
380
+ () => {
381
+ // Return different-length arrays based on external state
382
+ const arr: unknown[] = []
383
+ for (let i = 0; i < depCount; i++) arr.push(i)
384
+ return arr
385
+ }
386
+ )
387
+ await flushMicrotasks()
388
+ await advanceFrame()
389
+ const afterInit = capture.renderCount
390
+
391
+ // Change the number of deps
392
+ depCount = 3
393
+ await advanceFrame()
394
+
395
+ expect(capture.renderCount).toBeGreaterThan(afterInit)
396
+ })
397
+
398
+ it("simulates C# proxy caching — same object reference, property changes", async () => {
399
+ // This is the core scenario: a C# proxy always returns the same
400
+ // JS object reference, but its properties change because they
401
+ // read through to C# on each access.
402
+ const proxy = { Name: "Village", NPCCount: 3, Version: 1 }
403
+
404
+ const capture = renderHookCapture(
405
+ () => proxy, // Always returns the SAME object reference
406
+ (p) => [p.Name, p.NPCCount, p.Version]
407
+ )
408
+ await flushMicrotasks()
409
+ await advanceFrame()
410
+ expect(capture.value.Name).toBe("Village")
411
+ const afterInit = capture.renderCount
412
+
413
+ // Mutate the proxy (simulates C# property change via proxy)
414
+ proxy.Name = "City"
415
+ proxy.Version = 2
416
+ await advanceFrame()
417
+
418
+ expect(capture.renderCount).toBeGreaterThan(afterInit)
419
+ expect(capture.value.Name).toBe("City")
420
+ })
421
+
422
+ it("handles nullable C# references with optional chaining in select", async () => {
423
+ let currentPlace: { Name: string; Items: { Count: number } } | null = {
424
+ Name: "Tavern",
425
+ Items: { Count: 5 },
426
+ }
427
+
428
+ const capture = renderHookCapture(
429
+ () => currentPlace,
430
+ (p) => [p?.Name, p?.Items?.Count]
431
+ )
432
+ await flushMicrotasks()
433
+ await advanceFrame()
434
+ expect(capture.value?.Name).toBe("Tavern")
435
+ const afterInit = capture.renderCount
436
+
437
+ // Set to null — should detect change (deps go from ["Tavern", 5] to [undefined, undefined])
438
+ currentPlace = null
439
+ await advanceFrame()
440
+
441
+ expect(capture.renderCount).toBeGreaterThan(afterInit)
442
+ expect(capture.value).toBeNull()
443
+ })
444
+
445
+ it("works with version stamp pattern", async () => {
446
+ const gameState = { Version: 1, data: "initial" }
447
+
448
+ const capture = renderHookCapture(
449
+ () => gameState,
450
+ (s) => [s.Version]
451
+ )
452
+ await flushMicrotasks()
453
+ await advanceFrame()
454
+ const afterInit = capture.renderCount
455
+
456
+ // No version change — no re-render
457
+ gameState.data = "changed but version same"
458
+ await advanceFrame()
459
+ expect(capture.renderCount).toBe(afterInit)
460
+
461
+ // Bump version — triggers re-render
462
+ gameState.Version = 2
463
+ gameState.data = "updated"
464
+ await advanceFrame()
465
+ expect(capture.renderCount).toBeGreaterThan(afterInit)
466
+ expect(capture.value.data).toBe("updated")
467
+ })
468
+
469
+ it("stops polling after unmount", async () => {
470
+ const getter = vi.fn(() => 42)
471
+
472
+ renderHookCapture(getter, (v) => [v])
473
+ await flushMicrotasks()
474
+ await advanceFrame()
475
+
476
+ const callsBeforeUnmount = getter.mock.calls.length
477
+
478
+ // Unmount
479
+ unmount(container as any)
480
+ await flushMicrotasks()
481
+
482
+ // Advance more frames — getter should not be called
483
+ await advanceFrame()
484
+ await advanceFrame()
485
+ await advanceFrame()
486
+
487
+ // At most one extra call from the in-flight RAF callback
488
+ expect(getter.mock.calls.length).toBeLessThanOrEqual(callsBeforeUnmount + 1)
489
+ })
490
+
491
+ it("continues polling across multiple frames", async () => {
492
+ const data = { value: 0 }
493
+
494
+ const capture = renderHookCapture(
495
+ () => data,
496
+ (d) => [d.value]
497
+ )
498
+ await flushMicrotasks()
499
+ await advanceFrame()
500
+ const afterInit = capture.renderCount
501
+
502
+ // Frame 2: change value
503
+ data.value = 1
504
+ await advanceFrame()
505
+ expect(capture.renderCount).toBeGreaterThan(afterInit)
506
+ const afterFirst = capture.renderCount
507
+
508
+ // Frame 3: change value again
509
+ data.value = 2
510
+ await advanceFrame()
511
+ expect(capture.renderCount).toBeGreaterThan(afterFirst)
512
+ expect(capture.value.value).toBe(2)
513
+ })
514
+
515
+ it("always returns fresh value from getter on render", async () => {
516
+ // Even between detected changes, the returned value should
517
+ // be the current live getter result (not a stale snapshot).
518
+ let callCount = 0
519
+ const capture = renderHookCapture(
520
+ () => {
521
+ callCount++
522
+ return { fresh: callCount }
523
+ },
524
+ () => [1] // Deps never change after init
525
+ )
526
+ await flushMicrotasks()
527
+
528
+ // The value should reflect a recent getter call
529
+ expect(capture.value.fresh).toBeGreaterThan(0)
530
+ })
531
+ })
532
+
533
+ // ===========================================================================
534
+ // useFrameSyncWith (deprecated)
535
+ // ===========================================================================
536
+
537
+ describe("useFrameSyncWith (deprecated)", () => {
538
+ let container: ReturnType<typeof createMockContainer>
539
+
540
+ beforeEach(() => {
541
+ container = createMockContainer()
542
+ })
543
+
544
+ afterEach(() => {
545
+ unmount(container as any)
546
+ })
547
+
548
+ it("works with custom equality for new JS objects", async () => {
549
+ // useFrameSyncWith works when the getter returns a NEW object each time
550
+ // (not a cached proxy). This is its valid use case.
551
+ let x = 1, y = 2, z = 3
552
+ let capturedValue: { x: number; y: number; z: number } = undefined as any
553
+
554
+ function TestComponent() {
555
+ const value = useFrameSyncWith(
556
+ () => ({ x, y, z }),
557
+ (a, b) => a.x === b.x && a.y === b.y && a.z === b.z
558
+ )
559
+ capturedValue = value
560
+ return null
561
+ }
562
+
563
+ render(<TestComponent />, container as any)
564
+ await flushMicrotasks()
565
+ expect(capturedValue).toEqual({ x: 1, y: 2, z: 3 })
566
+
567
+ // No change — should not update (custom equality says they're equal)
568
+ await advanceFrame()
569
+ const stableValue = capturedValue
570
+
571
+ await advanceFrame()
572
+ // The reference may change (new object each getter call) but
573
+ // custom equality prevents unnecessary state updates
574
+ expect(capturedValue).toEqual({ x: 1, y: 2, z: 3 })
575
+
576
+ // Change a value — should trigger update
577
+ x = 10
578
+ await advanceFrame()
579
+ expect(capturedValue.x).toBe(10)
580
+ })
581
+ })
@@ -663,21 +663,23 @@ describe('host-config', () => {
663
663
  });
664
664
 
665
665
  describe('visibility', () => {
666
- it('hideInstance sets display to none', () => {
666
+ it('hideInstance sets display to DisplayStyle.None', () => {
667
667
  const instance = createInstance('ojs-view', {});
668
668
 
669
669
  hideInstance(instance);
670
670
 
671
- expect(instance.element.style.display).toBe('none');
671
+ const DisplayStyle = (globalThis as any).CS.UnityEngine.UIElements.DisplayStyle;
672
+ expect(instance.element.style.display).toBe(DisplayStyle.None);
672
673
  });
673
674
 
674
- it('unhideInstance clears display', () => {
675
+ it('unhideInstance sets display to DisplayStyle.Flex', () => {
675
676
  const instance = createInstance('ojs-view', {});
676
- instance.element.style.display = 'none';
677
+ const DisplayStyle = (globalThis as any).CS.UnityEngine.UIElements.DisplayStyle;
678
+ instance.element.style.display = DisplayStyle.None;
677
679
 
678
680
  unhideInstance(instance, {});
679
681
 
680
- expect(instance.element.style.display).toBe('');
682
+ expect(instance.element.style.display).toBe(DisplayStyle.Flex);
681
683
  });
682
684
  });
683
685
 
@@ -225,12 +225,17 @@ export class MockImage extends MockVisualElement {
225
225
 
226
226
  /**
227
227
  * Create the mock CS global object that mirrors QuickJSBootstrap.js proxy
228
+ *
229
+ * Enum values match Unity's actual enum definitions so that tests
230
+ * verify the real mapping behavior (CSS string -> Unity enum number).
228
231
  */
229
232
  export function createMockCS() {
230
233
  return {
231
234
  UnityEngine: {
232
235
  // Core types
233
236
  Color: MockColor,
237
+ Rect: class { constructor(public x: number, public y: number, public width: number, public height: number) {} },
238
+ ScaleMode: { StretchToFill: 0, ScaleAndCrop: 1, ScaleToFit: 2 },
234
239
  // UI Elements
235
240
  UIElements: {
236
241
  VisualElement: MockVisualElement,
@@ -246,6 +251,39 @@ export function createMockCS() {
246
251
  Length: MockLength,
247
252
  LengthUnit: MockLengthUnit,
248
253
  StyleKeyword: MockStyleKeyword,
254
+ // Enums (values match Unity's actual enum definitions)
255
+ FlexDirection: { Column: 0, ColumnReverse: 1, Row: 2, RowReverse: 3 },
256
+ Wrap: { NoWrap: 0, Wrap: 1, WrapReverse: 2 },
257
+ Align: { Auto: 0, FlexStart: 1, Center: 2, FlexEnd: 3, Stretch: 4 },
258
+ Justify: { FlexStart: 0, Center: 1, FlexEnd: 2, SpaceBetween: 3, SpaceAround: 4 },
259
+ Position: { Relative: 0, Absolute: 1 },
260
+ Overflow: { Visible: 0, Hidden: 1 },
261
+ DisplayStyle: { Flex: 0, None: 1 },
262
+ Visibility: { Visible: 0, Hidden: 1 },
263
+ WhiteSpace: { Normal: 0, NoWrap: 1 },
264
+ TextOverflow: { Clip: 0, Ellipsis: 1 },
265
+ TextOverflowPosition: { End: 0, Start: 1, Middle: 2 },
266
+ OverflowClipBox: { PaddingBox: 0, ContentBox: 1 },
267
+ PickingMode: { Position: 0, Ignore: 1 },
268
+ SliderDirection: { Horizontal: 0, Vertical: 1 },
269
+ // ScrollView enums
270
+ ScrollViewMode: { Vertical: 0, Horizontal: 1, VerticalAndHorizontal: 2 },
271
+ ScrollerVisibility: { Auto: 0, AlwaysVisible: 1, Hidden: 2 },
272
+ TouchScrollBehavior: { Unrestricted: 0, Elastic: 1, Clamped: 2 },
273
+ NestedInteractionKind: { Default: 0, StopScrolling: 1, ForwardScrolling: 2 },
274
+ // ListView enums
275
+ SelectionType: { None: 0, Single: 1, Multiple: 2 },
276
+ ListViewReorderMode: { Simple: 0, Animated: 1 },
277
+ AlternatingRowBackground: { None: 0, ContentOnly: 1, All: 2 },
278
+ CollectionVirtualizationMethod: { FixedHeight: 0, DynamicHeight: 1 },
279
+ },
280
+ },
281
+ OneJS: {
282
+ GPU: {
283
+ GPUBridge: {
284
+ SetElementBackgroundImage: () => {},
285
+ ClearElementBackgroundImage: () => {},
286
+ },
249
287
  },
250
288
  },
251
289
  };
@@ -323,5 +361,7 @@ export function getEventAPI() {
323
361
  addEventListener: ReturnType<typeof import('vitest').vi.fn>;
324
362
  removeEventListener: ReturnType<typeof import('vitest').vi.fn>;
325
363
  removeAllEventListeners: ReturnType<typeof import('vitest').vi.fn>;
364
+ setParent: ReturnType<typeof import('vitest').vi.fn>;
365
+ removeParent: ReturnType<typeof import('vitest').vi.fn>;
326
366
  };
327
367
  }