1- import { act } from 'react'
1+ import { act , useEffect , useState } from 'react'
22import { createRoot } from 'react-dom/client'
33import { MemoryRouter } from 'react-router-dom'
44import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
55
6- import { AppUIProvider , useSidebarUI } from '../contexts/app-ui'
6+ import { AppUIProvider , useSettingsUI , useSidebarUI , useTitleBarRightPanelUI } from '../contexts/app-ui'
77import { AuthContextBridge , type AuthContextValue } from '../contexts/auth'
88import { DesktopTitleBar } from '../components/DesktopTitleBar'
99import { LocaleProvider } from '../contexts/LocaleContext'
@@ -33,6 +33,41 @@ function SidebarProbe() {
3333 )
3434}
3535
36+ function SettingsProbe ( ) {
37+ const { settingsOpen, settingsInitialTab } = useSettingsUI ( )
38+
39+ return (
40+ < div >
41+ < span data-testid = "settings-open" > { settingsOpen ? 'open' : 'closed' } </ span >
42+ < span data-testid = "settings-tab" > { settingsInitialTab } </ span >
43+ </ div >
44+ )
45+ }
46+
47+ function RightPanelShortcutProbe ( ) {
48+ const { rightPanelOpen, setRightPanelOpen } = useSidebarUI ( )
49+ const { setTitleBarRightPanelClick } = useTitleBarRightPanelUI ( )
50+ const [ visible , setVisible ] = useState ( false )
51+
52+ useEffect ( ( ) => {
53+ setTitleBarRightPanelClick ( ( ) => {
54+ setVisible ( ( open ) => ! open )
55+ } )
56+ return ( ) => setTitleBarRightPanelClick ( null )
57+ } , [ setTitleBarRightPanelClick ] )
58+
59+ useEffect ( ( ) => {
60+ setRightPanelOpen ( visible )
61+ } , [ visible , setRightPanelOpen ] )
62+
63+ return (
64+ < div >
65+ < span data-testid = "right-panel-visible" > { visible ? 'open' : 'closed' } </ span >
66+ < span data-testid = "right-panel-icon" > { rightPanelOpen ? 'open' : 'closed' } </ span >
67+ </ div >
68+ )
69+ }
70+
3671describe ( 'AppUIProvider sidebar state' , ( ) => {
3772 const authValue : AuthContextValue = {
3873 me : null ,
@@ -43,11 +78,13 @@ describe('AppUIProvider sidebar state', () => {
4378 }
4479
4580 const originalInnerWidth = window . innerWidth
81+ const originalNavigatorPlatform = Object . getOwnPropertyDescriptor ( window . navigator , 'platform' )
4682 const originalActEnvironment = ( globalThis as typeof globalThis & {
4783 IS_REACT_ACT_ENVIRONMENT ?: boolean
4884 } ) . IS_REACT_ACT_ENVIRONMENT
4985
5086 beforeEach ( ( ) => {
87+ desktopMock . isDesktop . mockReturnValue ( true )
5188 vi . useFakeTimers ( )
5289 vi . stubGlobal ( 'requestAnimationFrame' , ( cb : FrameRequestCallback ) => setTimeout ( ( ) => cb ( 0 ) , 0 ) )
5390 vi . stubGlobal ( 'cancelAnimationFrame' , ( id : number ) => clearTimeout ( id ) )
@@ -78,6 +115,11 @@ describe('AppUIProvider sidebar state', () => {
78115 IS_REACT_ACT_ENVIRONMENT ?: boolean
79116 } ) . IS_REACT_ACT_ENVIRONMENT = originalActEnvironment
80117 }
118+ if ( originalNavigatorPlatform ) {
119+ Object . defineProperty ( window . navigator , 'platform' , originalNavigatorPlatform )
120+ } else {
121+ Reflect . deleteProperty ( window . navigator , 'platform' )
122+ }
81123 } )
82124
83125 it ( '保留手动折叠状态,即使跨过宽度断点后再回来' , async ( ) => {
@@ -148,6 +190,112 @@ describe('AppUIProvider sidebar state', () => {
148190 } )
149191 container . remove ( )
150192 } )
193+
194+ it ( '全局设置快捷键打开 settings 页签并可再次关闭' , async ( ) => {
195+ desktopMock . isDesktop . mockReturnValue ( false )
196+ Object . defineProperty ( window . navigator , 'platform' , {
197+ configurable : true ,
198+ value : 'Win32' ,
199+ } )
200+
201+ const container = document . createElement ( 'div' )
202+ document . body . appendChild ( container )
203+ const root = createRoot ( container )
204+
205+ await act ( async ( ) => {
206+ root . render (
207+ < MemoryRouter initialEntries = { [ '/' ] } >
208+ < AuthContextBridge value = { authValue } >
209+ < AppUIProvider >
210+ < SettingsProbe />
211+ </ AppUIProvider >
212+ </ AuthContextBridge >
213+ </ MemoryRouter > ,
214+ )
215+ } )
216+
217+ expect ( container . querySelector ( '[data-testid="settings-open"]' ) ?. textContent ) . toBe ( 'closed' )
218+ expect ( container . querySelector ( '[data-testid="settings-tab"]' ) ?. textContent ) . toBe ( 'account' )
219+
220+ let event = new KeyboardEvent ( 'keydown' , {
221+ key : ',' ,
222+ code : 'Comma' ,
223+ ctrlKey : true ,
224+ bubbles : true ,
225+ cancelable : true ,
226+ } )
227+ await act ( async ( ) => {
228+ window . dispatchEvent ( event )
229+ } )
230+
231+ expect ( event . defaultPrevented ) . toBe ( true )
232+ expect ( container . querySelector ( '[data-testid="settings-open"]' ) ?. textContent ) . toBe ( 'open' )
233+ expect ( container . querySelector ( '[data-testid="settings-tab"]' ) ?. textContent ) . toBe ( 'settings' )
234+
235+ event = new KeyboardEvent ( 'keydown' , {
236+ key : ',' ,
237+ code : 'Comma' ,
238+ ctrlKey : true ,
239+ bubbles : true ,
240+ cancelable : true ,
241+ } )
242+ await act ( async ( ) => {
243+ window . dispatchEvent ( event )
244+ } )
245+
246+ expect ( container . querySelector ( '[data-testid="settings-open"]' ) ?. textContent ) . toBe ( 'closed' )
247+
248+ act ( ( ) => {
249+ root . unmount ( )
250+ } )
251+ container . remove ( )
252+ } )
253+
254+ it ( '右侧面板快捷键复用标题栏回调路径' , async ( ) => {
255+ desktopMock . isDesktop . mockReturnValue ( false )
256+ Object . defineProperty ( window . navigator , 'platform' , {
257+ configurable : true ,
258+ value : 'Win32' ,
259+ } )
260+
261+ const container = document . createElement ( 'div' )
262+ document . body . appendChild ( container )
263+ const root = createRoot ( container )
264+
265+ await act ( async ( ) => {
266+ root . render (
267+ < MemoryRouter initialEntries = { [ '/' ] } >
268+ < AuthContextBridge value = { authValue } >
269+ < AppUIProvider >
270+ < RightPanelShortcutProbe />
271+ </ AppUIProvider >
272+ </ AuthContextBridge >
273+ </ MemoryRouter > ,
274+ )
275+ } )
276+
277+ expect ( container . querySelector ( '[data-testid="right-panel-visible"]' ) ?. textContent ) . toBe ( 'closed' )
278+ expect ( container . querySelector ( '[data-testid="right-panel-icon"]' ) ?. textContent ) . toBe ( 'closed' )
279+
280+ await act ( async ( ) => {
281+ window . dispatchEvent ( new KeyboardEvent ( 'keydown' , {
282+ key : 'b' ,
283+ code : 'KeyB' ,
284+ altKey : true ,
285+ ctrlKey : true ,
286+ bubbles : true ,
287+ cancelable : true ,
288+ } ) )
289+ } )
290+
291+ expect ( container . querySelector ( '[data-testid="right-panel-visible"]' ) ?. textContent ) . toBe ( 'open' )
292+ expect ( container . querySelector ( '[data-testid="right-panel-icon"]' ) ?. textContent ) . toBe ( 'open' )
293+
294+ act ( ( ) => {
295+ root . unmount ( )
296+ } )
297+ container . remove ( )
298+ } )
151299} )
152300
153301describe ( 'DesktopTitleBar update entry' , ( ) => {
0 commit comments