Skip to content

Commit 7c9e104

Browse files
authored
Merge pull request #7206 from LibreSign/backport/7203/stable32
[stable32] chore: migration to vue3
2 parents d8c0e35 + 35bc5d0 commit 7c9e104

23 files changed

Lines changed: 779 additions & 319 deletions

src/components/PdfEditor/SignatureBox.vue

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,41 +11,54 @@
1111
</div>
1212
</template>
1313

14-
<script>
14+
<script setup lang="ts">
1515
import { t } from '@nextcloud/l10n'
1616
import { usernameToColor } from '@nextcloud/vue/functions/usernameToColor'
17+
import { computed } from 'vue'
1718
18-
export default {
19+
defineOptions({
1920
name: 'SignatureBox',
20-
props: {
21-
label: {
22-
type: String,
23-
default: '',
24-
},
25-
signer: {
26-
type: Object,
27-
default: null,
28-
},
29-
},
30-
computed: {
31-
signatureBoxAriaLabel() {
32-
// TRANSLATORS Accessible label for a placed signature box on the PDF. {name} is the signer's display name.
33-
return t('libresign', 'Signature position for {name}', { name: this.label })
34-
},
35-
boxStyle() {
36-
const signer = this.signer || {}
37-
const seed = signer.displayName || signer.name || signer.email || signer.id || this.label
38-
if (!seed) {
39-
return {}
40-
}
41-
const { r, g, b } = usernameToColor(String(seed))
42-
return {
43-
borderColor: `rgb(${r}, ${g}, ${b})`,
44-
backgroundColor: `rgba(${r}, ${g}, ${b}, 0.12)`,
45-
}
46-
},
47-
},
48-
}
21+
})
22+
23+
type Signer = {
24+
displayName?: string
25+
name?: string
26+
email?: string
27+
id?: string
28+
} | null
29+
30+
const props = withDefaults(defineProps<{
31+
label?: string
32+
signer?: Signer
33+
}>(), {
34+
label: '',
35+
signer: null,
36+
})
37+
38+
const signatureBoxAriaLabel = computed(() => {
39+
return t('libresign', 'Signature position for {name}', { name: props.label })
40+
})
41+
42+
const boxStyle = computed(() => {
43+
const signer = props.signer || {}
44+
const seed = signer.displayName || signer.name || signer.email || signer.id || props.label
45+
46+
if (!seed) {
47+
return {}
48+
}
49+
50+
const { r, g, b } = usernameToColor(String(seed))
51+
return {
52+
borderColor: `rgb(${r}, ${g}, ${b})`,
53+
backgroundColor: `rgba(${r}, ${g}, ${b}, 0.12)`,
54+
}
55+
})
56+
57+
defineExpose({
58+
signatureBoxAriaLabel,
59+
boxStyle,
60+
props,
61+
})
4962
</script>
5063

5164
<style lang="scss" scoped>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 LibreSign contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { describe, expect, it, vi } from 'vitest'
7+
import { mount } from '@vue/test-utils'
8+
9+
import SignatureBox from '../../../components/PdfEditor/SignatureBox.vue'
10+
11+
const usernameToColorMock = vi.fn((_seed: string) => ({ r: 10, g: 20, b: 30 }))
12+
13+
vi.mock('@nextcloud/l10n', () => ({
14+
t: vi.fn((_app: string, text: string, params?: Record<string, string>) => {
15+
if (!params) {
16+
return text
17+
}
18+
19+
return Object.entries(params).reduce((message, [key, value]) => {
20+
return message.replace(`{${key}}`, value)
21+
}, text)
22+
}),
23+
translate: vi.fn((_app: string, text: string) => text),
24+
translatePlural: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
25+
n: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
26+
getLanguage: vi.fn(() => 'en'),
27+
getLocale: vi.fn(() => 'en'),
28+
isRTL: vi.fn(() => false),
29+
}))
30+
31+
vi.mock('@nextcloud/vue/functions/usernameToColor', () => ({
32+
usernameToColor: (seed: string) => usernameToColorMock(seed),
33+
}))
34+
35+
describe('SignatureBox.vue', () => {
36+
it('computes the aria label from the label prop', () => {
37+
const wrapper = mount(SignatureBox, {
38+
props: {
39+
label: 'Ada Lovelace',
40+
},
41+
})
42+
43+
expect(wrapper.attributes('aria-label')).toBe('Signature position for Ada Lovelace')
44+
})
45+
46+
it('uses signer displayName as the color seed when available', () => {
47+
const wrapper = mount(SignatureBox, {
48+
props: {
49+
label: 'Fallback',
50+
signer: {
51+
displayName: 'Grace Hopper',
52+
},
53+
},
54+
})
55+
56+
expect(usernameToColorMock).toHaveBeenCalledWith('Grace Hopper')
57+
expect(wrapper.vm.boxStyle).toEqual({
58+
borderColor: 'rgb(10, 20, 30)',
59+
backgroundColor: 'rgba(10, 20, 30, 0.12)',
60+
})
61+
})
62+
63+
it('returns an empty style object when there is no seed', () => {
64+
const wrapper = mount(SignatureBox)
65+
66+
expect(wrapper.vm.boxStyle).toEqual({})
67+
})
68+
})

