@@ -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 = {
@@ -1790,4 +1927,61 @@ describe('Sign.vue - signWithTokenCode', () => {
17901927 { documentId : 1 } ,
17911928 )
17921929 } )
1930+
1931+ it ( 'submits each envelope file when current signer uuids only exist in child files' , async ( ) => {
1932+ const storeSubmitMock = vi . fn ( ) . mockResolvedValue ( { status : 'signed' , data : { } } )
1933+
1934+ const context = {
1935+ loading : false ,
1936+ elements : [
1937+ { elementId : 100 , signRequestId : 10 , type : 'signature' } ,
1938+ { elementId : 200 , signRequestId : 20 , type : 'signature' } ,
1939+ ] ,
1940+ canCreateSignature : false ,
1941+ signRequestUuid : 'fallback-envelope-uuid' ,
1942+ signatureElementsStore : { signs : { } } ,
1943+ actionHandler : { showModal : vi . fn ( ) , closeModal : vi . fn ( ) } ,
1944+ signMethodsStore : { certificateEngine : 'openssl' } ,
1945+ signStore : {
1946+ document : {
1947+ id : 1 ,
1948+ nodeType : 'envelope' ,
1949+ signers : [ ] ,
1950+ files : [
1951+ {
1952+ signers : [
1953+ { signRequestId : 10 , me : true , sign_request_uuid : 'uuid-file-1' } ,
1954+ ] ,
1955+ } ,
1956+ {
1957+ signers : [
1958+ { signRequestId : 20 , me : true , sign_request_uuid : 'uuid-file-2' } ,
1959+ ] ,
1960+ } ,
1961+ ] ,
1962+ } ,
1963+ clearSigningErrors : vi . fn ( ) ,
1964+ setSigningErrors : vi . fn ( ) ,
1965+ submitSignature : storeSubmitMock ,
1966+ } ,
1967+ $emit : vi . fn ( ) ,
1968+ sidebarStore : { hideSidebar : vi . fn ( ) } ,
1969+ }
1970+
1971+ await submitSignatureCompatMethod . call ( context , { method : 'clickToSign' } )
1972+
1973+ expect ( storeSubmitMock ) . toHaveBeenCalledTimes ( 2 )
1974+ expect ( storeSubmitMock ) . toHaveBeenNthCalledWith (
1975+ 1 ,
1976+ { method : 'clickToSign' , elements : [ { documentElementId : 100 } ] } ,
1977+ 'uuid-file-1' ,
1978+ { documentId : 1 } ,
1979+ )
1980+ expect ( storeSubmitMock ) . toHaveBeenNthCalledWith (
1981+ 2 ,
1982+ { method : 'clickToSign' , elements : [ { documentElementId : 200 } ] } ,
1983+ 'uuid-file-2' ,
1984+ { documentId : 1 } ,
1985+ )
1986+ } )
17931987 } )
0 commit comments