1+ /**
2+ * @vitest -environment happy-dom
3+ */
4+ import { describe , expect , it , afterEach , beforeEach , vi } from 'vitest'
5+
6+ import { onWindowFocus } from './onWindowFocus'
7+
8+ describe ( 'onWindowFocus' , ( ) => {
9+ let mockCallback : ReturnType < typeof vi . fn >
10+ let addEventListenerSpy : ReturnType < typeof vi . spyOn >
11+ let removeEventListenerSpy : ReturnType < typeof vi . spyOn >
12+
13+ beforeEach ( ( ) => {
14+ mockCallback = vi . fn ( )
15+ addEventListenerSpy = vi . spyOn ( window , 'addEventListener' )
16+ removeEventListenerSpy = vi . spyOn ( window , 'removeEventListener' )
17+ } )
18+
19+ afterEach ( ( ) => {
20+ vi . restoreAllMocks ( )
21+ vi . clearAllTimers ( )
22+ } )
23+
24+ it ( 'should add event listeners for focus and visibilitychange' , ( ) => {
25+ /* 测试事件监听器是否正确添加 */
26+ onWindowFocus ( mockCallback )
27+
28+ expect ( addEventListenerSpy ) . toHaveBeenCalledWith ( 'focus' , expect . any ( Function ) , false )
29+ expect ( addEventListenerSpy ) . toHaveBeenCalledWith ( 'visibilitychange' , expect . any ( Function ) , false )
30+ expect ( addEventListenerSpy ) . toHaveBeenCalledTimes ( 2 )
31+ } )
32+
33+ it ( 'should execute callback when window focus event is triggered' , ( ) => {
34+ /* 测试 focus 事件触发回调 */
35+ vi . useFakeTimers ( )
36+ onWindowFocus ( mockCallback )
37+
38+ /* 触发 focus 事件 */
39+ window . dispatchEvent ( new Event ( 'focus' ) )
40+
41+ /* 等待 debounce 延迟 */
42+ vi . advanceTimersByTime ( 100 )
43+
44+ expect ( mockCallback ) . toHaveBeenCalledTimes ( 1 )
45+ } )
46+
47+ it ( 'should execute callback when document becomes visible' , ( ) => {
48+ /* 测试文档可见时的 visibilitychange 事件 */
49+ vi . useFakeTimers ( )
50+ onWindowFocus ( mockCallback )
51+
52+ /* 设置文档为可见状态 */
53+ Object . defineProperty ( document , 'visibilityState' , {
54+ value : 'visible' ,
55+ writable : true ,
56+ } )
57+
58+ /* 触发 visibilitychange 事件 - 在 window 对象上触发 */
59+ window . dispatchEvent ( new Event ( 'visibilitychange' ) )
60+
61+ /* 等待 debounce 延迟 */
62+ vi . advanceTimersByTime ( 100 )
63+
64+ expect ( mockCallback ) . toHaveBeenCalledTimes ( 1 )
65+ } )
66+
67+ it ( 'should not execute callback when document is hidden' , ( ) => {
68+ /* 测试文档隐藏时不触发回调 */
69+ vi . useFakeTimers ( )
70+ onWindowFocus ( mockCallback )
71+
72+ /* 设置文档为隐藏状态 */
73+ Object . defineProperty ( document , 'visibilityState' , {
74+ value : 'hidden' ,
75+ writable : true ,
76+ } )
77+
78+ /* 触发 visibilitychange 事件 - 在 window 对象上触发 */
79+ window . dispatchEvent ( new Event ( 'visibilitychange' ) )
80+
81+ /* 等待 debounce 延迟 */
82+ vi . advanceTimersByTime ( 100 )
83+
84+ expect ( mockCallback ) . not . toHaveBeenCalled ( )
85+ } )
86+
87+ it ( 'should debounce multiple rapid focus events' , ( ) => {
88+ /* 测试防抖功能 */
89+ vi . useFakeTimers ( )
90+ onWindowFocus ( mockCallback )
91+
92+ /* 快速触发多次 focus 事件 */
93+ window . dispatchEvent ( new Event ( 'focus' ) )
94+ window . dispatchEvent ( new Event ( 'focus' ) )
95+ window . dispatchEvent ( new Event ( 'focus' ) )
96+
97+ /* 在防抖时间内,回调不应该被执行 */
98+ vi . advanceTimersByTime ( 50 )
99+ expect ( mockCallback ) . not . toHaveBeenCalled ( )
100+
101+ /* 等待完整的防抖延迟后,回调应该只被执行一次 */
102+ vi . advanceTimersByTime ( 50 )
103+ expect ( mockCallback ) . toHaveBeenCalledTimes ( 1 )
104+ } )
105+
106+ it ( 'should return a cleanup function that removes event listeners' , ( ) => {
107+ /* 测试清理函数正确移除事件监听器 */
108+ const cleanup = onWindowFocus ( mockCallback )
109+
110+ /* 执行清理函数 */
111+ cleanup ( )
112+
113+ expect ( removeEventListenerSpy ) . toHaveBeenCalledWith ( 'focus' , expect . any ( Function ) )
114+ expect ( removeEventListenerSpy ) . toHaveBeenCalledWith ( 'visibilitychange' , expect . any ( Function ) )
115+ expect ( removeEventListenerSpy ) . toHaveBeenCalledTimes ( 2 )
116+ } )
117+
118+ it ( 'should not execute callback after cleanup' , ( ) => {
119+ /* 测试清理后不再执行回调 */
120+ vi . useFakeTimers ( )
121+ const cleanup = onWindowFocus ( mockCallback )
122+
123+ /* 执行清理 */
124+ cleanup ( )
125+
126+ /* 触发事件 */
127+ window . dispatchEvent ( new Event ( 'focus' ) )
128+ vi . advanceTimersByTime ( 100 )
129+
130+ expect ( mockCallback ) . not . toHaveBeenCalled ( )
131+ } )
132+
133+ it ( 'should handle multiple callback arguments' , ( ) => {
134+ /* 测试回调函数接收多个参数 */
135+ vi . useFakeTimers ( )
136+ const mockCallbackWithArgs = vi . fn ( ( ...args : any [ ] ) => args )
137+ onWindowFocus ( mockCallbackWithArgs )
138+
139+ window . dispatchEvent ( new Event ( 'focus' ) )
140+ vi . advanceTimersByTime ( 100 )
141+
142+ expect ( mockCallbackWithArgs ) . toHaveBeenCalledTimes ( 1 )
143+ } )
144+
145+ it ( 'should work with async callbacks' , async ( ) => {
146+ /* 测试异步回调函数 */
147+ vi . useFakeTimers ( )
148+ const asyncCallback = vi . fn ( async ( ) => {
149+ await new Promise ( resolve => setTimeout ( resolve , 10 ) )
150+ } )
151+
152+ onWindowFocus ( asyncCallback )
153+
154+ window . dispatchEvent ( new Event ( 'focus' ) )
155+ vi . advanceTimersByTime ( 100 )
156+
157+ expect ( asyncCallback ) . toHaveBeenCalledTimes ( 1 )
158+ } )
159+
160+ it ( 'should handle visibility state edge cases' , ( ) => {
161+ /* 测试 visibilityState 边界情况 */
162+ vi . useFakeTimers ( )
163+ onWindowFocus ( mockCallback )
164+
165+ /* 测试 prerender 状态 */
166+ Object . defineProperty ( document , 'visibilityState' , {
167+ value : 'prerender' ,
168+ writable : true ,
169+ } )
170+
171+ window . dispatchEvent ( new Event ( 'visibilitychange' ) )
172+ vi . advanceTimersByTime ( 100 )
173+
174+ expect ( mockCallback ) . not . toHaveBeenCalled ( )
175+ } )
176+ } )
0 commit comments