src/tests/components/PreviewSignature/PreviewSignature.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,4 @@ describe('PreviewSignature.vue', () => {
109109
responseType: 'arraybuffer',
110110
})
111111
})
112-
})
112+
})
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 LibreSign contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { describe, expect, it, vi } from 'vitest'
7+
import { mount } from '@vue/test-utils'
8+
9+
import UserImage from '../../../../views/Account/partials/UserImage.vue'
10+
11+
vi.mock('@nextcloud/l10n', () => ({
12+
t: vi.fn((_app: string, text: string) => text),
13+
translate: vi.fn((_app: string, text: string) => text),
14+
translatePlural: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
15+
n: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
16+
getLanguage: vi.fn(() => 'en'),
17+
getLocale: vi.fn(() => 'en'),
18+
isRTL: vi.fn(() => false),
19+
}))
20+
21+
describe('UserImage.vue', () => {
22+
it('renders the profile picture heading and passes the user data to the avatar', () => {
23+
const wrapper = mount(UserImage, {
24+
props: {
25+
user: {
26+
uid: 'ada',
27+
displayName: 'Ada Lovelace',
28+
},
29+
},
30+
global: {
31+
stubs: {
32+
NcAvatar: {
33+
name: 'NcAvatar',
34+
props: ['user', 'displayName'],
35+
template: '<div class="avatar-stub" />',
36+
},
37+
},
38+
},
39+
})
40+
41+
expect(wrapper.text()).toContain('Profile picture')
42+
const avatar = wrapper.findComponent({ name: 'NcAvatar' })
43+
expect(avatar.props('user')).toBe('ada')
44+
expect(avatar.props('displayName')).toBe('Ada Lovelace')
45+
})
46+
})

src/tests/views/CreatePassword.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,4 @@ describe('CreatePassword.vue', () => {
100100
expect(wrapper.emitted('password:created')).toEqual([[false]])
101101
expect(wrapper.vm.hasLoading).toBe(false)
102102
})
103-
})
103+
})

src/tests/views/Settings/ActiveSignings.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,4 @@ describe('ActiveSignings.vue', () => {
9999

100100
expect(wrapper.vm.getFileUrl(42)).toBe('/index.php/apps/files/?fileid=42')
101101
})
102-
})
102+
})

