@@ -106,6 +106,36 @@ const createSignDocument = (overrides: Partial<SignDocument> = {}): SignDocument
106106 ...overrides ,
107107} )
108108
109+ const createSignMountOptions = ( ) => ( {
110+ global : {
111+ stubs : {
112+ NcButton : true ,
113+ NcDialog : true ,
114+ NcLoadingIcon : true ,
115+ TokenManager : true ,
116+ EmailManager : true ,
117+ UploadCertificate : true ,
118+ Documents : true ,
119+ Signatures : true ,
120+ Draw : true ,
121+ ManagePassword : true ,
122+ CreatePassword : true ,
123+ NcNoteCard : true ,
124+ NcPasswordField : true ,
125+ NcRichText : true ,
126+ } ,
127+ mocks : {
128+ $watch : vi . fn ( ) ,
129+ $nextTick : vi . fn ( ) ,
130+ } ,
131+ } ,
132+ } )
133+
134+ const mountRealSignComponent = async ( ) => {
135+ const SignComponent = await import ( '../../../views/SignPDF/_partials/Sign.vue' )
136+ return mount ( SignComponent . default , createSignMountOptions ( ) )
137+ }
138+
109139// Global mock for axios - prevents unhandled rejections during component mounting
110140vi . mock ( '@nextcloud/axios' , ( ) => {
111141 const axiosInstanceMock = Object . assign ( vi . fn ( ) . mockResolvedValue ( {
@@ -468,6 +498,113 @@ describe('Sign.vue - signWithTokenCode', () => {
468498 } )
469499 } )
470500
501+ describe ( 'Sign.vue - lifecycle regressions' , ( ) => {
502+ it ( 'consumes pendingAction on mount and opens the matching signing modal' , async ( ) => {
503+ setActivePinia ( createPinia ( ) )
504+
505+ const { useSignStore } = await import ( '../../../store/sign.js' )
506+ const signStore = useSignStore ( )
507+ const signMethodsStore = useSignMethodsStore ( )
508+
509+ signStore . document = createSignDocument ( {
510+ status : FILE_STATUS . ABLE_TO_SIGN ,
511+ signers : [ { me : true , status : SIGN_REQUEST_STATUS . ABLE_TO_SIGN , signRequestId : 501 , sign_request_uuid : 'pending-action-uuid' } ] ,
512+ } )
513+ signStore . queueAction ( 'sign' )
514+ signMethodsStore . settings = {
515+ clickToSign : { } ,
516+ }
517+
518+ await mountRealSignComponent ( )
519+ await flushPromises ( )
520+
521+ expect ( signMethodsStore . modal . clickToSign ) . toBe ( true )
522+ expect ( signStore . pendingAction ) . toBe ( null )
523+ } )
524+
525+ it ( 'emits signing-started on mount when document is already signing in progress' , async ( ) => {
526+ setActivePinia ( createPinia ( ) )
527+
528+ const { useSignStore } = await import ( '../../../store/sign.js' )
529+ const signStore = useSignStore ( )
530+
531+ signStore . document = createSignDocument ( {
532+ status : FILE_STATUS . SIGNING_IN_PROGRESS ,
533+ signers : [ { me : true , signRequestId : 501 , sign_request_uuid : 'progress-uuid' } ] ,
534+ } )
535+
536+ const wrapper = await mountRealSignComponent ( )
537+ await flushPromises ( )
538+
539+ expect ( wrapper . emitted ( 'signing-started' ) ) . toEqual ( [
540+ [ { signRequestUuid : 'progress-uuid' , async : true } ] ,
541+ ] )
542+ } )
543+
544+ it ( 'resets modal and local signing state when signRequestUuid changes' , async ( ) => {
545+ setActivePinia ( createPinia ( ) )
546+
547+ const { useSignStore } = await import ( '../../../store/sign.js' )
548+ const signStore = useSignStore ( )
549+ const signMethodsStore = useSignMethodsStore ( )
550+
551+ signStore . document = createSignDocument ( {
552+ signers : [ { me : true , signRequestId : 501 , sign_request_uuid : 'uuid-before' } ] ,
553+ } )
554+
555+ const wrapper = await mountRealSignComponent ( )
556+ const signVm = wrapper . vm as typeof wrapper . vm & {
557+ showManagePassword : boolean
558+ signPassword : string
559+ }
560+ signMethodsStore . showModal ( 'password' )
561+ signMethodsStore . showModal ( 'token' )
562+ signStore . setSigningErrors ( [ { message : 'existing error' , code : 422 } ] )
563+ signVm . showManagePassword = true
564+ signVm . signPassword = '123456'
565+
566+ signStore . document = createSignDocument ( {
567+ signers : [ { me : true , signRequestId : 502 , sign_request_uuid : 'uuid-after' } ] ,
568+ } )
569+
570+ await wrapper . vm . $nextTick ( )
571+ await flushPromises ( )
572+
573+ expect ( signMethodsStore . modal . password ) . toBe ( false )
574+ expect ( signMethodsStore . modal . token ) . toBe ( false )
575+ expect ( signStore . errors ) . toEqual ( [ ] )
576+ expect ( signVm . showManagePassword ) . toBe ( false )
577+ expect ( signVm . signPassword ) . toBe ( '' )
578+ } )
579+
580+ it ( 'cleans modal and signing errors on unmount' , async ( ) => {
581+ setActivePinia ( createPinia ( ) )
582+
583+ const { useSignStore } = await import ( '../../../store/sign.js' )
584+ const signStore = useSignStore ( )
585+ const signMethodsStore = useSignMethodsStore ( )
586+
587+ signStore . document = createSignDocument ( {
588+ signers : [ { me : true , signRequestId : 501 , sign_request_uuid : 'cleanup-uuid' } ] ,
589+ } )
590+
591+ const wrapper = await mountRealSignComponent ( )
592+ signMethodsStore . showModal ( 'password' )
593+ signMethodsStore . showModal ( 'createSignature' )
594+ signMethodsStore . settings = {
595+ password : { hasSignatureFile : true } ,
596+ }
597+ signStore . setSigningErrors ( [ { message : 'existing error' , code : 422 } ] )
598+
599+ wrapper . unmount ( )
600+
601+ expect ( signMethodsStore . modal . password ) . toBe ( false )
602+ expect ( signMethodsStore . modal . createSignature ) . toBe ( false )
603+ expect ( signMethodsStore . settings ) . toEqual ( { } )
604+ expect ( signStore . errors ) . toEqual ( [ ] )
605+ } )
606+ } )
607+
471608 describe ( 'Sign.vue - API error handling' , ( ) => {
472609 it ( 'emits signed when envelope submit has mixed results and the final result is signed' , async ( ) => {
473610 const context = {
@@ -1723,4 +1860,61 @@ describe('Sign.vue - signWithTokenCode', () => {
17231860 { documentId : 1 } ,
17241861 )
17251862 } )
1863+
1864+ it ( 'submits each envelope file when current signer uuids only exist in child files' , async ( ) => {
1865+ const storeSubmitMock = vi . fn ( ) . mockResolvedValue ( { status : 'signed' , data : { } } )
1866+
1867+ const context = {
1868+ loading : false ,
1869+ elements : [
1870+ { elementId : 100 , signRequestId : 10 , type : 'signature' } ,
1871+ { elementId : 200 , signRequestId : 20 , type : 'signature' } ,
1872+ ] ,
1873+ canCreateSignature : false ,
1874+ signRequestUuid : 'fallback-envelope-uuid' ,
1875+ signatureElementsStore : { signs : { } } ,
1876+ actionHandler : { showModal : vi . fn ( ) , closeModal : vi . fn ( ) } ,
1877+ signMethodsStore : { certificateEngine : 'openssl' } ,
1878+ signStore : {
1879+ document : {
1880+ id : 1 ,
1881+ nodeType : 'envelope' ,
1882+ signers : [ ] ,
1883+ files : [
1884+ {
1885+ signers : [
1886+ { signRequestId : 10 , me : true , sign_request_uuid : 'uuid-file-1' } ,
1887+ ] ,
1888+ } ,
1889+ {
1890+ signers : [
1891+ { signRequestId : 20 , me : true , sign_request_uuid : 'uuid-file-2' } ,
1892+ ] ,
1893+ } ,
1894+ ] ,
1895+ } ,
1896+ clearSigningErrors : vi . fn ( ) ,
1897+ setSigningErrors : vi . fn ( ) ,
1898+ submitSignature : storeSubmitMock ,
1899+ } ,
1900+ $emit : vi . fn ( ) ,
1901+ sidebarStore : { hideSidebar : vi . fn ( ) } ,
1902+ }
1903+
1904+ await submitSignatureCompatMethod . call ( context , { method : 'clickToSign' } )
1905+
1906+ expect ( storeSubmitMock ) . toHaveBeenCalledTimes ( 2 )
1907+ expect ( storeSubmitMock ) . toHaveBeenNthCalledWith (
1908+ 1 ,
1909+ { method : 'clickToSign' , elements : [ { documentElementId : 100 } ] } ,
1910+ 'uuid-file-1' ,
1911+ { documentId : 1 } ,
1912+ )
1913+ expect ( storeSubmitMock ) . toHaveBeenNthCalledWith (
1914+ 2 ,
1915+ { method : 'clickToSign' , elements : [ { documentElementId : 200 } ] } ,
1916+ 'uuid-file-2' ,
1917+ { documentId : 1 } ,
1918+ )
1919+ } )
17261920 } )
0 commit comments