@@ -65,6 +65,22 @@ function fakeApps(): INavigationEntry[] {
6565 ]
6666}
6767
68+ // Mimics a page where the active entry is a settings one, so it is excluded
69+ // from the `apps` list. The object shape matches PHP's serialization, which
70+ // ships getAll('settings') keyed by entry id.
71+ function mockActiveSettingsEntry ( overrides : Partial < INavigationEntry > ) : void {
72+ const entry = makeApp ( { type : 'settings' , active : true , ...overrides } )
73+ initialState . loadState . mockImplementation ( ( _a : string , key : string , fallback : unknown ) => {
74+ if ( key === 'apps' ) {
75+ return [ makeApp ( { id : 'files' , name : 'Files' , active : false } ) ]
76+ }
77+ if ( key === 'settingsNavEntries' ) {
78+ return { [ entry . id ] : entry }
79+ }
80+ return fallback
81+ } )
82+ }
83+
6884function eightApps ( activeIndex : number = - 1 ) : INavigationEntry [ ] {
6985 const ids = [ 'files' , 'mail' , 'calendar' , 'contacts' , 'notes' , 'photos' , 'talk' , 'deck' ]
7086 return ids . map ( ( id , i ) => makeApp ( {
@@ -137,6 +153,18 @@ describe('core: AppMenu', () => {
137153 expect ( moreApps ) . toBeTruthy ( )
138154 } )
139155
156+ it ( 'marks the "More apps" tile active on the app management page' , async ( ) => {
157+ auth . getCurrentUser . mockReturnValue ( { isAdmin : true } )
158+ mockActiveSettingsEntry ( { id : 'appstore' , name : 'Apps' , href : '/settings/apps' } )
159+ const wrapper = mount ( AppMenu , { attachTo : document . body } )
160+ await openPopover ( wrapper )
161+
162+ const moreApps = Array . from ( document . querySelectorAll ( '[role="menuitem"]' ) )
163+ . find ( ( el ) => el . textContent ?. includes ( 'More apps' ) )
164+ expect ( moreApps ?. classList . contains ( 'app-item--active' ) ) . toBe ( true )
165+ expect ( moreApps ?. getAttribute ( 'aria-current' ) ) . toBe ( 'page' )
166+ } )
167+
140168 it ( 'ArrowRight moves the roving stop from index 0 to index 1 and focuses it' , async ( ) => {
141169 initialState . loadState . mockImplementation ( ( _a : string , key : string , fallback : unknown ) => key === 'apps' ? eightApps ( ) : fallback )
142170 const wrapper = mount ( AppMenu , { attachTo : document . body } )
@@ -174,41 +202,54 @@ describe('core: AppMenu', () => {
174202 } )
175203
176204 it ( 'falls back to the active settings entry when no app is active' , ( ) => {
177- // Mimics being on /settings/admin/* where the active entry is registered
178- // as type=settings (NavigationManager) and excluded from the `apps` list.
179- initialState . loadState . mockImplementation ( ( _a : string , key : string , fallback : unknown ) => {
180- if ( key === 'apps' ) {
181- return [ makeApp ( { id : 'files' , name : 'Files' , active : false } ) ]
182- }
183- if ( key === 'settingsNavEntries' ) {
184- // Object keyed by entry id — matches PHP's serialization shape
185- // (TemplateLayout ships the filtered associative array as-is).
186- return {
187- admin_settings : makeApp ( {
188- id : 'admin_settings' ,
189- name : 'Administration settings' ,
190- type : 'settings' ,
191- href : '/settings/admin/overview' ,
192- icon : '/settings/img/admin.svg' ,
193- active : true ,
194- } ) ,
195- }
196- }
197- return fallback
205+ // Mimics being on /settings/admin/*
206+ mockActiveSettingsEntry ( {
207+ id : 'settings_administration' ,
208+ name : 'Administration settings' ,
209+ href : '/settings/admin/overview' ,
210+ icon : '/settings/img/admin.svg' ,
198211 } )
199212 const wrapper = mount ( AppMenu , { attachTo : document . body } )
200213 expect ( wrapper . find ( '.app-menu__current-app' ) . exists ( ) ) . toBe ( true )
201214 // Settings sub-section names are collapsed to a single "Settings" label.
202215 expect ( wrapper . find ( '.app-menu__current-app-name' ) . text ( ) ) . toBe ( 'Settings' )
203216 } )
204217
218+ it ( 'keeps the own name of settings entries outside the settings app' , ( ) => {
219+ // On /settings/apps the active entry is the app management one, which
220+ // shows "Apps" here and in the account menu, not "Settings".
221+ mockActiveSettingsEntry ( { id : 'appstore' , name : 'Apps' , href : '/settings/apps' , icon : '/apps/appstore/img/app-dark.svg' } )
222+ const wrapper = mount ( AppMenu , { attachTo : document . body } )
223+ expect ( wrapper . find ( '.app-menu__current-app-name' ) . text ( ) ) . toBe ( 'Apps' )
224+ // Its own icon, not the generic cog of the settings sections
225+ expect ( wrapper . find ( '.app-menu__current-app-glyph' ) . attributes ( 'style' ) )
226+ . toContain ( '/apps/appstore/img/app-dark.svg' )
227+ } )
228+
229+ it ( 'shows the profile page as "Profile" with the generic user icon' , ( ) => {
230+ // The profile app names its entry "View profile" for the account menu
231+ // and ships no icon, so both are replaced here.
232+ mockActiveSettingsEntry ( { id : 'profile' , name : 'View profile' , href : '/u/admin' , icon : '' } )
233+ const wrapper = mount ( AppMenu , { attachTo : document . body } )
234+ expect ( wrapper . find ( '.app-menu__current-app-name' ) . text ( ) ) . toBe ( 'Profile' )
235+ expect ( wrapper . find ( '.app-menu__current-app-glyph' ) . attributes ( 'style' ) )
236+ . toContain ( '/core/img/actions/user.svg' )
237+ } )
238+
239+ it ( 'falls back to the cog for entries without an icon' , ( ) => {
240+ mockActiveSettingsEntry ( { id : 'help' , name : 'Help & privacy' , href : '/settings/help' , icon : '' } )
241+ const wrapper = mount ( AppMenu , { attachTo : document . body } )
242+ expect ( wrapper . find ( '.app-menu__current-app-cog' ) . exists ( ) ) . toBe ( true )
243+ expect ( wrapper . find ( '.app-menu__current-app-glyph' ) . exists ( ) ) . toBe ( false )
244+ } )
245+
205246 it ( 'prefers the active app over a settings entry when both are marked active' , ( ) => {
206247 initialState . loadState . mockImplementation ( ( _a : string , key : string , fallback : unknown ) => {
207248 if ( key === 'apps' ) {
208249 return [ makeApp ( { id : 'files' , name : 'Files' , active : true } ) ]
209250 }
210251 if ( key === 'settingsNavEntries' ) {
211- return { admin_settings : makeApp ( { id : 'admin_settings ' , name : 'Administration settings' , type : 'settings' , active : true } ) }
252+ return { settings_administration : makeApp ( { id : 'settings_administration ' , name : 'Administration settings' , type : 'settings' , active : true } ) }
212253 }
213254 return fallback
214255 } )
0 commit comments