@@ -44,27 +44,21 @@ const mountLayout = (configOverrides = {}, routePath = '/admin/users') =>
4444 stubs : {
4545 RouterLink : true ,
4646 RouterView : { template : '<div class="router-view-stub" />' } ,
47- PageHeader : {
48- name : 'PageHeader' ,
49- props : [ 'title' , 'icon' ] ,
47+ // Single stub for the new bundled header-with-tabs primitive — exposes
48+ // all the props admin.layout passes through, plus the breadcrumb slot.
49+ CorePageHeaderTabs : {
50+ name : 'CorePageHeaderTabs' ,
51+ props : [ 'title' , 'icon' , 'tabs' , 'can' , 'basePath' , 'hideTabs' ] ,
5052 template : `
51- <div class="page-header-stub" :data-title="title" :data-icon="icon">
53+ <div class="page-header-tabs- stub" :data-title="title" :data-icon="icon" :data-hide-tabs="hideTabs ">
5254 <slot name="avatar" />
5355 <slot name="breadcrumb" />
54- <slot name="tabs" />
5556 <slot name="title" />
5657 <slot name="subtitle" />
5758 <slot name="actions" />
5859 </div>
5960 ` ,
6061 } ,
61- // Stub SurfaceTabBar so we can read the props it receives without rendering full v-tabs.
62- // name: 'CoreSurfaceTabBar' is required for findComponent({ name: ... }) to work.
63- CoreSurfaceTabBar : {
64- name : 'CoreSurfaceTabBar' ,
65- props : [ 'tabs' , 'can' , 'basePath' ] ,
66- template : '<div class="surface-tab-bar-stub" :data-tabs-count="tabs?.length || 0" />' ,
67- } ,
6862 } ,
6963 } ,
7064 } ) ;
@@ -79,24 +73,24 @@ describe('admin.layout', () => {
7973
8074 it ( 'renders the page header' , ( ) => {
8175 const wrapper = mountLayout ( ) ;
82- expect ( wrapper . find ( '.page-header-stub' ) . exists ( ) ) . toBe ( true ) ;
76+ expect ( wrapper . find ( '.page-header-tabs- stub' ) . exists ( ) ) . toBe ( true ) ;
8377 } ) ;
8478
8579 it ( 'renders a <router-view> for nested admin content' , ( ) => {
8680 const wrapper = mountLayout ( ) ;
8781 expect ( wrapper . find ( '.router-view-stub' ) . exists ( ) ) . toBe ( true ) ;
8882 } ) ;
8983
90- it ( 'passes the four built-in tabs to CoreSurfaceTabBar when no extras are configured' , ( ) => {
84+ it ( 'passes the four built-in tabs to CorePageHeaderTabs when no extras are configured' , ( ) => {
9185 const wrapper = mountLayout ( ) ;
92- const bar = wrapper . findComponent ( { name : 'CoreSurfaceTabBar ' } ) ;
93- expect ( bar . exists ( ) ) . toBe ( true ) ;
94- const tabs = bar . props ( 'tabs' ) ;
86+ const headerTabs = wrapper . findComponent ( { name : 'CorePageHeaderTabs ' } ) ;
87+ expect ( headerTabs . exists ( ) ) . toBe ( true ) ;
88+ const tabs = headerTabs . props ( 'tabs' ) ;
9589 expect ( tabs ) . toHaveLength ( 4 ) ;
9690 expect ( tabs . map ( ( t ) => t . value ) ) . toEqual ( [ 'users' , 'organizations' , 'readiness' , 'activity' ] ) ;
9791 } ) ;
9892
99- it ( 'passes built-in + extra tabs from config.admin.tabs to CoreSurfaceTabBar ' , ( ) => {
93+ it ( 'passes built-in + extra tabs from config.admin.tabs to CorePageHeaderTabs ' , ( ) => {
10094 const wrapper = mountLayout ( {
10195 admin : {
10296 tabs : [
@@ -105,45 +99,45 @@ describe('admin.layout', () => {
10599 ] ,
106100 } ,
107101 } ) ;
108- const bar = wrapper . findComponent ( { name : 'CoreSurfaceTabBar ' } ) ;
109- const tabs = bar . props ( 'tabs' ) ;
102+ const headerTabs = wrapper . findComponent ( { name : 'CorePageHeaderTabs ' } ) ;
103+ const tabs = headerTabs . props ( 'tabs' ) ;
110104 expect ( tabs ) . toHaveLength ( 6 ) ;
111105 expect ( tabs [ 4 ] . value ) . toBe ( 'knowledge' ) ;
112106 expect ( tabs [ 5 ] . value ) . toBe ( 'costs' ) ;
113107 } ) ;
114108
115- it ( 'passes basePath="/admin" to CoreSurfaceTabBar ' , ( ) => {
109+ it ( 'passes basePath="/admin" to CorePageHeaderTabs ' , ( ) => {
116110 const wrapper = mountLayout ( ) ;
117- const bar = wrapper . findComponent ( { name : 'CoreSurfaceTabBar ' } ) ;
118- expect ( bar . props ( 'basePath' ) ) . toBe ( '/admin' ) ;
111+ const headerTabs = wrapper . findComponent ( { name : 'CorePageHeaderTabs ' } ) ;
112+ expect ( headerTabs . props ( 'basePath' ) ) . toBe ( '/admin' ) ;
119113 } ) ;
120114
121- it ( 'passes a function `can` predicate to CoreSurfaceTabBar ' , ( ) => {
115+ it ( 'passes a function `can` predicate to CorePageHeaderTabs ' , ( ) => {
122116 const wrapper = mountLayout ( ) ;
123- const bar = wrapper . findComponent ( { name : 'CoreSurfaceTabBar ' } ) ;
124- expect ( typeof bar . props ( 'can' ) ) . toBe ( 'function' ) ;
117+ const headerTabs = wrapper . findComponent ( { name : 'CorePageHeaderTabs ' } ) ;
118+ expect ( typeof headerTabs . props ( 'can' ) ) . toBe ( 'function' ) ;
125119 } ) ;
126120
127- it ( 'gracefully handles non-array admin.tabs (CoreSurfaceTabBar receives only the built-in 4)' , ( ) => {
121+ it ( 'gracefully handles non-array admin.tabs (CorePageHeaderTabs receives only the built-in 4)' , ( ) => {
128122 const wrapper = mountLayout ( { admin : { tabs : 'invalid' } } ) ;
129- const bar = wrapper . findComponent ( { name : 'CoreSurfaceTabBar ' } ) ;
130- expect ( bar . props ( 'tabs' ) ) . toHaveLength ( 4 ) ;
123+ const headerTabs = wrapper . findComponent ( { name : 'CorePageHeaderTabs ' } ) ;
124+ expect ( headerTabs . props ( 'tabs' ) ) . toHaveLength ( 4 ) ;
131125 } ) ;
132126
133127 it ( 'renders the error banner at the TOP of the layout, before the header' , async ( ) => {
134128 adminStoreState . error = 'Boom' ;
135129 const wrapper = mountLayout ( ) ;
136130 await wrapper . vm . $nextTick ( ) ;
137131 const html = wrapper . html ( ) ;
138- expect ( html . indexOf ( 'Boom' ) ) . toBeLessThan ( html . indexOf ( 'page-header-stub' ) ) ;
132+ expect ( html . indexOf ( 'Boom' ) ) . toBeLessThan ( html . indexOf ( 'page-header-tabs- stub' ) ) ;
139133 } ) ;
140134
141135 it ( 'renders the mailer warning at the TOP when serverConfig.mail.configured is false' , async ( ) => {
142136 authStoreState . serverConfig = { mail : { configured : false } } ;
143137 const wrapper = mountLayout ( ) ;
144138 await wrapper . vm . $nextTick ( ) ;
145139 const html = wrapper . html ( ) ;
146- expect ( html . indexOf ( 'No mailer configured' ) ) . toBeLessThan ( html . indexOf ( 'page-header-stub' ) ) ;
140+ expect ( html . indexOf ( 'No mailer configured' ) ) . toBeLessThan ( html . indexOf ( 'page-header-tabs- stub' ) ) ;
147141 } ) ;
148142
149143 it ( 'does NOT render the mailer warning when mail is configured' , ( ) => {
@@ -152,16 +146,6 @@ describe('admin.layout', () => {
152146 expect ( wrapper . html ( ) ) . not . toContain ( 'No mailer configured' ) ;
153147 } ) ;
154148
155- it ( 'renders <CoreSurfaceTabBar> as a SIBLING of <PageHeader> (not inside its #tabs slot)' , ( ) => {
156- const wrapper = mountLayout ( ) ;
157- const pageHeaderEl = wrapper . find ( '.page-header-stub' ) ;
158- const surfaceTabBarEl = wrapper . find ( '.surface-tab-bar-stub' ) ;
159- expect ( pageHeaderEl . exists ( ) ) . toBe ( true ) ;
160- expect ( surfaceTabBarEl . exists ( ) ) . toBe ( true ) ;
161- // Sibling layout: the tab-bar element is NOT inside the page-header-stub element.
162- expect ( pageHeaderEl . element . contains ( surfaceTabBarEl . element ) ) . toBe ( false ) ;
163- } ) ;
164-
165149 it ( 'renders <router-view> OUTSIDE the layout <v-container>' , ( ) => {
166150 const wrapper = mountLayout ( ) ;
167151 const layoutContainer = wrapper . find ( '.v-container' ) ;
@@ -171,32 +155,34 @@ describe('admin.layout', () => {
171155 expect ( layoutContainer . element . contains ( routerViewEl . element ) ) . toBe ( false ) ;
172156 } ) ;
173157
174- it ( 'PageHeader receives title="Admin" + icon="fa-solid fa-user-tie" in list mode' , ( ) => {
158+ it ( 'CorePageHeaderTabs receives title="Admin" + icon="fa-solid fa-user-tie" in list mode' , ( ) => {
175159 const wrapper = mountLayout ( ) ;
176- const ph = wrapper . findComponent ( { name : 'PageHeader ' } ) ;
177- expect ( ph . props ( 'title' ) ) . toBe ( 'Admin' ) ;
178- expect ( ph . props ( 'icon' ) ) . toBe ( 'fa-solid fa-user-tie' ) ;
160+ const headerTabs = wrapper . findComponent ( { name : 'CorePageHeaderTabs ' } ) ;
161+ expect ( headerTabs . props ( 'title' ) ) . toBe ( 'Admin' ) ;
162+ expect ( headerTabs . props ( 'icon' ) ) . toBe ( 'fa-solid fa-user-tie' ) ;
179163 } ) ;
180164
181- it ( 'PageHeader receives title="" + icon stays in breadcrumb mode' , async ( ) => {
165+ it ( 'CorePageHeaderTabs receives title="" + icon stays in breadcrumb mode' , async ( ) => {
182166 adminStoreState . currentBreadcrumb = { title : 'Jane Doe' } ;
183167 const wrapper = mountLayout ( { } , '/admin/users/u1' ) ;
184168 await wrapper . vm . $nextTick ( ) ;
185- const ph = wrapper . findComponent ( { name : 'PageHeader ' } ) ;
186- expect ( ph . props ( 'title' ) ) . toBe ( '' ) ;
187- expect ( ph . props ( 'icon' ) ) . toBe ( 'fa-solid fa-user-tie' ) ;
169+ const headerTabs = wrapper . findComponent ( { name : 'CorePageHeaderTabs ' } ) ;
170+ expect ( headerTabs . props ( 'title' ) ) . toBe ( '' ) ;
171+ expect ( headerTabs . props ( 'icon' ) ) . toBe ( 'fa-solid fa-user-tie' ) ;
188172 } ) ;
189173
190- it ( 'does NOT render <CoreSurfaceTabBar> when currentBreadcrumb is set (detail mode)' , async ( ) => {
174+ it ( 'passes hideTabs=true when currentBreadcrumb is set (detail mode)' , async ( ) => {
191175 adminStoreState . currentBreadcrumb = { title : 'Jane Doe' } ;
192176 const wrapper = mountLayout ( { } , '/admin/users/u1' ) ;
193177 await wrapper . vm . $nextTick ( ) ;
194- expect ( wrapper . findComponent ( { name : 'CoreSurfaceTabBar' } ) . exists ( ) ) . toBe ( false ) ;
178+ const headerTabs = wrapper . findComponent ( { name : 'CorePageHeaderTabs' } ) ;
179+ expect ( headerTabs . props ( 'hideTabs' ) ) . toBe ( true ) ;
195180 } ) ;
196181
197- it ( 'renders <CoreSurfaceTabBar> when currentBreadcrumb is null (list mode)' , ( ) => {
182+ it ( 'passes hideTabs=false when currentBreadcrumb is null (list mode)' , ( ) => {
198183 const wrapper = mountLayout ( ) ;
199- expect ( wrapper . findComponent ( { name : 'CoreSurfaceTabBar' } ) . exists ( ) ) . toBe ( true ) ;
184+ const headerTabs = wrapper . findComponent ( { name : 'CorePageHeaderTabs' } ) ;
185+ expect ( headerTabs . props ( 'hideTabs' ) ) . toBe ( false ) ;
200186 } ) ;
201187
202188 it ( 'renders the breadcrumb when useAdminStore().currentBreadcrumb is set' , async ( ) => {
0 commit comments