Skip to content

Commit 977de7f

Browse files
committed
Extract mountSidebar helper in ShellSidebar test to reduce boilerplate
Replace 14 repeated inline mount+stubs configurations with a shared mountSidebar() helper. Add explicit existence assertion for the active route test to prevent unclear failures from optional chaining.
1 parent 728657c commit 977de7f

1 file changed

Lines changed: 30 additions & 70 deletions

File tree

frontend/taskdeck-web/src/tests/components/ShellSidebar.spec.ts

Lines changed: 30 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
3645
describe('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

Comments
 (0)