@@ -49,16 +49,16 @@ afterEach(async () => {
4949 vi . unstubAllGlobals ( )
5050} )
5151
52- async function render ( canEditLogo = true ) {
52+ async function render ( canEditLogo = true , isCollapsed = false , onExpandSidebar = vi . fn ( ) ) {
5353 await act ( async ( ) => {
5454 root . render (
5555 < QueryClientProvider client = { queryClient } >
5656 < ToastProvider >
5757 < OrganizationHeader
5858 organization = { organization }
5959 canEditLogo = { canEditLogo }
60- isCollapsed = { false }
61- onExpandSidebar = { vi . fn ( ) }
60+ isCollapsed = { isCollapsed }
61+ onExpandSidebar = { onExpandSidebar }
6262 />
6363 </ ToastProvider >
6464 </ QueryClientProvider >
@@ -74,9 +74,9 @@ async function openMenu() {
7474 } )
7575}
7676
77- function menuItem ( name : string ) {
78- return Array . from ( document . querySelectorAll < HTMLElement > ( '[role="menuitem"]' ) ) . find (
79- ( item ) => item . textContent === name
77+ function logoControl ( ) {
78+ return document . querySelector < HTMLElement > (
79+ '[role="menuitem"][aria-label="Change organization logo"]'
8080 )
8181}
8282
@@ -87,21 +87,70 @@ async function pickFile(file: File) {
8787}
8888
8989describe ( 'OrganizationHeader logo upload' , ( ) => {
90- it ( 'opens the same native file picker from the admin menu' , async ( ) => {
90+ it ( 'opens the native file picker by clicking the logo and keeps the menu open ' , async ( ) => {
9191 await render ( )
9292 await openMenu ( )
9393 const input = container . querySelector < HTMLInputElement > ( 'input[type="file"]' ) !
9494 const click = vi . spyOn ( input , 'click' ) . mockImplementation ( ( ) => { } )
9595 expect ( input . accept ) . toContain ( 'image/png' )
96- await act ( async ( ) => menuItem ( 'Upload logo' ) ! . click ( ) )
96+ await act ( async ( ) => logoControl ( ) ! . click ( ) )
97+ expect ( click ) . toHaveBeenCalledOnce ( )
98+ expect ( logoControl ( ) ) . not . toBeNull ( )
99+ expect ( document . body . textContent ) . not . toContain ( 'Upload logo' )
100+ expect ( document . querySelector ( '[role="menu"]' ) ?. textContent ) . toContain ( 'Settings' )
101+ } )
102+
103+ it . each ( [ 'Enter' , ' ' ] ) ( 'opens the file picker using the %j key' , async ( key ) => {
104+ await render ( )
105+ await openMenu ( )
106+ const input = container . querySelector < HTMLInputElement > ( 'input[type="file"]' ) !
107+ const click = vi . spyOn ( input , 'click' ) . mockImplementation ( ( ) => { } )
108+ await act ( async ( ) => {
109+ logoControl ( ) ! . focus ( )
110+ logoControl ( ) ! . dispatchEvent ( new KeyboardEvent ( 'keydown' , { key, bubbles : true } ) )
111+ } )
97112 expect ( click ) . toHaveBeenCalledOnce ( )
98113 } )
99114
100115 it ( 'does not offer logo changes to members' , async ( ) => {
101116 await render ( false )
102117 await openMenu ( )
103- expect ( menuItem ( 'Upload logo' ) ) . toBeUndefined ( )
118+ expect ( logoControl ( ) ) . toBeNull ( )
119+ expect ( container . querySelector ( 'input[type="file"]' ) ) . toBeNull ( )
120+ expect ( document . querySelector ( '[role="menu"]' ) ?. textContent ) . toContain ( 'Design' )
121+ } )
122+
123+ it ( 'preserves the collapsed logo as the sidebar expand control' , async ( ) => {
124+ const expand = vi . fn ( )
125+ await render ( true , true , expand )
126+ await act ( async ( ) => {
127+ container . querySelector < HTMLButtonElement > ( '[aria-label="Expand sidebar"]' ) ! . click ( )
128+ } )
129+ expect ( expand ) . toHaveBeenCalledOnce ( )
104130 expect ( container . querySelector ( 'input[type="file"]' ) ) . toBeNull ( )
131+ expect ( mocks . upload ) . not . toHaveBeenCalled ( )
132+ } )
133+
134+ it ( 'disables logo changes while the upload is pending' , async ( ) => {
135+ let completeUpload ! : ( ) => void
136+ mocks . upload . mockImplementation (
137+ ( ) =>
138+ new Promise < void > ( ( resolve ) => {
139+ completeUpload = resolve
140+ } )
141+ )
142+ await render ( )
143+ await openMenu ( )
144+ await pickFile ( new File ( [ 'image' ] , 'logo.png' , { type : 'image/png' } ) )
145+ await act ( async ( ) => {
146+ await vi . waitFor ( ( ) => expect ( logoControl ( ) ?. getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) )
147+ } )
148+ expect ( logoControl ( ) ?. getAttribute ( 'aria-busy' ) ) . toBe ( 'true' )
149+ expect ( container . querySelector < HTMLInputElement > ( 'input[type="file"]' ) ! . disabled ) . toBe ( true )
150+ await act ( async ( ) => logoControl ( ) ! . click ( ) )
151+ expect ( mocks . upload ) . toHaveBeenCalledOnce ( )
152+ await act ( async ( ) => completeUpload ( ) )
153+ expect ( mocks . refresh ) . toHaveBeenCalledOnce ( )
105154 } )
106155
107156 it ( 'uploads under the organization scope and refreshes its identity after success' , async ( ) => {
0 commit comments