@@ -13,6 +13,7 @@ import userEvent from '@testing-library/user-event';
1313import { waitFor , within } from '@testing-library/vue' ;
1414import { useSettingsStore } from '@/stores/settings.store' ;
1515import { useProjectPages } from '@/composables/useProjectPages' ;
16+ import { useUIStore } from '@/stores/ui.store' ;
1617
1718const mockPush = vi . fn ( ) ;
1819vi . mock ( 'vue-router' , async ( ) => {
@@ -54,6 +55,7 @@ const renderComponent = createComponentRenderer(ProjectHeader, {
5455let route : ReturnType < typeof router . useRoute > ;
5556let projectsStore : ReturnType < typeof mockedStore < typeof useProjectsStore > > ;
5657let settingsStore : ReturnType < typeof mockedStore < typeof useSettingsStore > > ;
58+ let uiStore : ReturnType < typeof mockedStore < typeof useUIStore > > ;
5759let projectPages : ReturnType < typeof useProjectPages > ;
5860
5961describe ( 'ProjectHeader' , ( ) => {
@@ -62,10 +64,18 @@ describe('ProjectHeader', () => {
6264 route = router . useRoute ( ) ;
6365 projectsStore = mockedStore ( useProjectsStore ) ;
6466 settingsStore = mockedStore ( useSettingsStore ) ;
67+ uiStore = mockedStore ( useUIStore ) ;
6568 projectPages = useProjectPages ( ) ;
6669
6770 projectsStore . teamProjectsLimit = - 1 ;
6871 settingsStore . settings . folders = { enabled : false } ;
72+
73+ // Setup default moduleTabs structure
74+ uiStore . moduleTabs = {
75+ shared : { } ,
76+ overview : { } ,
77+ project : { } ,
78+ } ;
6979 } ) ;
7080
7181 afterEach ( ( ) => {
@@ -256,4 +266,174 @@ describe('ProjectHeader', () => {
256266 const { queryByTestId } = renderComponent ( ) ;
257267 expect ( queryByTestId ( 'add-resource-buttons' ) ) . not . toBeInTheDocument ( ) ;
258268 } ) ;
269+
270+ describe ( 'customProjectTabs' , ( ) => {
271+ it ( 'should pass tabs for shared page type when on shared sub page' , ( ) => {
272+ vi . spyOn ( projectPages , 'isSharedSubPage' , 'get' ) . mockReturnValue ( true ) ;
273+ vi . spyOn ( projectPages , 'isOverviewSubPage' , 'get' ) . mockReturnValue ( false ) ;
274+
275+ const mockTabs = [
276+ { value : 'shared-tab-1' , label : 'Shared Tab 1' } ,
277+ { value : 'shared-tab-2' , label : 'Shared Tab 2' } ,
278+ ] ;
279+
280+ uiStore . moduleTabs . shared = {
281+ module1 : mockTabs ,
282+ module2 : [ ] ,
283+ } ;
284+
285+ settingsStore . isModuleActive = vi
286+ . fn ( )
287+ . mockReturnValueOnce ( true ) // module1 is active
288+ . mockReturnValueOnce ( false ) ; // module2 is inactive
289+
290+ renderComponent ( ) ;
291+
292+ expect ( projectTabsSpy ) . toHaveBeenCalledWith (
293+ expect . objectContaining ( {
294+ 'additional-tabs' : mockTabs ,
295+ } ) ,
296+ null ,
297+ ) ;
298+ } ) ;
299+
300+ it ( 'should pass tabs for overview page type when on overview sub page' , ( ) => {
301+ vi . spyOn ( projectPages , 'isSharedSubPage' , 'get' ) . mockReturnValue ( false ) ;
302+ vi . spyOn ( projectPages , 'isOverviewSubPage' , 'get' ) . mockReturnValue ( true ) ;
303+
304+ const mockTabs = [ { value : 'overview-tab-1' , label : 'Overview Tab 1' } ] ;
305+
306+ uiStore . moduleTabs . overview = {
307+ overviewModule : mockTabs ,
308+ } ;
309+
310+ settingsStore . isModuleActive = vi . fn ( ) . mockReturnValue ( true ) ;
311+
312+ renderComponent ( ) ;
313+
314+ expect ( projectTabsSpy ) . toHaveBeenCalledWith (
315+ expect . objectContaining ( {
316+ 'additional-tabs' : mockTabs ,
317+ } ) ,
318+ null ,
319+ ) ;
320+ } ) ;
321+
322+ it ( 'should pass tabs for project page type when not on shared or overview sub pages' , ( ) => {
323+ vi . spyOn ( projectPages , 'isSharedSubPage' , 'get' ) . mockReturnValue ( false ) ;
324+ vi . spyOn ( projectPages , 'isOverviewSubPage' , 'get' ) . mockReturnValue ( false ) ;
325+
326+ const mockTabs = [
327+ { value : 'project-tab-1' , label : 'Project Tab 1' } ,
328+ { value : 'project-tab-2' , label : 'Project Tab 2' } ,
329+ ] ;
330+
331+ uiStore . moduleTabs . project = {
332+ projectModule : mockTabs ,
333+ } ;
334+
335+ settingsStore . isModuleActive = vi . fn ( ) . mockReturnValue ( true ) ;
336+
337+ renderComponent ( ) ;
338+
339+ expect ( projectTabsSpy ) . toHaveBeenCalledWith (
340+ expect . objectContaining ( {
341+ 'additional-tabs' : mockTabs ,
342+ } ) ,
343+ null ,
344+ ) ;
345+ } ) ;
346+
347+ it ( 'should filter out tabs from inactive modules' , ( ) => {
348+ vi . spyOn ( projectPages , 'isSharedSubPage' , 'get' ) . mockReturnValue ( false ) ;
349+ vi . spyOn ( projectPages , 'isOverviewSubPage' , 'get' ) . mockReturnValue ( false ) ;
350+
351+ const activeTabs = [ { value : 'active-tab' , label : 'Active Tab' } ] ;
352+ const inactiveTabs = [ { value : 'inactive-tab' , label : 'Inactive Tab' } ] ;
353+
354+ uiStore . moduleTabs . project = {
355+ activeModule : activeTabs ,
356+ inactiveModule : inactiveTabs ,
357+ } ;
358+
359+ settingsStore . isModuleActive = vi
360+ . fn ( )
361+ . mockImplementation ( ( module : string ) => module === 'activeModule' ) ;
362+
363+ renderComponent ( ) ;
364+
365+ expect ( projectTabsSpy ) . toHaveBeenCalledWith (
366+ expect . objectContaining ( {
367+ 'additional-tabs' : activeTabs ,
368+ } ) ,
369+ null ,
370+ ) ;
371+ } ) ;
372+
373+ it ( 'should flatten tabs from multiple active modules' , ( ) => {
374+ vi . spyOn ( projectPages , 'isSharedSubPage' , 'get' ) . mockReturnValue ( false ) ;
375+ vi . spyOn ( projectPages , 'isOverviewSubPage' , 'get' ) . mockReturnValue ( false ) ;
376+
377+ const module1Tabs = [
378+ { value : 'module1-tab1' , label : 'Module 1 Tab 1' } ,
379+ { value : 'module1-tab2' , label : 'Module 1 Tab 2' } ,
380+ ] ;
381+ const module2Tabs = [ { value : 'module2-tab1' , label : 'Module 2 Tab 1' } ] ;
382+
383+ uiStore . moduleTabs . project = {
384+ module1 : module1Tabs ,
385+ module2 : module2Tabs ,
386+ module3 : [ ] , // Empty tabs array
387+ } ;
388+
389+ settingsStore . isModuleActive = vi . fn ( ) . mockReturnValue ( true ) ;
390+
391+ renderComponent ( ) ;
392+
393+ expect ( projectTabsSpy ) . toHaveBeenCalledWith (
394+ expect . objectContaining ( {
395+ 'additional-tabs' : [ ...module1Tabs , ...module2Tabs ] ,
396+ } ) ,
397+ null ,
398+ ) ;
399+ expect ( settingsStore . isModuleActive ) . toHaveBeenCalledTimes ( 3 ) ;
400+ } ) ;
401+
402+ it ( 'should pass empty array when no modules are active' , ( ) => {
403+ vi . spyOn ( projectPages , 'isSharedSubPage' , 'get' ) . mockReturnValue ( false ) ;
404+ vi . spyOn ( projectPages , 'isOverviewSubPage' , 'get' ) . mockReturnValue ( false ) ;
405+
406+ uiStore . moduleTabs . project = {
407+ module1 : [ { value : 'tab1' , label : 'Tab 1' } ] ,
408+ module2 : [ { value : 'tab2' , label : 'Tab 2' } ] ,
409+ } ;
410+
411+ settingsStore . isModuleActive = vi . fn ( ) . mockReturnValue ( false ) ;
412+
413+ renderComponent ( ) ;
414+
415+ expect ( projectTabsSpy ) . toHaveBeenCalledWith (
416+ expect . objectContaining ( {
417+ 'additional-tabs' : [ ] ,
418+ } ) ,
419+ null ,
420+ ) ;
421+ } ) ;
422+
423+ it ( 'should pass empty array when no modules exist for the tab type' , ( ) => {
424+ vi . spyOn ( projectPages , 'isSharedSubPage' , 'get' ) . mockReturnValue ( false ) ;
425+ vi . spyOn ( projectPages , 'isOverviewSubPage' , 'get' ) . mockReturnValue ( false ) ;
426+
427+ uiStore . moduleTabs . project = { } ; // No modules
428+
429+ renderComponent ( ) ;
430+
431+ expect ( projectTabsSpy ) . toHaveBeenCalledWith (
432+ expect . objectContaining ( {
433+ 'additional-tabs' : [ ] ,
434+ } ) ,
435+ null ,
436+ ) ;
437+ } ) ;
438+ } ) ;
259439} ) ;
0 commit comments