Skip to content

Commit ad432cd

Browse files
test(vue3): add IdDocsValidation view coverage
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent ee18d3d commit ad432cd

1 file changed

Lines changed: 292 additions & 0 deletions

File tree

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 LibreSign contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { flushPromises, mount } from '@vue/test-utils'
8+
9+
import IdDocsValidation from '../../../views/Documents/IdDocsValidation.vue'
10+
import { FILE_STATUS } from '../../../constants.js'
11+
12+
const axiosGetMock = vi.fn()
13+
const axiosDeleteMock = vi.fn()
14+
const showErrorMock = vi.fn()
15+
const openDocumentMock = vi.fn()
16+
const routerPushMock = vi.fn()
17+
const userConfigUpdateMock = vi.fn()
18+
19+
const userConfigStore = {
20+
id_docs_filters: {
21+
owner: '',
22+
status: null,
23+
},
24+
id_docs_sort: {
25+
sortBy: 'owner',
26+
sortOrder: 'DESC',
27+
},
28+
update: vi.fn((...args: unknown[]) => userConfigUpdateMock(...args)),
29+
}
30+
31+
vi.mock('@nextcloud/l10n', () => ({
32+
t: vi.fn((_app: string, text: string) => text),
33+
}))
34+
35+
vi.mock('@nextcloud/axios', () => ({
36+
default: {
37+
get: vi.fn((...args: unknown[]) => axiosGetMock(...args)),
38+
delete: vi.fn((...args: unknown[]) => axiosDeleteMock(...args)),
39+
},
40+
}))
41+
42+
vi.mock('@nextcloud/dialogs', () => ({
43+
showError: vi.fn((...args: unknown[]) => showErrorMock(...args)),
44+
}))
45+
46+
vi.mock('@nextcloud/router', () => ({
47+
generateOcsUrl: vi.fn((path: string, params?: Record<string, string | number>) => {
48+
let resolvedPath = path
49+
for (const [key, value] of Object.entries(params || {})) {
50+
resolvedPath = resolvedPath.replace(`{${key}}`, String(value))
51+
}
52+
return `/ocs/v2.php${resolvedPath}`
53+
}),
54+
}))
55+
56+
vi.mock('vue-router', () => ({
57+
useRouter: vi.fn(() => ({
58+
push: routerPushMock,
59+
})),
60+
}))
61+
62+
vi.mock('../../../store/userconfig.js', () => ({
63+
useUserConfigStore: vi.fn(() => userConfigStore),
64+
}))
65+
66+
vi.mock('../../../utils/viewer.js', () => ({
67+
openDocument: vi.fn((...args: unknown[]) => openDocumentMock(...args)),
68+
}))
69+
70+
vi.mock('@nextcloud/vue/components/NcActions', () => ({
71+
default: { name: 'NcActions', template: '<div class="nc-actions-stub"><slot /><slot name="icon" /></div>' },
72+
}))
73+
74+
vi.mock('@nextcloud/vue/components/NcActionButton', () => ({
75+
default: {
76+
name: 'NcActionButton',
77+
emits: ['click', 'update:modelValue'],
78+
template: '<button class="nc-action-button-stub" @click="$emit(\'click\')"><slot /><slot name="icon" /></button>',
79+
},
80+
}))
81+
82+
vi.mock('@nextcloud/vue/components/NcActionInput', () => ({
83+
default: {
84+
name: 'NcActionInput',
85+
props: ['modelValue', 'label'],
86+
emits: ['update:modelValue'],
87+
template: '<input class="nc-action-input-stub" />',
88+
},
89+
}))
90+
91+
vi.mock('@nextcloud/vue/components/NcActionSeparator', () => ({
92+
default: { name: 'NcActionSeparator', template: '<hr class="nc-action-separator-stub" />' },
93+
}))
94+
95+
vi.mock('@nextcloud/vue/components/NcAvatar', () => ({
96+
default: { name: 'NcAvatar', template: '<div class="nc-avatar-stub" />' },
97+
}))
98+
99+
vi.mock('@nextcloud/vue/components/NcEmptyContent', () => ({
100+
default: { name: 'NcEmptyContent', template: '<div class="nc-empty-content-stub"><slot /><slot name="icon" /></div>' },
101+
}))
102+
103+
vi.mock('@nextcloud/vue/components/NcLoadingIcon', () => ({
104+
default: { name: 'NcLoadingIcon', template: '<span class="nc-loading-icon-stub" />' },
105+
}))
106+
107+
vi.mock('@nextcloud/vue/components/NcIconSvgWrapper', () => ({
108+
default: { name: 'NcIconSvgWrapper', template: '<i class="nc-icon-stub" />' },
109+
}))
110+
111+
describe('IdDocsValidation.vue', () => {
112+
const signedDoc = {
113+
uuid: 'doc-1',
114+
account: {
115+
userId: 'alice',
116+
displayName: 'Alice',
117+
},
118+
file_type: {
119+
type: 'passport',
120+
name: 'Passport',
121+
},
122+
file: {
123+
uuid: 'file-1',
124+
status: FILE_STATUS.SIGNED,
125+
statusText: 'Signed',
126+
name: 'alice-passport.pdf',
127+
file: {
128+
nodeId: 10,
129+
url: '/files/alice-passport.pdf',
130+
},
131+
signers: [{ uid: 'approver', displayName: 'Approver', sign_date: '2026-03-06T12:00:00Z' }],
132+
},
133+
}
134+
135+
const pendingDoc = {
136+
uuid: 'doc-2',
137+
account: {
138+
userId: 'bob',
139+
displayName: 'Bob',
140+
},
141+
file_type: {
142+
type: 'driver-license',
143+
name: 'Driver License',
144+
},
145+
file: {
146+
uuid: 'file-2',
147+
status: FILE_STATUS.ABLE_TO_SIGN,
148+
statusText: 'Pending',
149+
name: 'bob-license.pdf',
150+
file: {
151+
nodeId: 11,
152+
url: '/files/bob-license.pdf',
153+
},
154+
signers: [],
155+
},
156+
}
157+
158+
const createWrapper = () => mount(IdDocsValidation)
159+
160+
beforeEach(() => {
161+
axiosGetMock.mockReset()
162+
axiosDeleteMock.mockReset()
163+
showErrorMock.mockReset()
164+
openDocumentMock.mockReset()
165+
routerPushMock.mockReset()
166+
userConfigUpdateMock.mockReset()
167+
userConfigStore.update.mockClear()
168+
userConfigStore.id_docs_filters = { owner: '', status: null }
169+
userConfigStore.id_docs_sort = { sortBy: 'owner', sortOrder: 'DESC' }
170+
171+
axiosGetMock.mockResolvedValue({
172+
data: {
173+
ocs: {
174+
data: {
175+
data: [signedDoc, pendingDoc],
176+
total: 2,
177+
},
178+
},
179+
},
180+
})
181+
182+
axiosDeleteMock.mockResolvedValue({
183+
data: {
184+
ocs: {
185+
data: {
186+
success: true,
187+
},
188+
},
189+
},
190+
})
191+
})
192+
193+
it('loads documents on mount using saved sort', async () => {
194+
const wrapper = createWrapper()
195+
await flushPromises()
196+
197+
expect(axiosGetMock).toHaveBeenCalledWith('/ocs/v2.php/apps/libresign/api/v1/id-docs/approval/list', {
198+
params: {
199+
page: 1,
200+
length: 50,
201+
sortBy: 'owner',
202+
sortOrder: 'DESC',
203+
},
204+
})
205+
expect(wrapper.vm.documentList).toHaveLength(2)
206+
expect(wrapper.vm.hasMore).toBe(false)
207+
})
208+
209+
it('filters by owner and status and persists filter changes', async () => {
210+
vi.useFakeTimers()
211+
const wrapper = createWrapper()
212+
await flushPromises()
213+
214+
wrapper.vm.filters.owner = 'bob'
215+
wrapper.vm.setStatusFilter('pending', true)
216+
vi.runAllTimers()
217+
await flushPromises()
218+
219+
expect(wrapper.vm.hasActiveFilters).toBe(true)
220+
expect(wrapper.vm.activeFilterCount).toBe(2)
221+
expect(wrapper.vm.filteredDocuments).toEqual([pendingDoc])
222+
expect(userConfigUpdateMock).toHaveBeenCalledWith('id_docs_filters', {
223+
owner: 'bob',
224+
status: {
225+
value: 'pending',
226+
label: 'Pending',
227+
},
228+
})
229+
230+
vi.useRealTimers()
231+
})
232+
233+
it('toggles sort direction and then clears the sort for the same column', async () => {
234+
const wrapper = createWrapper()
235+
await flushPromises()
236+
237+
await wrapper.vm.sortColumn('owner')
238+
expect(wrapper.vm.sortOrder).toBe('ASC')
239+
240+
await wrapper.vm.sortColumn('owner')
241+
expect(wrapper.vm.sortBy).toBeNull()
242+
expect(wrapper.vm.sortOrder).toBeNull()
243+
expect(userConfigUpdateMock).toHaveBeenCalledWith('id_docs_sort', {
244+
sortBy: null,
245+
sortOrder: null,
246+
})
247+
})
248+
249+
it('routes to approve and validation pages using document uuid', async () => {
250+
const wrapper = createWrapper()
251+
await flushPromises()
252+
253+
wrapper.vm.openApprove(pendingDoc)
254+
wrapper.vm.openValidationURL(signedDoc)
255+
256+
expect(routerPushMock).toHaveBeenNthCalledWith(1, {
257+
name: 'IdDocsApprove',
258+
params: { uuid: 'file-2' },
259+
query: { idDocApproval: 'true' },
260+
})
261+
expect(routerPushMock).toHaveBeenNthCalledWith(2, {
262+
name: 'ValidationFile',
263+
params: { uuid: 'file-1' },
264+
})
265+
})
266+
267+
it('opens the file in the viewer and reports missing urls', async () => {
268+
const wrapper = createWrapper()
269+
await flushPromises()
270+
271+
wrapper.vm.openFile(signedDoc)
272+
wrapper.vm.openFile({ file: { file: { nodeId: 12 }, name: 'missing.pdf' } })
273+
274+
expect(openDocumentMock).toHaveBeenCalledWith({
275+
fileUrl: '/files/alice-passport.pdf',
276+
filename: 'alice-passport.pdf',
277+
nodeId: 10,
278+
})
279+
expect(showErrorMock).toHaveBeenCalledWith('File not found')
280+
})
281+
282+
it('deletes a document and reloads the list', async () => {
283+
const wrapper = createWrapper()
284+
await flushPromises()
285+
286+
await wrapper.vm.deleteDocument(signedDoc)
287+
await flushPromises()
288+
289+
expect(axiosDeleteMock).toHaveBeenCalledWith('/ocs/v2.php/apps/libresign/api/v1/id-docs/10')
290+
expect(axiosGetMock).toHaveBeenCalledTimes(2)
291+
})
292+
})

0 commit comments

Comments
 (0)