@@ -14,51 +14,109 @@ if (typeof globalThis.ResizeObserver === "undefined") {
1414 disconnect ( ) { }
1515 } ;
1616}
17+ // Default to a desktop viewport so mobile-only behavior (like auto-collapsing the sidebar)
18+ // doesn't interfere with UI integration tests.
19+ try {
20+ Object . defineProperty ( globalThis , "innerWidth" , { value : 1024 , writable : true } ) ;
21+ Object . defineProperty ( globalThis , "innerHeight" , { value : 768 , writable : true } ) ;
22+ } catch {
23+ // ignore
24+ }
25+
1726globalThis . import_meta_env = {
1827 VITE_BACKEND_URL : undefined ,
1928 MODE : "test" ,
2029 DEV : false ,
2130 PROD : false ,
2231} ;
2332
24- // Patch setTimeout to add unref method (required by undici timers)
25- // jsdom's setTimeout doesn't have unref, but node's does
26- const originalSetTimeout = globalThis . setTimeout ;
27- globalThis . setTimeout = function patchedSetTimeout ( ... args ) {
28- const timer = originalSetTimeout . apply ( this , args ) ;
29- if ( timer && typeof timer === "object" && ! timer . unref ) {
30- timer . unref = ( ) => timer ;
31- timer . ref = ( ) => timer ;
32- }
33- return timer ;
34- } ;
33+ // Use Node's timers implementation so the returned handles support unref/ref
34+ // requestIdleCallback is used by the renderer for stream batching.
35+ // jsdom doesn't provide it.
36+ globalThis . requestIdleCallback =
37+ globalThis . requestIdleCallback ??
38+ ( ( cb ) =>
39+ globalThis . setTimeout ( ( ) =>
40+ cb ( { didTimeout : false , timeRemaining : ( ) => 50 } )
41+ ) ) ;
42+ globalThis . cancelIdleCallback =
43+ globalThis . cancelIdleCallback ?? ( ( id ) => globalThis . clearTimeout ( id ) ) ;
3544
36- const originalSetInterval = globalThis . setInterval ;
37- globalThis . setInterval = function patchedSetInterval ( ...args ) {
38- const timer = originalSetInterval . apply ( this , args ) ;
39- if ( timer && typeof timer === "object" && ! timer . unref ) {
40- timer . unref = ( ) => timer ;
41- timer . ref = ( ) => timer ;
42- }
43- return timer ;
44- } ;
45+ // (required by undici timers). This also provides setImmediate/clearImmediate.
46+ const nodeTimers = require ( "node:timers" ) ;
47+ globalThis . setTimeout = nodeTimers . setTimeout ;
48+ globalThis . clearTimeout = nodeTimers . clearTimeout ;
49+ globalThis . setInterval = nodeTimers . setInterval ;
50+ globalThis . clearInterval = nodeTimers . clearInterval ;
51+ globalThis . setImmediate = nodeTimers . setImmediate ;
52+ globalThis . clearImmediate = nodeTimers . clearImmediate ;
4553
4654// Polyfill TextEncoder/TextDecoder - required by undici
4755const { TextEncoder, TextDecoder } = require ( "util" ) ;
4856globalThis . TextEncoder = globalThis . TextEncoder ?? TextEncoder ;
4957globalThis . TextDecoder = globalThis . TextDecoder ?? TextDecoder ;
5058
5159// Polyfill streams - required by AI SDK
52- const { TransformStream, ReadableStream, WritableStream } = require ( "node:stream/web" ) ;
60+ const {
61+ TransformStream,
62+ ReadableStream,
63+ WritableStream,
64+ TextDecoderStream,
65+ } = require ( "node:stream/web" ) ;
5366globalThis . TransformStream = globalThis . TransformStream ?? TransformStream ;
5467globalThis . ReadableStream = globalThis . ReadableStream ?? ReadableStream ;
5568globalThis . WritableStream = globalThis . WritableStream ?? WritableStream ;
69+ globalThis . TextDecoderStream = globalThis . TextDecoderStream ?? TextDecoderStream ;
5670
5771// Polyfill MessageChannel/MessagePort - required by undici
5872const { MessageChannel, MessagePort } = require ( "node:worker_threads" ) ;
5973globalThis . MessageChannel = globalThis . MessageChannel ?? MessageChannel ;
74+
75+ // Radix UI (Select, etc.) relies on Pointer Events + pointer capture.
76+ // jsdom doesn't implement these, so provide minimal no-op shims.
77+ if ( globalThis . Element && ! globalThis . Element . prototype . hasPointerCapture ) {
78+ globalThis . Element . prototype . hasPointerCapture = ( ) => false ;
79+ }
80+ if ( globalThis . Element && ! globalThis . Element . prototype . setPointerCapture ) {
81+ globalThis . Element . prototype . setPointerCapture = ( ) => { } ;
82+ }
83+ if ( globalThis . Element && ! globalThis . Element . prototype . scrollIntoView ) {
84+ globalThis . Element . prototype . scrollIntoView = ( ) => { } ;
85+ }
86+ if ( globalThis . Element && ! globalThis . Element . prototype . releasePointerCapture ) {
87+ globalThis . Element . prototype . releasePointerCapture = ( ) => { } ;
88+ }
6089globalThis . MessagePort = globalThis . MessagePort ?? MessagePort ;
6190
91+ // undici reads `performance.markResourceTiming` at import time. In jsdom,
92+ // Some renderer code uses `performance.mark()` for lightweight timing.
93+ if ( globalThis . performance && typeof globalThis . performance . mark !== "function" ) {
94+ globalThis . performance . mark = ( ) => { } ;
95+ }
96+ if ( globalThis . performance && typeof globalThis . performance . measure !== "function" ) {
97+ globalThis . performance . measure = ( ) => { } ;
98+ }
99+ if (
100+ globalThis . performance &&
101+ typeof globalThis . performance . clearMarks !== "function"
102+ ) {
103+ globalThis . performance . clearMarks = ( ) => { } ;
104+ }
105+ if (
106+ globalThis . performance &&
107+ typeof globalThis . performance . clearMeasures !== "function"
108+ ) {
109+ globalThis . performance . clearMeasures = ( ) => { } ;
110+ }
111+
112+ // `performance` exists but doesn't implement the Resource Timing API.
113+ if (
114+ globalThis . performance &&
115+ typeof globalThis . performance . markResourceTiming !== "function"
116+ ) {
117+ globalThis . performance . markResourceTiming = ( ) => { } ;
118+ }
119+
62120// Now undici can be safely imported
63121const { fetch, Request, Response, Headers, FormData, Blob } = require ( "undici" ) ;
64122globalThis . fetch = globalThis . fetch ?? fetch ;
0 commit comments