@@ -33,6 +33,15 @@ vi.mock('vue-router', () => ({
3333 useRoute : ( ) => routeMock ,
3434} ) )
3535
36+ const routerLinkStub = { template : '<a><slot /></a>' , props : [ 'to' ] }
37+
38+ function mountSidebar ( overrides ?: { isAuthenticated ?: boolean ; stub ?: Record < string , unknown > } ) {
39+ return mount ( ShellSidebar , {
40+ props : { isAuthenticated : overrides ?. isAuthenticated ?? true } ,
41+ global : { stubs : { 'router-link' : overrides ?. stub ?? routerLinkStub } } ,
42+ } )
43+ }
44+
3645describe ( 'ShellSidebar' , ( ) => {
3746 beforeEach ( ( ) => {
3847 vi . clearAllMocks ( )
@@ -44,19 +53,13 @@ describe('ShellSidebar', () => {
4453 } )
4554
4655 it ( 'renders the Taskdeck brand title' , ( ) => {
47- const wrapper = mount ( ShellSidebar , {
48- props : { isAuthenticated : true } ,
49- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
50- } )
56+ const wrapper = mountSidebar ( )
5157 expect ( wrapper . text ( ) ) . toContain ( 'Taskdeck' )
5258 expect ( wrapper . text ( ) ) . toContain ( 'Precision Mode Active' )
5359 } )
5460
5561 it ( 'renders primary nav items for guided mode' , ( ) => {
56- const wrapper = mount ( ShellSidebar , {
57- props : { isAuthenticated : true } ,
58- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
59- } )
62+ const wrapper = mountSidebar ( )
6063 expect ( wrapper . text ( ) ) . toContain ( 'Home' )
6164 expect ( wrapper . text ( ) ) . toContain ( 'Today' )
6265 expect ( wrapper . text ( ) ) . toContain ( 'Review' )
@@ -66,10 +69,7 @@ describe('ShellSidebar', () => {
6669
6770 it ( 'shows secondary nav items with Workbench Tools section label in guided mode' , ( ) => {
6871 mockWorkspaceStore . mode = 'guided'
69- const wrapper = mount ( ShellSidebar , {
70- props : { isAuthenticated : true } ,
71- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
72- } )
72+ const wrapper = mountSidebar ( )
7373 // In guided mode, items with secondaryModes including 'guided' appear as secondary
7474 expect ( wrapper . text ( ) ) . toContain ( 'Workbench Tools' )
7575 expect ( wrapper . text ( ) ) . toContain ( 'Views' )
@@ -78,10 +78,7 @@ describe('ShellSidebar', () => {
7878
7979 it ( 'promotes all nav items to primary in workbench mode' , ( ) => {
8080 mockWorkspaceStore . mode = 'workbench'
81- const wrapper = mount ( ShellSidebar , {
82- props : { isAuthenticated : true } ,
83- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
84- } )
81+ const wrapper = mountSidebar ( )
8582 // In workbench mode, items with primaryModes=['workbench'] are primary, not secondary
8683 expect ( wrapper . text ( ) ) . toContain ( 'Metrics' )
8784 expect ( wrapper . text ( ) ) . toContain ( 'Activity' )
@@ -90,39 +87,27 @@ describe('ShellSidebar', () => {
9087
9188 it ( 'shows badge count on inbox when inboxBadgeCount > 0' , ( ) => {
9289 mockWorkspaceStore . inboxBadgeCount = 5
93- const wrapper = mount ( ShellSidebar , {
94- props : { isAuthenticated : true } ,
95- global : { stubs : { 'router-link' : { template : '<a :href="to"><slot /></a>' , props : [ 'to' ] } } } ,
96- } )
90+ const wrapper = mountSidebar ( )
9791 const badges = wrapper . findAll ( '.td-nav-badge' )
9892 const inboxBadge = badges . find ( ( b ) => b . text ( ) === '5' )
9993 expect ( inboxBadge ) . toBeDefined ( )
10094 } )
10195
10296 it ( 'shows badge count on review when reviewBadgeCount > 0' , ( ) => {
10397 mockWorkspaceStore . reviewBadgeCount = 3
104- const wrapper = mount ( ShellSidebar , {
105- props : { isAuthenticated : true } ,
106- global : { stubs : { 'router-link' : { template : '<a :href="to"><slot /></a>' , props : [ 'to' ] } } } ,
107- } )
98+ const wrapper = mountSidebar ( )
10899 const badges = wrapper . findAll ( '.td-nav-badge' )
109100 const reviewBadge = badges . find ( ( b ) => b . text ( ) === '3' )
110101 expect ( reviewBadge ) . toBeDefined ( )
111102 } )
112103
113104 it ( 'does not show badges when counts are zero' , ( ) => {
114- const wrapper = mount ( ShellSidebar , {
115- props : { isAuthenticated : true } ,
116- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
117- } )
105+ const wrapper = mountSidebar ( )
118106 expect ( wrapper . findAll ( '.td-nav-badge' ) ) . toHaveLength ( 0 )
119107 } )
120108
121109 it ( 'shows shortcuts button and emits show-keyboard-help on click' , async ( ) => {
122- const wrapper = mount ( ShellSidebar , {
123- props : { isAuthenticated : true } ,
124- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
125- } )
110+ const wrapper = mountSidebar ( )
126111 const shortcutsBtn = wrapper . find ( '.td-nav-item--help' )
127112 expect ( shortcutsBtn . exists ( ) ) . toBe ( true )
128113 expect ( shortcutsBtn . text ( ) ) . toContain ( 'Shortcuts' )
@@ -131,10 +116,7 @@ describe('ShellSidebar', () => {
131116 } )
132117
133118 it ( 'shows logout button when authenticated and emits logout on click' , async ( ) => {
134- const wrapper = mount ( ShellSidebar , {
135- props : { isAuthenticated : true } ,
136- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
137- } )
119+ const wrapper = mountSidebar ( )
138120 const logoutBtn = wrapper . find ( '.td-nav-item--logout' )
139121 expect ( logoutBtn . exists ( ) ) . toBe ( true )
140122 expect ( logoutBtn . attributes ( 'aria-label' ) ) . toBe ( 'Log out' )
@@ -143,18 +125,12 @@ describe('ShellSidebar', () => {
143125 } )
144126
145127 it ( 'hides logout button when not authenticated' , ( ) => {
146- const wrapper = mount ( ShellSidebar , {
147- props : { isAuthenticated : false } ,
148- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
149- } )
128+ const wrapper = mountSidebar ( { isAuthenticated : false } )
150129 expect ( wrapper . find ( '.td-nav-item--logout' ) . exists ( ) ) . toBe ( false )
151130 } )
152131
153132 it ( 'toggles collapsed state when toggle button is clicked' , async ( ) => {
154- const wrapper = mount ( ShellSidebar , {
155- props : { isAuthenticated : true } ,
156- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
157- } )
133+ const wrapper = mountSidebar ( )
158134 expect ( wrapper . find ( '.td-sidebar--collapsed' ) . exists ( ) ) . toBe ( false )
159135
160136 const toggleBtn = wrapper . find ( '.td-sidebar__toggle' )
@@ -170,57 +146,41 @@ describe('ShellSidebar', () => {
170146 if ( flag === 'newAutomation' ) return false
171147 return true
172148 } )
173- const wrapper = mount ( ShellSidebar , {
174- props : { isAuthenticated : true } ,
175- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
176- } )
149+ const wrapper = mountSidebar ( )
177150 expect ( wrapper . text ( ) ) . not . toContain ( 'Review' )
178151 } )
179152
180153 it ( 'shows feature-flagged items in workbench mode even when flag is disabled (workbenchBypassesFlag)' , ( ) => {
181154 mockWorkspaceStore . mode = 'workbench'
182155 mockFeatureFlags . isEnabled = vi . fn ( ( ) => false )
183- const wrapper = mount ( ShellSidebar , {
184- props : { isAuthenticated : true } ,
185- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
186- } )
156+ const wrapper = mountSidebar ( )
187157 // Review has workbenchBypassesFlag=true so it should appear even with flag disabled
188158 expect ( wrapper . text ( ) ) . toContain ( 'Review' )
189159 } )
190160
191161 it ( 'has navigation landmark role' , ( ) => {
192- const wrapper = mount ( ShellSidebar , {
193- props : { isAuthenticated : true } ,
194- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
195- } )
162+ const wrapper = mountSidebar ( )
196163 expect ( wrapper . find ( 'aside' ) . attributes ( 'role' ) ) . toBe ( 'navigation' )
197164 expect ( wrapper . find ( 'aside' ) . attributes ( 'aria-label' ) ) . toBe ( 'Main navigation' )
198165 } )
199166
200167 it ( 'highlights the active route with aria-current' , ( ) => {
201168 routeMock . path = '/workspace/inbox'
202- const wrapper = mount ( ShellSidebar , {
203- props : { isAuthenticated : true } ,
204- global : {
205- stubs : {
206- 'router-link' : {
207- template : '<a :class="{ \'td-nav-item--active\': $attrs.class?.includes(\'td-nav-item--active\') }" :aria-current="$attrs[\'aria-current\']"><slot /></a>' ,
208- props : [ 'to' ] ,
209- } ,
210- } ,
169+ const wrapper = mountSidebar ( {
170+ stub : {
171+ template : '<a :class="{ \'td-nav-item--active\': $attrs.class?.includes(\'td-nav-item--active\') }" :aria-current="$attrs[\'aria-current\']"><slot /></a>' ,
172+ props : [ 'to' ] ,
211173 } ,
212174 } )
213175 // The Inbox nav item should have td-nav-item--active class applied by the component
214176 const navItems = wrapper . findAll ( '.td-nav-item' )
215177 const inboxItem = navItems . find ( ( el ) => el . text ( ) . includes ( 'Inbox' ) )
216- expect ( inboxItem ?. classes ( ) ) . toContain ( 'td-nav-item--active' )
178+ expect ( inboxItem ) . toBeTruthy ( )
179+ expect ( inboxItem ! . classes ( ) ) . toContain ( 'td-nav-item--active' )
217180 } )
218181
219182 it ( 'exposes availableNavItems via defineExpose for command palette' , ( ) => {
220- const wrapper = mount ( ShellSidebar , {
221- props : { isAuthenticated : true } ,
222- global : { stubs : { 'router-link' : { template : '<a><slot /></a>' , props : [ 'to' ] } } } ,
223- } )
183+ const wrapper = mountSidebar ( )
224184 const exposed = ( wrapper . vm as unknown as { availableNavItems : Array < { id : string } > } ) . availableNavItems
225185 expect ( Array . isArray ( exposed ) ) . toBe ( true )
226186 expect ( exposed . length ) . toBeGreaterThan ( 0 )
0 commit comments