Skip to content

Commit a59fb23

Browse files
test(FileListFilterStatus): add component tests
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent c3ab3cd commit a59fb23

1 file changed

Lines changed: 189 additions & 0 deletions

File tree

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 LibreCode coop and LibreCode contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { mount } from '@vue/test-utils'
8+
import { setActivePinia } from 'pinia'
9+
import { createTestingPinia } from '@pinia/testing'
10+
11+
import FileListFilterStatus from '../../../../views/FilesList/FileListFilter/FileListFilterStatus.vue'
12+
import { useFiltersStore } from '../../../../store/filters.js'
13+
import { FILE_STATUS } from '../../../../constants.js'
14+
15+
vi.mock('@nextcloud/l10n', () => ({
16+
t: vi.fn((_app: string, text: string) => text),
17+
}))
18+
19+
vi.mock('@nextcloud/logger', () => ({
20+
getLogger: vi.fn(() => ({
21+
error: vi.fn(),
22+
warn: vi.fn(),
23+
info: vi.fn(),
24+
debug: vi.fn(),
25+
})),
26+
getLoggerBuilder: vi.fn(() => ({
27+
setApp: vi.fn().mockReturnThis(),
28+
detectUser: vi.fn().mockReturnThis(),
29+
build: vi.fn(() => ({
30+
error: vi.fn(),
31+
warn: vi.fn(),
32+
info: vi.fn(),
33+
debug: vi.fn(),
34+
})),
35+
})),
36+
}))
37+
38+
vi.mock('@nextcloud/axios', () => ({
39+
default: { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn(), patch: vi.fn() },
40+
}))
41+
42+
vi.mock('@nextcloud/router', () => ({
43+
generateOcsUrl: vi.fn((path: string) => `/ocs/v2.php${path}`),
44+
}))
45+
46+
vi.mock('@nextcloud/initial-state', () => ({
47+
loadState: vi.fn((_app: string, _key: string, defaultValue: unknown) => defaultValue),
48+
}))
49+
50+
vi.mock('@nextcloud/event-bus', () => ({
51+
emit: vi.fn(),
52+
subscribe: vi.fn(),
53+
}))
54+
55+
vi.mock('@nextcloud/vue/components/NcButton', () => ({
56+
default: {
57+
name: 'NcButton',
58+
props: ['variant', 'alignment', 'wide', 'pressed'],
59+
emits: ['click'],
60+
template: '<button class="nc-button-stub" :data-pressed="pressed" :data-variant="variant" @click="$emit(\'click\')"><slot /><slot name="icon" /></button>',
61+
},
62+
}))
63+
64+
vi.mock('@nextcloud/vue/components/NcIconSvgWrapper', () => ({
65+
default: {
66+
name: 'NcIconSvgWrapper',
67+
props: ['path', 'svg', 'size'],
68+
template: '<i class="nc-icon" />',
69+
},
70+
}))
71+
72+
vi.mock('../../../../views/FilesList/FileListFilter/FileListFilter.vue', () => ({
73+
default: {
74+
name: 'FileListFilter',
75+
props: ['isActive', 'filterName'],
76+
emits: ['reset-filter'],
77+
template: '<div class="file-list-filter-stub" :data-is-active="isActive"><slot /><slot name="icon" /></div>',
78+
},
79+
}))
80+
81+
describe('FileListFilterStatus.vue', () => {
82+
beforeEach(() => {
83+
setActivePinia(createTestingPinia({ createSpy: vi.fn }))
84+
})
85+
86+
function mountComponent() {
87+
return mount(FileListFilterStatus)
88+
}
89+
90+
/** Finds a status option button by its visible label */
91+
function findStatusButton(wrapper: ReturnType<typeof mountComponent>, label: string) {
92+
return wrapper.findAll('.nc-button-stub').find((b) => b.text().includes(label))
93+
}
94+
95+
it('renders one button for each file status option', () => {
96+
const wrapper = mountComponent()
97+
// DRAFT, ABLE_TO_SIGN, PARTIAL_SIGNED, SIGNED = 4
98+
expect(wrapper.findAll('.nc-button-stub')).toHaveLength(4)
99+
})
100+
101+
it('isActive is false when no options are selected', () => {
102+
const wrapper = mountComponent()
103+
expect(wrapper.vm.isActive).toBe(false)
104+
})
105+
106+
it('passes isActive=false to FileListFilter when nothing selected', () => {
107+
const wrapper = mountComponent()
108+
expect(wrapper.find('.file-list-filter-stub').attributes('data-is-active')).toBe('false')
109+
})
110+
111+
it('all buttons are unpressed when no options are selected', () => {
112+
const wrapper = mountComponent()
113+
wrapper.findAll('.nc-button-stub').forEach(button => {
114+
expect(button.attributes('data-pressed')).toBe('false')
115+
})
116+
})
117+
118+
it('clicking a status button adds it to selectedOptions', async () => {
119+
const wrapper = mountComponent()
120+
await findStatusButton(wrapper, 'Draft')!.trigger('click')
121+
expect(wrapper.vm.selectedOptions).toContain(FILE_STATUS.DRAFT)
122+
})
123+
124+
it('isActive becomes true after a status is selected', async () => {
125+
const wrapper = mountComponent()
126+
await findStatusButton(wrapper, 'Draft')!.trigger('click')
127+
expect(wrapper.vm.isActive).toBe(true)
128+
})
129+
130+
it('clicked button becomes pressed', async () => {
131+
const wrapper = mountComponent()
132+
const draftButton = findStatusButton(wrapper, 'Draft')!
133+
await draftButton.trigger('click')
134+
expect(draftButton.attributes('data-pressed')).toBe('true')
135+
})
136+
137+
it('clicking a second different status adds it too (multi-select)', async () => {
138+
const wrapper = mountComponent()
139+
await findStatusButton(wrapper, 'Draft')!.trigger('click')
140+
await findStatusButton(wrapper, 'Ready to sign')!.trigger('click')
141+
expect(wrapper.vm.selectedOptions).toContain(FILE_STATUS.DRAFT)
142+
expect(wrapper.vm.selectedOptions).toContain(FILE_STATUS.ABLE_TO_SIGN)
143+
})
144+
145+
it('clicking an already-selected option removes it (toggle)', async () => {
146+
const wrapper = mountComponent()
147+
const draftButton = findStatusButton(wrapper, 'Draft')!
148+
await draftButton.trigger('click') // select
149+
expect(wrapper.vm.selectedOptions).toContain(FILE_STATUS.DRAFT)
150+
await draftButton.trigger('click') // deselect
151+
expect(wrapper.vm.selectedOptions).not.toContain(FILE_STATUS.DRAFT)
152+
})
153+
154+
it('isActive goes back to false when all selections are removed', async () => {
155+
const wrapper = mountComponent()
156+
const draftButton = findStatusButton(wrapper, 'Draft')!
157+
await draftButton.trigger('click')
158+
await draftButton.trigger('click')
159+
expect(wrapper.vm.isActive).toBe(false)
160+
})
161+
162+
it('initialises selectedOptions from filtersStore.filterStatusArray', () => {
163+
const filtersStore = useFiltersStore()
164+
filtersStore.$patch({ filter_status: `[${FILE_STATUS.SIGNED}]` })
165+
166+
const wrapper = mountComponent()
167+
expect(wrapper.vm.selectedOptions).toContain(FILE_STATUS.SIGNED)
168+
})
169+
170+
it('resetFilter clears all selectedOptions', async () => {
171+
const wrapper = mountComponent()
172+
await findStatusButton(wrapper, 'Draft')!.trigger('click')
173+
await findStatusButton(wrapper, 'Ready to sign')!.trigger('click')
174+
expect(wrapper.vm.selectedOptions).toHaveLength(2)
175+
176+
wrapper.vm.resetFilter()
177+
await wrapper.vm.$nextTick()
178+
179+
expect(wrapper.vm.selectedOptions).toHaveLength(0)
180+
})
181+
182+
// createTestingPinia auto-spies all store actions — no vi.spyOn needed
183+
it('watch calls onFilterUpdateChipsAndSave on the store when selectedOptions changes', async () => {
184+
const filtersStore = useFiltersStore()
185+
const wrapper = mountComponent()
186+
await findStatusButton(wrapper, 'Draft')!.trigger('click')
187+
expect(filtersStore.onFilterUpdateChipsAndSave).toHaveBeenCalled()
188+
})
189+
})

0 commit comments

Comments
 (0)