src/tests/views/Settings/CertificateEngine.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,4 @@ describe('CertificateEngine.vue', () => {
9090

9191
expect(emitMock).not.toHaveBeenCalled()
9292
})
93-
})
93+
})
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 LibreSign 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 axios from '@nextcloud/axios'
10+
import CertificatePolicy from '../../../views/Settings/CertificatePolicy.vue'
11+
import * as viewer from '../../../utils/viewer.js'
12+
13+
const loadStateMock = vi.fn()
14+
15+
vi.mock('@nextcloud/axios', () => ({
16+
default: {
17+
post: vi.fn(),
18+
delete: vi.fn(),
19+
},
20+
}))
21+
22+
vi.mock('@nextcloud/initial-state', () => ({
23+
loadState: (...args: unknown[]) => loadStateMock(...args),
24+
}))
25+
26+
vi.mock('@nextcloud/l10n', () => ({
27+
t: vi.fn((_app: string, text: string) => text),
28+
translate: vi.fn((_app: string, text: string) => text),
29+
translatePlural: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
30+
n: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
31+
getLanguage: vi.fn(() => 'en'),
32+
getLocale: vi.fn(() => 'en'),
33+
isRTL: vi.fn(() => false),
34+
}))
35+
36+
vi.mock('@nextcloud/router', () => ({
37+
generateOcsUrl: vi.fn((path: string) => path),
38+
}))
39+
40+
vi.mock('../../../utils/viewer.js', () => ({
41+
openDocument: vi.fn(),
42+
}))
43+
44+
describe('CertificatePolicy.vue', () => {
45+
beforeEach(() => {
46+
vi.clearAllMocks()
47+
loadStateMock.mockImplementation((_app: string, key: string) => {
48+
if (key === 'certificate_policies_oid') {
49+
return '1.2.3'
50+
}
51+
52+
if (key === 'certificate_policies_cps') {
53+
return '/apps/files/cps.pdf'
54+
}
55+
56+
return ''
57+
})
58+
})
59+
60+
function createWrapper() {
61+
return mount(CertificatePolicy, {
62+
global: {
63+
stubs: {
64+
NcButton: { template: '<button><slot /><slot name="icon" /></button>' },
65+
NcNoteCard: { template: '<div><slot /></div>' },
66+
NcTextField: true,
67+
NcIconSvgWrapper: true,
68+
NcLoadingIcon: true,
69+
},
70+
},
71+
})
72+
}
73+
74+
it('emits the initial validity on mount', () => {
75+
const wrapper = createWrapper()
76+
77+
expect(wrapper.emitted('certificate-policy-valid')).toEqual([['/apps/files/cps.pdf']])
78+
})
79+
80+
it('opens the current CPS document in the viewer', () => {
81+
const wrapper = createWrapper()
82+
83+
wrapper.vm.view()
84+
85+
expect(viewer.openDocument).toHaveBeenCalledWith({
86+
fileUrl: '/apps/files/cps.pdf',
87+
filename: 'Certificate Policy',
88+
nodeId: null,
89+
})
90+
})
91+
92+
it('persists the OID and toggles the success flag', async () => {
93+
vi.useFakeTimers()
94+
vi.mocked(axios.post).mockResolvedValue({ data: {} })
95+
const wrapper = createWrapper()
96+
97+
wrapper.vm.OID = '2.5.4.3'
98+
await wrapper.vm._saveOID()
99+
100+
expect(axios.post).toHaveBeenCalledWith('/apps/libresign/api/v1/admin/certificate-policy/oid', {
101+
oid: '2.5.4.3',
102+
})
103+
expect(wrapper.vm.dislaySuccessOID).toBe(true)
104+
105+
await vi.advanceTimersByTimeAsync(2000)
106+
expect(wrapper.vm.dislaySuccessOID).toBe(false)
107+
108+
vi.useRealTimers()
109+
})
110+
111+
it('removes the CPS and emits the updated validity', async () => {
112+
vi.mocked(axios.delete).mockResolvedValue({ data: {} })
113+
const wrapper = createWrapper()
114+
115+
await wrapper.vm.removeCps()
116+
await flushPromises()
117+
118+
expect(axios.delete).toHaveBeenCalledWith('/apps/libresign/api/v1/admin/certificate-policy')
119+
expect(wrapper.vm.CPS).toBe('')
120+
expect(wrapper.emitted('certificate-policy-valid')?.at(-1)).toEqual([''])
121+
})
122+
})

src/tests/views/Settings/CollectMetadata.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,4 @@ describe('CollectMetadata.vue', () => {
8282

8383
expect(emitMock).toHaveBeenCalledWith('collect-metadata:changed')
8484
})
85-
})
85+
})

src/tests/views/Settings/ConfigureCheck.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,4 @@ describe('ConfigureCheck.vue', () => {
6363
expect(wrapper.text()).toContain('Install Java')
6464
expect(wrapper.find('td.error').exists()).toBe(true)
6565
})
66-
})
66+
})

0 commit comments

Comments
 (0)