Skip to content

Commit 91b2ee8

Browse files
test(sign): add ModalVerificationCode spec
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 8e9e691 commit 91b2ee8

1 file changed

Lines changed: 373 additions & 0 deletions

File tree

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { setActivePinia, createPinia } from 'pinia'
8+
import { mount } from '@vue/test-utils'
9+
import ModalVerificationCode from '@/views/SignPDF/_partials/ModalVerificationCode.vue'
10+
import { useSignMethodsStore } from '@/store/signMethods.js'
11+
12+
// Mock axios
13+
vi.mock('@nextcloud/axios', () => ({
14+
default: vi.fn().mockResolvedValue({ data: { ocs: { data: {} } } }),
15+
post: vi.fn().mockResolvedValue({ data: { ocs: { data: { message: 'Code sent' } } } }),
16+
}))
17+
18+
vi.mock('@nextcloud/router', () => ({
19+
generateOcsUrl: vi.fn((path: string) => `/ocs/v2.php/apps/libresign${path}`),
20+
}))
21+
22+
vi.mock('@nextcloud/initial-state', () => ({
23+
loadState: vi.fn(() => 6),
24+
}))
25+
26+
vi.mock('@nextcloud/dialogs', () => ({
27+
showError: vi.fn(),
28+
showSuccess: vi.fn(),
29+
}))
30+
31+
vi.mock('@nextcloud/password-confirmation', () => ({
32+
confirmPassword: vi.fn().mockResolvedValue(true),
33+
}))
34+
35+
describe('ModalVerificationCode (email mode)', () => {
36+
let wrapper: ReturnType<typeof mount>
37+
let signMethodsStore: ReturnType<typeof useSignMethodsStore>
38+
39+
const stubs = {
40+
NcDialog: { template: '<div><slot /></div>' },
41+
NcTextField: { template: '<input />' },
42+
NcButton: { template: '<button><slot /></button>' },
43+
NcLoadingIcon: { template: '<div />' },
44+
NcIconSvgWrapper: { template: '<div />' },
45+
}
46+
47+
const stubsWithActions = {
48+
...stubs,
49+
NcDialog: { template: '<div><slot name="actions" /></div>' },
50+
}
51+
52+
const stubsWithName = {
53+
...stubs,
54+
NcDialog: { props: ['name'], template: '<div><slot :name="name" /></div>' },
55+
}
56+
57+
const mountEmail = (extraProps = {}) => mount(ModalVerificationCode, {
58+
props: { mode: 'email', ...extraProps },
59+
global: { stubs },
60+
})
61+
62+
beforeEach(() => {
63+
setActivePinia(createPinia())
64+
signMethodsStore = useSignMethodsStore()
65+
signMethodsStore.modal.emailToken = true
66+
signMethodsStore.settings.emailToken = {
67+
hasConfirmCode: false,
68+
hashOfEmail: '5d41402abc4b2a76b9719d911017c592',
69+
blurredEmail: 'u***@email.com',
70+
}
71+
})
72+
73+
it('displays progress indicator on step 1', async () => {
74+
wrapper = mountEmail()
75+
76+
const progressIndicator = wrapper.find('.progress-indicator')
77+
expect(progressIndicator.exists()).toBe(true)
78+
expect(progressIndicator.text()).toContain('Step 1 of 3 - Email verification')
79+
})
80+
81+
it('displays explanatory text on step 1', async () => {
82+
wrapper = mountEmail()
83+
84+
const explanation = wrapper.find('.step-explanation')
85+
expect(explanation.exists()).toBe(true)
86+
expect(explanation.text()).toContain('verify your identity')
87+
expect(explanation.text()).toContain('verification code')
88+
})
89+
90+
it('shows correct dialog title for step 1', async () => {
91+
wrapper = mount(ModalVerificationCode, {
92+
props: { mode: 'email' },
93+
global: { stubs: stubsWithName },
94+
})
95+
96+
expect(wrapper.vm.dialogTitle).toBe('Email verification')
97+
})
98+
99+
it('renders step-content class for styling', async () => {
100+
wrapper = mountEmail()
101+
102+
expect(wrapper.find('.step-content').exists()).toBe(true)
103+
})
104+
105+
it('shows contact on step 2', async () => {
106+
signMethodsStore.settings.emailToken.hasConfirmCode = true
107+
wrapper = mountEmail()
108+
109+
const contactDisplay = wrapper.find('.contact-display')
110+
expect(contactDisplay.exists()).toBe(true)
111+
expect(contactDisplay.text()).toContain('u***@email.com')
112+
})
113+
114+
it('shows correct state on step 1', async () => {
115+
wrapper = mount(ModalVerificationCode, {
116+
props: { mode: 'email' },
117+
global: { stubs: stubsWithActions },
118+
})
119+
120+
expect(wrapper.vm.signMethodsStore.settings.emailToken.hasConfirmCode).toBe(false)
121+
expect(wrapper.vm.identityVerified).toBe(false)
122+
})
123+
124+
it('shows correct state on step 2', async () => {
125+
signMethodsStore.settings.emailToken.hasConfirmCode = true
126+
wrapper = mount(ModalVerificationCode, {
127+
props: { mode: 'email' },
128+
global: { stubs: stubsWithActions },
129+
})
130+
131+
expect(wrapper.vm.signMethodsStore.settings.emailToken.hasConfirmCode).toBe(true)
132+
expect(wrapper.vm.identityVerified).toBe(false)
133+
})
134+
135+
it('updates to step 3 when identityVerified is true', async () => {
136+
signMethodsStore.settings.emailToken.hasConfirmCode = true
137+
wrapper = mountEmail()
138+
139+
wrapper.vm.identityVerified = true
140+
await wrapper.vm.$nextTick()
141+
142+
expect(wrapper.find('.progress-indicator').text()).toContain('Step 3 of 3 - Signature confirmation')
143+
})
144+
145+
it('shows verification success message on step 3', async () => {
146+
signMethodsStore.settings.emailToken.hasConfirmCode = true
147+
wrapper = mountEmail()
148+
149+
wrapper.vm.identityVerified = true
150+
await wrapper.vm.$nextTick()
151+
152+
const verificationSuccess = wrapper.find('.verification-success')
153+
expect(verificationSuccess.exists()).toBe(true)
154+
expect(verificationSuccess.text()).toContain('Your identity has been verified')
155+
expect(verificationSuccess.text()).toContain('You can now sign the document')
156+
})
157+
158+
it('shows correct dialog title on step 3', async () => {
159+
signMethodsStore.settings.emailToken.hasConfirmCode = true
160+
wrapper = mount(ModalVerificationCode, {
161+
props: { mode: 'email' },
162+
global: { stubs: stubsWithActions },
163+
})
164+
165+
wrapper.vm.identityVerified = true
166+
await wrapper.vm.$nextTick()
167+
168+
expect(wrapper.vm.dialogTitle).toBe('Signature confirmation')
169+
})
170+
171+
it('sendCode sets identityVerified to true', async () => {
172+
signMethodsStore.settings.emailToken.hasConfirmCode = true
173+
wrapper = mountEmail()
174+
175+
wrapper.vm.token = '123456'
176+
wrapper.vm.sendCode()
177+
178+
expect(wrapper.vm.identityVerified).toBe(true)
179+
})
180+
181+
it('requestNewCode resets identityVerified', async () => {
182+
wrapper = mountEmail()
183+
184+
wrapper.vm.identityVerified = true
185+
wrapper.vm.requestNewCode()
186+
187+
expect(wrapper.vm.identityVerified).toBe(false)
188+
})
189+
})
190+
191+
describe('ModalVerificationCode (token mode)', () => {
192+
let wrapper: ReturnType<typeof mount>
193+
let signMethodsStore: ReturnType<typeof useSignMethodsStore>
194+
195+
const stubs = {
196+
NcDialog: { template: '<div><slot /></div>' },
197+
NcTextField: { template: '<input />' },
198+
NcButton: { template: '<button><slot /></button>' },
199+
NcLoadingIcon: { template: '<div />' },
200+
NcIconSvgWrapper: { template: '<div />' },
201+
}
202+
203+
const stubsWithActions = {
204+
...stubs,
205+
NcDialog: { template: '<div><slot name="actions" /></div>' },
206+
}
207+
208+
const stubsWithName = {
209+
...stubs,
210+
NcDialog: { props: ['name'], template: '<div><slot :name="name" /></div>' },
211+
}
212+
213+
const mountToken = (extraProps = {}) => mount(ModalVerificationCode, {
214+
props: { mode: 'token', phoneNumber: '', ...extraProps },
215+
global: { stubs },
216+
})
217+
218+
beforeEach(() => {
219+
setActivePinia(createPinia())
220+
signMethodsStore = useSignMethodsStore()
221+
signMethodsStore.modal.token = true
222+
signMethodsStore.settings.smsToken = {
223+
identifyMethod: 'email',
224+
}
225+
})
226+
227+
it('displays progress indicator on step 1', async () => {
228+
wrapper = mountToken()
229+
230+
const progressIndicator = wrapper.find('.progress-indicator')
231+
expect(progressIndicator.exists()).toBe(true)
232+
expect(progressIndicator.text()).toContain('Step 1 of 3 - Identity verification')
233+
})
234+
235+
it('displays generic explanatory text (not phone-specific) on step 1', async () => {
236+
wrapper = mountToken()
237+
238+
const explanation = wrapper.find('.step-explanation')
239+
expect(explanation.exists()).toBe(true)
240+
expect(explanation.text()).toContain('verify your identity')
241+
expect(explanation.text()).toContain('contact information')
242+
expect(explanation.text()).not.toContain('phone')
243+
})
244+
245+
it('shows correct dialog title for step 1', async () => {
246+
wrapper = mount(ModalVerificationCode, {
247+
props: { mode: 'token', phoneNumber: '' },
248+
global: { stubs: stubsWithName },
249+
})
250+
251+
expect(wrapper.vm.dialogTitle).toBe('Identity verification')
252+
})
253+
254+
it('uses generic "Contact information" label, not phone-specific', async () => {
255+
wrapper = mount(ModalVerificationCode, {
256+
props: { mode: 'token', phoneNumber: '' },
257+
global: {
258+
stubs: {
259+
...stubs,
260+
NcTextField: { props: ['label'], template: '<input :label="label" />' },
261+
},
262+
},
263+
})
264+
265+
expect(wrapper.html()).toContain('Contact information')
266+
expect(wrapper.html()).not.toContain('Phone number')
267+
})
268+
269+
it('renders step-content class for styling', async () => {
270+
wrapper = mountToken()
271+
272+
expect(wrapper.find('.step-content').exists()).toBe(true)
273+
})
274+
275+
it('displays progress with correct 3-step numbering', async () => {
276+
wrapper = mountToken()
277+
278+
expect(wrapper.vm.progressText).toContain('of 3')
279+
expect(wrapper.vm.progressText).not.toContain('of 2')
280+
})
281+
282+
it('updates to step 2 when tokenRequested is true', async () => {
283+
wrapper = mountToken({ phoneNumber: '+5511999999999' })
284+
285+
wrapper.vm.tokenRequested = true
286+
await wrapper.vm.$nextTick()
287+
288+
expect(wrapper.find('.progress-indicator').text()).toContain('Step 2 of 3 - Code validation')
289+
})
290+
291+
it('updates to step 3 when identityVerified is true', async () => {
292+
wrapper = mountToken({ phoneNumber: '+5511999999999' })
293+
294+
wrapper.vm.tokenRequested = true
295+
wrapper.vm.identityVerified = true
296+
await wrapper.vm.$nextTick()
297+
298+
expect(wrapper.find('.progress-indicator').text()).toContain('Step 3 of 3 - Signature confirmation')
299+
})
300+
301+
it('shows correct state on step 1', async () => {
302+
wrapper = mount(ModalVerificationCode, {
303+
props: { mode: 'token', phoneNumber: '' },
304+
global: { stubs: stubsWithActions },
305+
})
306+
307+
expect(wrapper.vm.tokenRequested).toBe(false)
308+
expect(wrapper.vm.identityVerified).toBe(false)
309+
})
310+
311+
it('shows correct state on step 2', async () => {
312+
wrapper = mount(ModalVerificationCode, {
313+
props: { mode: 'token', phoneNumber: '+5511999999999' },
314+
global: { stubs: stubsWithActions },
315+
})
316+
317+
wrapper.vm.tokenRequested = true
318+
await wrapper.vm.$nextTick()
319+
320+
expect(wrapper.vm.tokenRequested).toBe(true)
321+
expect(wrapper.vm.identityVerified).toBe(false)
322+
})
323+
324+
it('shows verification success message on step 3', async () => {
325+
wrapper = mountToken({ phoneNumber: '+5511999999999' })
326+
327+
wrapper.vm.tokenRequested = true
328+
wrapper.vm.identityVerified = true
329+
await wrapper.vm.$nextTick()
330+
331+
const verificationSuccess = wrapper.find('.verification-success')
332+
expect(verificationSuccess.exists()).toBe(true)
333+
expect(verificationSuccess.text()).toContain('Your identity has been verified')
334+
expect(verificationSuccess.text()).toContain('You can now sign the document')
335+
})
336+
337+
it('shows correct dialog title on step 3', async () => {
338+
wrapper = mount(ModalVerificationCode, {
339+
props: { mode: 'token', phoneNumber: '+5511999999999' },
340+
global: { stubs: stubsWithActions },
341+
})
342+
343+
wrapper.vm.tokenRequested = true
344+
wrapper.vm.identityVerified = true
345+
await wrapper.vm.$nextTick()
346+
347+
expect(wrapper.vm.dialogTitle).toBe('Signature confirmation')
348+
})
349+
350+
it('sendCode sets identityVerified to true', async () => {
351+
wrapper = mountToken({ phoneNumber: '+5511999999999' })
352+
353+
wrapper.vm.tokenRequested = true
354+
wrapper.vm.token = '123456'
355+
wrapper.vm.sendCode()
356+
357+
expect(wrapper.vm.identityVerified).toBe(true)
358+
})
359+
360+
it('requestNewCode resets tokenRequested state', async () => {
361+
wrapper = mountToken({ phoneNumber: '+5511999999999' })
362+
363+
wrapper.vm.tokenRequested = true
364+
wrapper.vm.token = '123456'
365+
wrapper.vm.identityVerified = true
366+
367+
wrapper.vm.requestNewCode()
368+
369+
expect(wrapper.vm.tokenRequested).toBe(false)
370+
expect(wrapper.vm.token).toBe('')
371+
expect(wrapper.vm.identityVerified).toBe(false)
372+
})
373+
})

0 commit comments

Comments
 (0)