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 { flushPromises , mount } from '@vue/test-utils'
8+
9+ import FooterTemplateEditor from '../../components/FooterTemplateEditor.vue'
10+
11+ const axiosGetMock = vi . fn ( )
12+ const axiosPostMock = vi . fn ( )
13+ const ensurePdfWorkerMock = vi . fn ( )
14+
15+ const appConfigMock = {
16+ deleteKey : vi . fn ( ) ,
17+ setValue : vi . fn ( ) ,
18+ }
19+
20+ const clipboardWriteTextMock = vi . fn ( )
21+
22+ vi . mock ( 'debounce' , ( ) => ( {
23+ default : vi . fn ( ( fn : ( ...args : unknown [ ] ) => unknown ) => fn ) ,
24+ } ) )
25+
26+ vi . mock ( '@nextcloud/l10n' , ( ) => ( {
27+ t : vi . fn ( ( _app : string , text : string ) => text ) ,
28+ } ) )
29+
30+ vi . mock ( '@nextcloud/axios' , ( ) => ( {
31+ default : {
32+ get : vi . fn ( ( ...args : unknown [ ] ) => axiosGetMock ( ...args ) ) ,
33+ post : vi . fn ( ( ...args : unknown [ ] ) => axiosPostMock ( ...args ) ) ,
34+ } ,
35+ } ) )
36+
37+ vi . mock ( '@nextcloud/initial-state' , ( ) => ( {
38+ loadState : vi . fn ( ( _app : string , key : string , defaultValue : unknown ) => {
39+ if ( key === 'footer_preview_zoom_level' ) {
40+ return 100
41+ }
42+ if ( key === 'footer_template_variables' ) {
43+ return {
44+ signerName : { description : 'Signer' , type : 'string' , example : 'Alice' } ,
45+ }
46+ }
47+ return defaultValue
48+ } ) ,
49+ } ) )
50+
51+ vi . mock ( '@nextcloud/router' , ( ) => ( {
52+ generateOcsUrl : vi . fn ( ( path : string ) => `/ocs/v2.php${ path } ` ) ,
53+ } ) )
54+
55+ vi . mock ( '../../helpers/pdfWorker' , ( ) => ( {
56+ ensurePdfWorker : vi . fn ( ( ) => ensurePdfWorkerMock ( ) ) ,
57+ } ) )
58+
59+ vi . mock ( '@libresign/pdf-elements/src/components/PDFElements.vue' , ( ) => ( {
60+ default : {
61+ name : 'PDFElements' ,
62+ props : [ 'initialScale' ] ,
63+ template : '<div class="pdf-elements-stub" />' ,
64+ } ,
65+ } ) )
66+
67+ vi . mock ( '../../components/CodeEditor.vue' , ( ) => ( {
68+ default : {
69+ name : 'CodeEditor' ,
70+ props : [ 'modelValue' , 'label' , 'placeholder' ] ,
71+ emits : [ 'update:modelValue' ] ,
72+ template : '<textarea class="code-editor-stub" />' ,
73+ } ,
74+ } ) )
75+
76+ vi . mock ( '@nextcloud/vue/components/NcButton' , ( ) => ( {
77+ default : {
78+ name : 'NcButton' ,
79+ emits : [ 'click' ] ,
80+ template : '<button class="nc-button-stub" @click="$emit(\'click\')"><slot /><slot name="icon" /></button>' ,
81+ } ,
82+ } ) )
83+
84+ vi . mock ( '@nextcloud/vue/components/NcDialog' , ( ) => ( {
85+ default : {
86+ name : 'NcDialog' ,
87+ template : '<div class="nc-dialog-stub"><slot /><slot name="actions" /></div>' ,
88+ } ,
89+ } ) )
90+
91+ vi . mock ( '@nextcloud/vue/components/NcFormBoxButton' , ( ) => ( {
92+ default : {
93+ name : 'NcFormBoxButton' ,
94+ emits : [ 'click' ] ,
95+ template : '<button class="nc-form-box-button-stub" @click="$emit(\'click\')"><slot /><slot name="icon" /><slot name="description" /></button>' ,
96+ } ,
97+ } ) )
98+
99+ vi . mock ( '@nextcloud/vue/components/NcLoadingIcon' , ( ) => ( {
100+ default : {
101+ name : 'NcLoadingIcon' ,
102+ template : '<span class="nc-loading-icon-stub" />' ,
103+ } ,
104+ } ) )
105+
106+ vi . mock ( '@nextcloud/vue/components/NcTextField' , ( ) => ( {
107+ default : {
108+ name : 'NcTextField' ,
109+ props : [ 'modelValue' , 'label' ] ,
110+ emits : [ 'update:modelValue' , 'input' ] ,
111+ template : '<input class="nc-text-field-stub" />' ,
112+ } ,
113+ } ) )
114+
115+ vi . mock ( '@nextcloud/vue/components/NcIconSvgWrapper' , ( ) => ( {
116+ default : {
117+ name : 'NcIconSvgWrapper' ,
118+ template : '<i class="nc-icon-svg-wrapper-stub" />' ,
119+ } ,
120+ } ) )
121+
122+ describe ( 'FooterTemplateEditor.vue' , ( ) => {
123+ const createWrapper = ( ) => mount ( FooterTemplateEditor , {
124+ global : {
125+ directives : {
126+ linkify : vi . fn ( ) ,
127+ } ,
128+ } ,
129+ } )
130+
131+ beforeEach ( ( ) => {
132+ axiosGetMock . mockReset ( )
133+ axiosPostMock . mockReset ( )
134+ ensurePdfWorkerMock . mockReset ( )
135+ appConfigMock . deleteKey . mockReset ( )
136+ appConfigMock . setValue . mockReset ( )
137+ clipboardWriteTextMock . mockReset ( )
138+
139+ axiosGetMock . mockResolvedValue ( {
140+ data : {
141+ ocs : {
142+ data : {
143+ template : 'Footer {{ signerName }}' ,
144+ preview_height : 120 ,
145+ preview_width : 640 ,
146+ } ,
147+ } ,
148+ } ,
149+ } )
150+ axiosPostMock . mockResolvedValue ( { data : new Blob ( [ 'pdf' ] , { type : 'application/pdf' } ) } )
151+
152+ vi . stubGlobal ( 'OCP' , { AppConfig : appConfigMock } )
153+ vi . stubGlobal ( 'navigator' , {
154+ clipboard : {
155+ writeText : clipboardWriteTextMock ,
156+ } ,
157+ } )
158+ } )
159+
160+ it ( 'loads the saved footer template on mount and initializes the PDF worker' , async ( ) => {
161+ const wrapper = createWrapper ( )
162+ await flushPromises ( )
163+
164+ expect ( ensurePdfWorkerMock ) . toHaveBeenCalledTimes ( 1 )
165+ expect ( axiosGetMock ) . toHaveBeenCalledWith ( '/ocs/v2.php/apps/libresign/api/v1/admin/footer-template' )
166+ expect ( wrapper . vm . footerTemplate ) . toBe ( 'Footer {{ signerName }}' )
167+ expect ( wrapper . vm . previewWidth ) . toBe ( 640 )
168+ expect ( wrapper . vm . previewHeight ) . toBe ( 120 )
169+ } )
170+
171+ it ( 'copies template variables to the clipboard and marks them as copied' , async ( ) => {
172+ const wrapper = createWrapper ( )
173+ await flushPromises ( )
174+
175+ wrapper . vm . copyToClipboard ( '{{ signerName }}' )
176+
177+ expect ( clipboardWriteTextMock ) . toHaveBeenCalledWith ( '{{ signerName }}' )
178+ expect ( wrapper . vm . isCopied ( 'signerName' ) ) . toBe ( true )
179+ } )
180+
181+ it ( 'resets dimensions and clears the stored app config values' , async ( ) => {
182+ const wrapper = createWrapper ( )
183+ await flushPromises ( )
184+
185+ wrapper . vm . previewWidth = 700
186+ wrapper . vm . previewHeight = 150
187+ wrapper . vm . resetDimensions ( )
188+
189+ expect ( wrapper . vm . previewWidth ) . toBe ( wrapper . vm . DEFAULT_PREVIEW_WIDTH )
190+ expect ( wrapper . vm . previewHeight ) . toBe ( wrapper . vm . DEFAULT_PREVIEW_HEIGHT )
191+ expect ( appConfigMock . deleteKey ) . toHaveBeenCalledWith ( 'libresign' , 'footer_preview_width' )
192+ expect ( appConfigMock . deleteKey ) . toHaveBeenCalledWith ( 'libresign' , 'footer_preview_height' )
193+ } )
194+
195+ it ( 'updates zoom level through the zoom controls logic' , async ( ) => {
196+ const wrapper = createWrapper ( )
197+ await flushPromises ( )
198+
199+ wrapper . vm . changeZoomLevel ( 20 )
200+
201+ expect ( wrapper . vm . zoomLevel ) . toBe ( 120 )
202+ } )
203+ } )
0 commit comments