From 054cb3e34d15c5920f1a53611bc9937da9979e79 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:53:40 -0300 Subject: [PATCH 01/27] feat: Add Step 3 - Signature confirmation to ModalEmailManager - Add identityVerified state to control Step 3 visibility - Update dialogTitle computed property for Step 3 title - Update progressText to show 'Step 3 of 3 - Signature confirmation' - Modify sendCode() to set identityVerified = true - Add signDocument() method to trigger signature - Add requestNewCode() to reset identityVerified state - Add CSS styling for verification success message block Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../SignPDF/_partials/ModalEmailManager.vue | 181 +++++++++++++++--- 1 file changed, 151 insertions(+), 30 deletions(-) diff --git a/src/views/SignPDF/_partials/ModalEmailManager.vue b/src/views/SignPDF/_partials/ModalEmailManager.vue index bf7a3072b7..6839410184 100644 --- a/src/views/SignPDF/_partials/ModalEmailManager.vue +++ b/src/views/SignPDF/_partials/ModalEmailManager.vue @@ -5,13 +5,41 @@ @@ -125,8 +169,34 @@ export default { errorMessage: '', token: '', sendTo: '', + identityVerified: false, }), computed: { + dialogTitle() { + if (!this.signMethodsStore.settings.emailToken.hasConfirmCode) { + return t('libresign', 'Email verification') + } + if (!this.identityVerified) { + return t('libresign', 'Code validation') + } + return t('libresign', 'Signature confirmation') + }, + progressText() { + if (!this.signMethodsStore.settings.emailToken.hasConfirmCode) { + return t('libresign', 'Step 1 of 3 - Email verification') + } + if (!this.identityVerified) { + return t('libresign', 'Step 2 of 3 - Code validation') + } + return t('libresign', 'Step 3 of 3 - Signature confirmation') + }, + codeExplanationText() { + const email = this.displayEmail + return t('libresign', 'A verification code has been sent to: {email}. Please enter the 6-digit code below.', { email }) + }, + displayEmail() { + return this.signMethodsStore.blurredEmail() || this.sendTo + }, canRequestCode() { if (validateEmail(this.sendTo)) { if (md5(this.sendTo.toLowerCase()) !== this.signMethodsStore.settings.emailToken.hashOfEmail) { @@ -160,6 +230,7 @@ export default { requestNewCode() { this.signMethodsStore.setHasEmailConfirmCode(false) this.signMethodsStore.setEmailToken('') + this.identityVerified = false }, async requestCode() { this.loading = true @@ -210,6 +281,9 @@ export default { if (!this.canSendCode) { return } + this.identityVerified = true + }, + signDocument() { this.$emit('change') this.close() }, @@ -224,8 +298,55 @@ export default { From 17ac8f0e5e03e4203aa0857692d5234b80c318f0 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:53:46 -0300 Subject: [PATCH 02/27] feat: Add Step 3 - Signature confirmation to ModalTokenManager - Add identityVerified state to control Step 3 visibility - Update dialogTitle computed property for Step 3 title - Update progressText to show 'Step 3 of 3 - Signature confirmation' - Modify sendToken() to set identityVerified = true - Add signDocument() method to trigger signature - Add requestNewToken() to reset identityVerified state - Add CSS styling for verification success message block - Match ModalEmailManager implementation for consistency Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../SignPDF/_partials/ModalTokenManager.vue | 168 ++++++++++++++++-- 1 file changed, 150 insertions(+), 18 deletions(-) diff --git a/src/views/SignPDF/_partials/ModalTokenManager.vue b/src/views/SignPDF/_partials/ModalTokenManager.vue index ecde2cd3d1..29a5f1f310 100644 --- a/src/views/SignPDF/_partials/ModalTokenManager.vue +++ b/src/views/SignPDF/_partials/ModalTokenManager.vue @@ -4,38 +4,98 @@ --> - {{ t('libresign', 'Sign the document.') }} + {{ t('libresign', 'Sign document') }} @@ -247,8 +257,10 @@ export default { const visibleElements = getVisibleElementsFromDocument(document) .filter(row => { - return this.signatureElementsStore.hasSignatureOfType(row.type) - && signRequestIds.has(String(row.signRequestId)) + // Access signatureElementsStore.signs[row.type] directly to ensure reactivity + const signatureData = this.signatureElementsStore.signs[row.type] + const hasSignature = signatureData && signatureData.createdAt && signatureData.createdAt.length > 0 + return hasSignature && signRequestIds.has(String(row.signRequestId)) }) return visibleElements }, @@ -544,6 +556,29 @@ export default { From 6f2630f5eb9756babd9468a287dc24fbba6184de Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:55:17 -0300 Subject: [PATCH 10/27] refactor(sign): remove ModalTokenManager replaced by ModalVerificationCode Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../SignPDF/_partials/ModalTokenManager.vue | 340 ------------------ 1 file changed, 340 deletions(-) delete mode 100644 src/views/SignPDF/_partials/ModalTokenManager.vue diff --git a/src/views/SignPDF/_partials/ModalTokenManager.vue b/src/views/SignPDF/_partials/ModalTokenManager.vue deleted file mode 100644 index 29a5f1f310..0000000000 --- a/src/views/SignPDF/_partials/ModalTokenManager.vue +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - From 50fed0f1bfcdbe4c8a0c9fe5328a7640d545372f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:55:17 -0300 Subject: [PATCH 11/27] feat(sign): add unified ModalVerificationCode component Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../_partials/ModalVerificationCode.vue | 447 ++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 src/views/SignPDF/_partials/ModalVerificationCode.vue diff --git a/src/views/SignPDF/_partials/ModalVerificationCode.vue b/src/views/SignPDF/_partials/ModalVerificationCode.vue new file mode 100644 index 0000000000..4ea2a0d887 --- /dev/null +++ b/src/views/SignPDF/_partials/ModalVerificationCode.vue @@ -0,0 +1,447 @@ + + + + + + From f6a51fb351b1690db995877f1ccb3a0454ecee24 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:55:18 -0300 Subject: [PATCH 12/27] refactor(sign): use ModalVerificationCode instead of separate modals Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/views/SignPDF/_partials/Sign.vue | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/views/SignPDF/_partials/Sign.vue b/src/views/SignPDF/_partials/Sign.vue index 06aab6546b..f00ed830e7 100644 --- a/src/views/SignPDF/_partials/Sign.vue +++ b/src/views/SignPDF/_partials/Sign.vue @@ -144,12 +144,14 @@ :useModal="true" :errors="signStore.errors" @certificate:uploaded="onSignatureFileCreated" /> - - @@ -172,8 +174,7 @@ import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' import NcPasswordField from '@nextcloud/vue/components/NcPasswordField' import NcRichText from '@nextcloud/vue/components/NcRichText' -import EmailManager from './ModalEmailManager.vue' -import TokenManager from './ModalTokenManager.vue' +import ModalVerificationCode from './ModalVerificationCode.vue' import Draw from '../../../components/Draw/Draw.vue' import Documents from '../../../views/Account/partials/Documents.vue' import Signatures from '../../../views/Account/partials/Signatures.vue' @@ -201,8 +202,7 @@ export default { NcPasswordField, NcRichText, CreatePassword, - TokenManager, - EmailManager, + ModalVerificationCode, UploadCertificate, Documents, Signatures, @@ -439,12 +439,14 @@ export default { await this.submitSignature({ method: identifyMethod, + modalCode: 'token', token, }) }, async signWithEmailToken() { await this.submitSignature({ method: this.signMethodsStore.settings.emailToken.identifyMethod, + modalCode: 'emailToken', token: this.signMethodsStore.settings.emailToken.token, }) }, @@ -485,13 +487,13 @@ export default { ) if (result.status === 'signingInProgress') { - this.actionHandler.closeModal(methodConfig.method) + this.actionHandler.closeModal(methodConfig.modalCode || methodConfig.method) this.$emit('signing-started', { signRequestUuid: this.signRequestUuid, async: true, }) } else if (result.status === 'signed') { - this.actionHandler.closeModal(methodConfig.method) + this.actionHandler.closeModal(methodConfig.modalCode || methodConfig.method) this.sidebarStore.hideSidebar() this.$emit('signed', { ...result.data, From c5eba38366836db25b91e675c0f31adeae6dbaec Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:55:18 -0300 Subject: [PATCH 13/27] test(sign): remove ModalEmailManager.spec.js Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../_partials/ModalEmailManager.spec.js | 307 ------------------ 1 file changed, 307 deletions(-) delete mode 100644 src/tests/views/SignPDF/_partials/ModalEmailManager.spec.js diff --git a/src/tests/views/SignPDF/_partials/ModalEmailManager.spec.js b/src/tests/views/SignPDF/_partials/ModalEmailManager.spec.js deleted file mode 100644 index c9368ef467..0000000000 --- a/src/tests/views/SignPDF/_partials/ModalEmailManager.spec.js +++ /dev/null @@ -1,307 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { setActivePinia, createPinia } from 'pinia' -import { mount } from '@vue/test-utils' -import ModalEmailManager from '@/views/SignPDF/_partials/ModalEmailManager.vue' -import { useSignMethodsStore } from '@/store/signMethods.js' - -// Mock axios -vi.mock('@nextcloud/axios', () => ({ - default: vi.fn().mockResolvedValue({ data: { ocs: { data: {} } } }), - post: vi.fn().mockResolvedValue({ data: { ocs: { data: { message: 'Code sent' } } } }), -})) - -vi.mock('@nextcloud/router', () => ({ - generateOcsUrl: vi.fn((path) => `/ocs/v2.php/apps/libresign${path}`), -})) - -vi.mock('@nextcloud/initial-state', () => ({ - loadState: vi.fn(() => 6), -})) - -vi.mock('@nextcloud/dialogs', () => ({ - showError: vi.fn(), - showSuccess: vi.fn(), -})) - -describe('ModalEmailManager - UX Improvements', () => { - let wrapper - let signMethodsStore - - beforeEach(() => { - setActivePinia(createPinia()) - signMethodsStore = useSignMethodsStore() - signMethodsStore.modal.emailToken = true - signMethodsStore.settings.emailToken = { - hasConfirmCode: false, - hashOfEmail: '5d41402abc4b2a76b9719d911017c592', - blurredEmail: 'u***@email.com', - } - }) - - it('displays progress indicator on step 1', async () => { - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - const progressIndicator = wrapper.find('.progress-indicator') - expect(progressIndicator.exists()).toBe(true) - expect(progressIndicator.text()).toContain('Step 1 of 3 - Email verification') - }) - - it('displays explanatory text on step 1', async () => { - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - const explanation = wrapper.find('.step-explanation') - expect(explanation.exists()).toBe(true) - expect(explanation.text()).toContain('verify your identity') - expect(explanation.text()).toContain('verification code') - }) - - it('shows correct dialog title for step 1', async () => { - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - expect(wrapper.vm.dialogTitle).toBe('Email verification') - }) - - it('updates to step 3 when identityVerified is true', async () => { - signMethodsStore.settings.emailToken.hasConfirmCode = true - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.identityVerified = true - await wrapper.vm.$nextTick() - - const progressIndicator = wrapper.find('.progress-indicator') - expect(progressIndicator.text()).toContain('Step 3 of 3 - Signature confirmation') - }) - - it('shows email address on step 2', async () => { - signMethodsStore.settings.emailToken.hasConfirmCode = true - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - const emailDisplay = wrapper.find('.email-display') - expect(emailDisplay.exists()).toBe(true) - expect(emailDisplay.text()).toContain('u***@email.com') - }) - - it('shows correct button label on step 1', async () => { - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - expect(wrapper.vm.signMethodsStore.settings.emailToken.hasConfirmCode).toBe(false) - expect(wrapper.vm.identityVerified).toBe(false) - }) - - it('shows correct button label on step 2', async () => { - signMethodsStore.settings.emailToken.hasConfirmCode = true - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - expect(wrapper.vm.signMethodsStore.settings.emailToken.hasConfirmCode).toBe(true) - expect(wrapper.vm.identityVerified).toBe(false) - }) - - it('renders step-content class for styling', async () => { - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - const stepContent = wrapper.find('.step-content') - expect(stepContent.exists()).toBe(true) - }) - - it('updates to step 3 when identityVerified is true', async () => { - signMethodsStore.settings.emailToken.hasConfirmCode = true - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.identityVerified = true - await wrapper.vm.$nextTick() - - const progressIndicator = wrapper.find('.progress-indicator') - expect(progressIndicator.text()).toContain('Step 3 of 3 - Signature confirmation') - }) - - it('shows verification success message on step 3', async () => { - signMethodsStore.settings.emailToken.hasConfirmCode = true - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.identityVerified = true - await wrapper.vm.$nextTick() - - const verificationSuccess = wrapper.find('.verification-success') - expect(verificationSuccess.exists()).toBe(true) - expect(verificationSuccess.text()).toContain('Your identity has been verified') - expect(verificationSuccess.text()).toContain('You can now sign the document') - }) - - it('shows correct button label on step 3', async () => { - signMethodsStore.settings.emailToken.hasConfirmCode = true - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.identityVerified = true - await wrapper.vm.$nextTick() - - expect(wrapper.vm.identityVerified).toBe(true) - expect(wrapper.vm.dialogTitle).toBe('Signature confirmation') - }) - - it('sendCode sets identityVerified to true', async () => { - signMethodsStore.settings.emailToken.hasConfirmCode = true - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.token = '123456' - wrapper.vm.sendCode() - - expect(wrapper.vm.identityVerified).toBe(true) - }) - - it('requestNewCode resets identityVerified', async () => { - wrapper = mount(ModalEmailManager, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - EmailIcon: { template: '
' }, - FormTextboxPasswordIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.identityVerified = true - wrapper.vm.requestNewCode() - - expect(wrapper.vm.identityVerified).toBe(false) - }) -}) From bb9f1b8a88bc398aab48eebe31c88151bd3185a9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:55:18 -0300 Subject: [PATCH 14/27] test(sign): remove ModalEmailManager.spec.ts Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../_partials/ModalEmailManager.spec.ts | 79 ------------------- 1 file changed, 79 deletions(-) delete mode 100644 src/tests/views/SignPDF/_partials/ModalEmailManager.spec.ts diff --git a/src/tests/views/SignPDF/_partials/ModalEmailManager.spec.ts b/src/tests/views/SignPDF/_partials/ModalEmailManager.spec.ts deleted file mode 100644 index cd01207263..0000000000 --- a/src/tests/views/SignPDF/_partials/ModalEmailManager.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2026 LibreSign contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' -import { mount } from '@vue/test-utils' - -const loadStateMock = vi.fn() - -vi.mock('@nextcloud/initial-state', () => ({ - loadState: (...args: unknown[]) => loadStateMock(...args), -})) - -vi.mock('../../../../store/sign.js', () => ({ - useSignStore: () => ({ - document: { - fileId: 1, - signers: [], - }, - }), -})) - -vi.mock('../../../../store/signMethods.js', () => ({ - useSignMethodsStore: () => ({ - settings: { - emailToken: { - hasConfirmCode: false, - hashOfEmail: '', - identifyMethod: 'email', - }, - }, - blurredEmail: () => '', - setEmailToken: vi.fn(), - setHasEmailConfirmCode: vi.fn(), - }), -})) - -vi.mock('@nextcloud/l10n', () => ({ - t: vi.fn((_app: string, text: string) => text), - translate: vi.fn((_app: string, text: string) => text), - translatePlural: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)), - n: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)), - isRTL: vi.fn(() => false), - getLanguage: vi.fn(() => 'en'), - getLocale: vi.fn(() => 'en'), -})) - -let ModalEmailManager: unknown - -beforeAll(async () => { - ;({ default: ModalEmailManager } = await import('../../../../views/SignPDF/_partials/ModalEmailManager.vue')) -}) - -describe('ModalEmailManager', () => { - beforeEach(() => { - loadStateMock.mockReset() - }) - - it('registers icon wrapper and exposes mdi icon paths used in template', () => { - loadStateMock.mockImplementation((_app: string, _key: string, fallback: unknown) => fallback) - - const wrapper = mount(ModalEmailManager as never, { - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '
' }, - NcButton: { template: '' }, - NcLoadingIcon: true, - NcIconSvgWrapper: { name: 'NcIconSvgWrapper', props: ['path'], template: '' }, - }, - }, - }) - - expect(wrapper.vm.$options.components.NcIconSvgWrapper).toBeTruthy() - expect(wrapper.vm.mdiFormTextboxPassword).toBeTruthy() - expect(wrapper.vm.mdiEmail).toBeTruthy() - }) -}) From 62dd85964f2e696568999e6cc336895c92662078 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:55:18 -0300 Subject: [PATCH 15/27] test(sign): remove ModalTokenManager.spec.js Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../_partials/ModalTokenManager.spec.js | 365 ------------------ 1 file changed, 365 deletions(-) delete mode 100644 src/tests/views/SignPDF/_partials/ModalTokenManager.spec.js diff --git a/src/tests/views/SignPDF/_partials/ModalTokenManager.spec.js b/src/tests/views/SignPDF/_partials/ModalTokenManager.spec.js deleted file mode 100644 index 223b20acaf..0000000000 --- a/src/tests/views/SignPDF/_partials/ModalTokenManager.spec.js +++ /dev/null @@ -1,365 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { setActivePinia, createPinia } from 'pinia' -import { mount } from '@vue/test-utils' -import ModalTokenManager from '@/views/SignPDF/_partials/ModalTokenManager.vue' -import { useSignMethodsStore } from '@/store/signMethods.js' - -// Mock axios -vi.mock('@nextcloud/axios', () => ({ - default: vi.fn().mockResolvedValue({ data: { ocs: { data: {} } } }), - post: vi.fn().mockResolvedValue({ data: { ocs: { data: { message: 'Code sent' } } } }), -})) - -vi.mock('@nextcloud/router', () => ({ - generateOcsUrl: vi.fn((path) => `/ocs/v2.php/apps/libresign${path}`), -})) - -vi.mock('@nextcloud/dialogs', () => ({ - showError: vi.fn(), - showSuccess: vi.fn(), -})) - -vi.mock('@nextcloud/password-confirmation', () => ({ - confirmPassword: vi.fn().mockResolvedValue(true), -})) - -describe('ModalTokenManager - UX Improvements', () => { - let wrapper - let signMethodsStore - - beforeEach(() => { - setActivePinia(createPinia()) - signMethodsStore = useSignMethodsStore() - signMethodsStore.modal.token = true - signMethodsStore.settings.smsToken = { - identifyMethod: 'email', - } - }) - - it('displays progress indicator on step 1', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - const progressIndicator = wrapper.find('.progress-indicator') - expect(progressIndicator.exists()).toBe(true) - expect(progressIndicator.text()).toContain('Step 1 of 3 - Identity verification') - }) - - it('displays generic explanatory text (not phone-specific) on step 1', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - const explanation = wrapper.find('.step-explanation') - expect(explanation.exists()).toBe(true) - expect(explanation.text()).toContain('verify your identity') - expect(explanation.text()).toContain('contact information') - expect(explanation.text()).not.toContain('phone') // Should be generic - }) - - it('shows correct dialog title for step 1', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - expect(wrapper.vm.dialogTitle).toBe('Identity verification') - }) - - it('uses generic "Contact information" label, not phone-specific', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - expect(wrapper.html()).toContain('Contact information') - expect(wrapper.html()).not.toContain('Phone number') - }) - - it('updates to step 2 when tokenRequested is true', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '+5511999999999', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.tokenRequested = true - await wrapper.vm.$nextTick() - - const progressIndicator = wrapper.find('.progress-indicator') - expect(progressIndicator.text()).toContain('Step 2 of 3 - Code validation') - }) - - it('updates to step 3 when identityVerified is true', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '+5511999999999', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.tokenRequested = true - wrapper.vm.identityVerified = true - await wrapper.vm.$nextTick() - - const progressIndicator = wrapper.find('.progress-indicator') - expect(progressIndicator.text()).toContain('Step 3 of 3 - Signature confirmation') - }) - - it('shows correct button label on step 1', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - expect(wrapper.vm.tokenRequested).toBe(false) - expect(wrapper.vm.identityVerified).toBe(false) - }) - - it('shows correct button label on step 2', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '+5511999999999', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.tokenRequested = true - await wrapper.vm.$nextTick() - - expect(wrapper.vm.tokenRequested).toBe(true) - expect(wrapper.vm.identityVerified).toBe(false) - }) - - it('renders step-content class for styling', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - const stepContent = wrapper.find('.step-content') - expect(stepContent.exists()).toBe(true) - }) - - it('displays progress with correct numbering (2 steps, not 3)', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - expect(wrapper.vm.progressText).toContain('of 3') - expect(wrapper.vm.progressText).not.toContain('of 2') - }) - - it('requestNewCode resets tokenRequested state', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '+5511999999999', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.tokenRequested = true - wrapper.vm.token = '123456' - wrapper.vm.identityVerified = true - - wrapper.vm.requestNewCode() - - expect(wrapper.vm.tokenRequested).toBe(false) - expect(wrapper.vm.token).toBe('') - expect(wrapper.vm.identityVerified).toBe(false) - }) - - it('updates to step 3 when identityVerified is true', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '+5511999999999', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.tokenRequested = true - wrapper.vm.identityVerified = true - await wrapper.vm.$nextTick() - - const progressIndicator = wrapper.find('.progress-indicator') - expect(progressIndicator.text()).toContain('Step 3 of 3 - Signature confirmation') - }) - - it('shows verification success message on step 3', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '+5511999999999', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.tokenRequested = true - wrapper.vm.identityVerified = true - await wrapper.vm.$nextTick() - - const verificationSuccess = wrapper.find('.verification-success') - expect(verificationSuccess.exists()).toBe(true) - expect(verificationSuccess.text()).toContain('Your identity has been verified') - expect(verificationSuccess.text()).toContain('You can now sign the document') - }) - - it('shows correct button label on step 3', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '+5511999999999', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.tokenRequested = true - wrapper.vm.identityVerified = true - await wrapper.vm.$nextTick() - - expect(wrapper.vm.identityVerified).toBe(true) - expect(wrapper.vm.dialogTitle).toBe('Signature confirmation') - }) - - it('sendCode sets identityVerified to true', async () => { - wrapper = mount(ModalTokenManager, { - props: { - phoneNumber: '+5511999999999', - }, - global: { - stubs: { - NcDialog: { template: '
' }, - NcTextField: { template: '' }, - NcButton: { template: '' }, - NcLoadingIcon: { template: '
' }, - }, - }, - }) - - wrapper.vm.tokenRequested = true - wrapper.vm.token = '123456' - wrapper.vm.sendCode() - - expect(wrapper.vm.identityVerified).toBe(true) - }) -}) From 6631ee9282c08f79fa68535cc13647daf8cd3422 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:55:18 -0300 Subject: [PATCH 16/27] test(sign): remove dead SignFinalModals spec Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../SignPDF/_partials/SignFinalModals.spec.js | 86 ------------------- 1 file changed, 86 deletions(-) delete mode 100644 src/tests/views/SignPDF/_partials/SignFinalModals.spec.js diff --git a/src/tests/views/SignPDF/_partials/SignFinalModals.spec.js b/src/tests/views/SignPDF/_partials/SignFinalModals.spec.js deleted file mode 100644 index 8ae9d3a5e7..0000000000 --- a/src/tests/views/SignPDF/_partials/SignFinalModals.spec.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { setActivePinia, createPinia } from 'pinia' -import { mount } from '@vue/test-utils' -import { useSignMethodsStore } from '@/store/signMethods.js' - -describe('Sign.vue - Final Signature Modal UX', () => { - let signMethodsStore - - beforeEach(() => { - setActivePinia(createPinia()) - signMethodsStore = useSignMethodsStore() - }) - - it('clickToSign modal shows confirmation text without progress numbers', async () => { - // This would normally be tested by mounting Sign.vue - // Here we verify the modal structure expectations - - const mockClickToSignModal = { - title: 'Sign document', - hasProgressIndicator: false, // No "Step 3 of 3" for click-to-sign - explanationText: 'Confirm that you want to sign this document.', - } - - expect(mockClickToSignModal.title).toContain('Sign document') - expect(mockClickToSignModal.hasProgressIndicator).toBe(false) - expect(mockClickToSignModal.explanationText).toContain('want to sign') - }) - - it('password modal shows confirmation text without progress numbers', async () => { - const mockPasswordModal = { - title: 'Sign document', - hasProgressIndicator: false, // No "Step 3 of 3" for password modal - explanationText: 'Enter your signature password to sign the document.', - } - - expect(mockPasswordModal.title).toContain('Sign document') - expect(mockPasswordModal.hasProgressIndicator).toBe(false) - expect(mockPasswordModal.explanationText).toContain('signature password') - }) - - it('final modals have confirmation-text class for styling', async () => { - // Verify that confirmaton-text class is used instead of step-explanation - const hasConfirmationTextClass = true // In the actual Vue template - - expect(hasConfirmationTextClass).toBe(true) - }) - - it('clickToSign button has clear action label', async () => { - const mockButton = { - label: 'Sign document', - } - - expect(mockButton.label).not.toContain('Confirm') - expect(mockButton.label).toContain('Sign document') - }) - - it('password modal button has clear action label', async () => { - const mockButton = { - label: 'Sign document', - } - - expect(mockButton.label).not.toContain('Sign the document') - expect(mockButton.label).toBe('Sign document') - }) - - it('progress indicators only appear in token/email modals, not in final signature modals', async () => { - // Token modal should have progress - const tokenModalHasProgress = true - // Email modal should have progress - const emailModalHasProgress = true - // Click to sign should NOT have progress - const clickToSignHasProgress = false - // Password should NOT have progress - const passwordHasProgress = false - - expect(tokenModalHasProgress).toBe(true) - expect(emailModalHasProgress).toBe(true) - expect(clickToSignHasProgress).toBe(false) - expect(passwordHasProgress).toBe(false) - }) -}) From d63799c4d269b6aa9412b93d17e723feee25c337 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:55:18 -0300 Subject: [PATCH 17/27] test(sign): add ModalVerificationCode spec Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../_partials/ModalVerificationCode.spec.ts | 373 ++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 src/tests/views/SignPDF/_partials/ModalVerificationCode.spec.ts diff --git a/src/tests/views/SignPDF/_partials/ModalVerificationCode.spec.ts b/src/tests/views/SignPDF/_partials/ModalVerificationCode.spec.ts new file mode 100644 index 0000000000..065b8f0baf --- /dev/null +++ b/src/tests/views/SignPDF/_partials/ModalVerificationCode.spec.ts @@ -0,0 +1,373 @@ +/** + * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { setActivePinia, createPinia } from 'pinia' +import { mount } from '@vue/test-utils' +import ModalVerificationCode from '@/views/SignPDF/_partials/ModalVerificationCode.vue' +import { useSignMethodsStore } from '@/store/signMethods.js' + +// Mock axios +vi.mock('@nextcloud/axios', () => ({ + default: vi.fn().mockResolvedValue({ data: { ocs: { data: {} } } }), + post: vi.fn().mockResolvedValue({ data: { ocs: { data: { message: 'Code sent' } } } }), +})) + +vi.mock('@nextcloud/router', () => ({ + generateOcsUrl: vi.fn((path: string) => `/ocs/v2.php/apps/libresign${path}`), +})) + +vi.mock('@nextcloud/initial-state', () => ({ + loadState: vi.fn(() => 6), +})) + +vi.mock('@nextcloud/dialogs', () => ({ + showError: vi.fn(), + showSuccess: vi.fn(), +})) + +vi.mock('@nextcloud/password-confirmation', () => ({ + confirmPassword: vi.fn().mockResolvedValue(true), +})) + +describe('ModalVerificationCode (email mode)', () => { + let wrapper: ReturnType + let signMethodsStore: ReturnType + + const stubs = { + NcDialog: { template: '
' }, + NcTextField: { template: '' }, + NcButton: { template: '' }, + NcLoadingIcon: { template: '
' }, + NcIconSvgWrapper: { template: '
' }, + } + + const stubsWithActions = { + ...stubs, + NcDialog: { template: '
' }, + } + + const stubsWithName = { + ...stubs, + NcDialog: { props: ['name'], template: '
' }, + } + + const mountEmail = (extraProps = {}) => mount(ModalVerificationCode, { + props: { mode: 'email', ...extraProps }, + global: { stubs }, + }) + + beforeEach(() => { + setActivePinia(createPinia()) + signMethodsStore = useSignMethodsStore() + signMethodsStore.modal.emailToken = true + signMethodsStore.settings.emailToken = { + hasConfirmCode: false, + hashOfEmail: '5d41402abc4b2a76b9719d911017c592', + blurredEmail: 'u***@email.com', + } + }) + + it('displays progress indicator on step 1', async () => { + wrapper = mountEmail() + + const progressIndicator = wrapper.find('.progress-indicator') + expect(progressIndicator.exists()).toBe(true) + expect(progressIndicator.text()).toContain('Step 1 of 3 - Email verification') + }) + + it('displays explanatory text on step 1', async () => { + wrapper = mountEmail() + + const explanation = wrapper.find('.step-explanation') + expect(explanation.exists()).toBe(true) + expect(explanation.text()).toContain('verify your identity') + expect(explanation.text()).toContain('verification code') + }) + + it('shows correct dialog title for step 1', async () => { + wrapper = mount(ModalVerificationCode, { + props: { mode: 'email' }, + global: { stubs: stubsWithName }, + }) + + expect(wrapper.vm.dialogTitle).toBe('Email verification') + }) + + it('renders step-content class for styling', async () => { + wrapper = mountEmail() + + expect(wrapper.find('.step-content').exists()).toBe(true) + }) + + it('shows contact on step 2', async () => { + signMethodsStore.settings.emailToken.hasConfirmCode = true + wrapper = mountEmail() + + const contactDisplay = wrapper.find('.contact-display') + expect(contactDisplay.exists()).toBe(true) + expect(contactDisplay.text()).toContain('u***@email.com') + }) + + it('shows correct state on step 1', async () => { + wrapper = mount(ModalVerificationCode, { + props: { mode: 'email' }, + global: { stubs: stubsWithActions }, + }) + + expect(wrapper.vm.signMethodsStore.settings.emailToken.hasConfirmCode).toBe(false) + expect(wrapper.vm.identityVerified).toBe(false) + }) + + it('shows correct state on step 2', async () => { + signMethodsStore.settings.emailToken.hasConfirmCode = true + wrapper = mount(ModalVerificationCode, { + props: { mode: 'email' }, + global: { stubs: stubsWithActions }, + }) + + expect(wrapper.vm.signMethodsStore.settings.emailToken.hasConfirmCode).toBe(true) + expect(wrapper.vm.identityVerified).toBe(false) + }) + + it('updates to step 3 when identityVerified is true', async () => { + signMethodsStore.settings.emailToken.hasConfirmCode = true + wrapper = mountEmail() + + wrapper.vm.identityVerified = true + await wrapper.vm.$nextTick() + + expect(wrapper.find('.progress-indicator').text()).toContain('Step 3 of 3 - Signature confirmation') + }) + + it('shows verification success message on step 3', async () => { + signMethodsStore.settings.emailToken.hasConfirmCode = true + wrapper = mountEmail() + + wrapper.vm.identityVerified = true + await wrapper.vm.$nextTick() + + const verificationSuccess = wrapper.find('.verification-success') + expect(verificationSuccess.exists()).toBe(true) + expect(verificationSuccess.text()).toContain('Your identity has been verified') + expect(verificationSuccess.text()).toContain('You can now sign the document') + }) + + it('shows correct dialog title on step 3', async () => { + signMethodsStore.settings.emailToken.hasConfirmCode = true + wrapper = mount(ModalVerificationCode, { + props: { mode: 'email' }, + global: { stubs: stubsWithActions }, + }) + + wrapper.vm.identityVerified = true + await wrapper.vm.$nextTick() + + expect(wrapper.vm.dialogTitle).toBe('Signature confirmation') + }) + + it('sendCode sets identityVerified to true', async () => { + signMethodsStore.settings.emailToken.hasConfirmCode = true + wrapper = mountEmail() + + wrapper.vm.token = '123456' + wrapper.vm.sendCode() + + expect(wrapper.vm.identityVerified).toBe(true) + }) + + it('requestNewCode resets identityVerified', async () => { + wrapper = mountEmail() + + wrapper.vm.identityVerified = true + wrapper.vm.requestNewCode() + + expect(wrapper.vm.identityVerified).toBe(false) + }) +}) + +describe('ModalVerificationCode (token mode)', () => { + let wrapper: ReturnType + let signMethodsStore: ReturnType + + const stubs = { + NcDialog: { template: '
' }, + NcTextField: { template: '' }, + NcButton: { template: '' }, + NcLoadingIcon: { template: '
' }, + NcIconSvgWrapper: { template: '
' }, + } + + const stubsWithActions = { + ...stubs, + NcDialog: { template: '
' }, + } + + const stubsWithName = { + ...stubs, + NcDialog: { props: ['name'], template: '
' }, + } + + const mountToken = (extraProps = {}) => mount(ModalVerificationCode, { + props: { mode: 'token', phoneNumber: '', ...extraProps }, + global: { stubs }, + }) + + beforeEach(() => { + setActivePinia(createPinia()) + signMethodsStore = useSignMethodsStore() + signMethodsStore.modal.token = true + signMethodsStore.settings.smsToken = { + identifyMethod: 'email', + } + }) + + it('displays progress indicator on step 1', async () => { + wrapper = mountToken() + + const progressIndicator = wrapper.find('.progress-indicator') + expect(progressIndicator.exists()).toBe(true) + expect(progressIndicator.text()).toContain('Step 1 of 3 - Identity verification') + }) + + it('displays generic explanatory text (not phone-specific) on step 1', async () => { + wrapper = mountToken() + + const explanation = wrapper.find('.step-explanation') + expect(explanation.exists()).toBe(true) + expect(explanation.text()).toContain('verify your identity') + expect(explanation.text()).toContain('contact information') + expect(explanation.text()).not.toContain('phone') + }) + + it('shows correct dialog title for step 1', async () => { + wrapper = mount(ModalVerificationCode, { + props: { mode: 'token', phoneNumber: '' }, + global: { stubs: stubsWithName }, + }) + + expect(wrapper.vm.dialogTitle).toBe('Identity verification') + }) + + it('uses generic "Contact information" label, not phone-specific', async () => { + wrapper = mount(ModalVerificationCode, { + props: { mode: 'token', phoneNumber: '' }, + global: { + stubs: { + ...stubs, + NcTextField: { props: ['label'], template: '' }, + }, + }, + }) + + expect(wrapper.html()).toContain('Contact information') + expect(wrapper.html()).not.toContain('Phone number') + }) + + it('renders step-content class for styling', async () => { + wrapper = mountToken() + + expect(wrapper.find('.step-content').exists()).toBe(true) + }) + + it('displays progress with correct 3-step numbering', async () => { + wrapper = mountToken() + + expect(wrapper.vm.progressText).toContain('of 3') + expect(wrapper.vm.progressText).not.toContain('of 2') + }) + + it('updates to step 2 when tokenRequested is true', async () => { + wrapper = mountToken({ phoneNumber: '+5511999999999' }) + + wrapper.vm.tokenRequested = true + await wrapper.vm.$nextTick() + + expect(wrapper.find('.progress-indicator').text()).toContain('Step 2 of 3 - Code validation') + }) + + it('updates to step 3 when identityVerified is true', async () => { + wrapper = mountToken({ phoneNumber: '+5511999999999' }) + + wrapper.vm.tokenRequested = true + wrapper.vm.identityVerified = true + await wrapper.vm.$nextTick() + + expect(wrapper.find('.progress-indicator').text()).toContain('Step 3 of 3 - Signature confirmation') + }) + + it('shows correct state on step 1', async () => { + wrapper = mount(ModalVerificationCode, { + props: { mode: 'token', phoneNumber: '' }, + global: { stubs: stubsWithActions }, + }) + + expect(wrapper.vm.tokenRequested).toBe(false) + expect(wrapper.vm.identityVerified).toBe(false) + }) + + it('shows correct state on step 2', async () => { + wrapper = mount(ModalVerificationCode, { + props: { mode: 'token', phoneNumber: '+5511999999999' }, + global: { stubs: stubsWithActions }, + }) + + wrapper.vm.tokenRequested = true + await wrapper.vm.$nextTick() + + expect(wrapper.vm.tokenRequested).toBe(true) + expect(wrapper.vm.identityVerified).toBe(false) + }) + + it('shows verification success message on step 3', async () => { + wrapper = mountToken({ phoneNumber: '+5511999999999' }) + + wrapper.vm.tokenRequested = true + wrapper.vm.identityVerified = true + await wrapper.vm.$nextTick() + + const verificationSuccess = wrapper.find('.verification-success') + expect(verificationSuccess.exists()).toBe(true) + expect(verificationSuccess.text()).toContain('Your identity has been verified') + expect(verificationSuccess.text()).toContain('You can now sign the document') + }) + + it('shows correct dialog title on step 3', async () => { + wrapper = mount(ModalVerificationCode, { + props: { mode: 'token', phoneNumber: '+5511999999999' }, + global: { stubs: stubsWithActions }, + }) + + wrapper.vm.tokenRequested = true + wrapper.vm.identityVerified = true + await wrapper.vm.$nextTick() + + expect(wrapper.vm.dialogTitle).toBe('Signature confirmation') + }) + + it('sendCode sets identityVerified to true', async () => { + wrapper = mountToken({ phoneNumber: '+5511999999999' }) + + wrapper.vm.tokenRequested = true + wrapper.vm.token = '123456' + wrapper.vm.sendCode() + + expect(wrapper.vm.identityVerified).toBe(true) + }) + + it('requestNewCode resets tokenRequested state', async () => { + wrapper = mountToken({ phoneNumber: '+5511999999999' }) + + wrapper.vm.tokenRequested = true + wrapper.vm.token = '123456' + wrapper.vm.identityVerified = true + + wrapper.vm.requestNewCode() + + expect(wrapper.vm.tokenRequested).toBe(false) + expect(wrapper.vm.token).toBe('') + expect(wrapper.vm.identityVerified).toBe(false) + }) +}) From 7ddd25c6b747c31712c6ef111d05811e4fb14471 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:55:18 -0300 Subject: [PATCH 18/27] test(sign): remove invalid $emit mock from Sign spec Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/tests/views/SignPDF/Sign.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tests/views/SignPDF/Sign.spec.ts b/src/tests/views/SignPDF/Sign.spec.ts index 3fa74c4531..0bfb67d98f 100644 --- a/src/tests/views/SignPDF/Sign.spec.ts +++ b/src/tests/views/SignPDF/Sign.spec.ts @@ -992,7 +992,6 @@ describe('Sign.vue - signWithTokenCode', () => { NcRichText: true, }, mocks: { - $emit: vi.fn(), $watch: vi.fn(), }, }, From 797353cb1bddb1fbb116b2be26454b177a93cf9d Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 18:01:21 -0300 Subject: [PATCH 19/27] fix: return the kind of modal Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/tests/views/SignPDF/Sign.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/views/SignPDF/Sign.spec.ts b/src/tests/views/SignPDF/Sign.spec.ts index 0bfb67d98f..cec48adc5a 100644 --- a/src/tests/views/SignPDF/Sign.spec.ts +++ b/src/tests/views/SignPDF/Sign.spec.ts @@ -865,12 +865,14 @@ describe('Sign.vue - signWithTokenCode', () => { // VERIFY: Must send identify method, NOT signature method name expect(submitSignatureMock).toHaveBeenCalledWith({ method: testCase.expectedIdentifyMethod, // 'whatsapp', 'sms', 'signal' + modalCode: 'token', token: testCase.token, }) // Double-check: Should NOT send the signature method key name expect(submitSignatureMock).not.toHaveBeenCalledWith({ method: testCase.signatureMethodKey, // NOT 'whatsappToken', 'smsToken', etc + modalCode: 'token', token: testCase.token, }) } From 6b27c15c708a43a6eda8c12ddfbefc5e0c99d42a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 27 Feb 2026 18:03:35 -0300 Subject: [PATCH 20/27] feat(sign): improve email verification step descriptions Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/views/SignPDF/_partials/ModalVerificationCode.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/SignPDF/_partials/ModalVerificationCode.vue b/src/views/SignPDF/_partials/ModalVerificationCode.vue index 4ea2a0d887..43aaebd8e9 100644 --- a/src/views/SignPDF/_partials/ModalVerificationCode.vue +++ b/src/views/SignPDF/_partials/ModalVerificationCode.vue @@ -15,7 +15,7 @@

- {{ t('libresign', 'To sign this document, we must verify your identity. Enter your email address to receive a verification code.') }} + {{ t('libresign', 'To verify your identity, enter the same email address where you received the signature request. We will send a verification code to this address.') }}