Skip to content

Commit 9fdd868

Browse files
refactor(vue3): migrate ActiveSignings to script setup ts
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 4cee448 commit 9fdd868

2 files changed

Lines changed: 212 additions & 100 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 LibreSign contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { flushPromises, mount } from '@vue/test-utils'
8+
import axios from '@nextcloud/axios'
9+
10+
import ActiveSignings from '../../../views/Settings/ActiveSignings.vue'
11+
12+
vi.mock('@nextcloud/axios', () => ({
13+
default: {
14+
get: vi.fn(),
15+
},
16+
}))
17+
18+
vi.mock('@nextcloud/l10n', () => ({
19+
t: vi.fn((_app: string, text: string, vars?: Record<string, string>) => {
20+
if (vars) {
21+
return text.replace(/{(\w+)}/g, (_m: string, key: string) => vars[key] || key)
22+
}
23+
return text
24+
}),
25+
translate: vi.fn((_app: string, text: string) => text),
26+
translatePlural: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
27+
n: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
28+
getLanguage: vi.fn(() => 'en'),
29+
getLocale: vi.fn(() => 'en'),
30+
isRTL: vi.fn(() => false),
31+
}))
32+
33+
vi.mock('@nextcloud/router', () => ({
34+
generateOcsUrl: vi.fn((path: string) => path),
35+
}))
36+
37+
vi.mock('@nextcloud/moment', () => ({
38+
default: vi.fn((value?: number | string) => ({
39+
format: vi.fn(() => '12:00:00'),
40+
fromNow: vi.fn(() => `from ${value}`),
41+
})),
42+
}))
43+
44+
describe('ActiveSignings.vue', () => {
45+
beforeEach(() => {
46+
vi.clearAllMocks()
47+
vi.useFakeTimers()
48+
})
49+
50+
afterEach(() => {
51+
vi.useRealTimers()
52+
})
53+
54+
function createWrapper() {
55+
return mount(ActiveSignings, {
56+
global: {
57+
stubs: {
58+
NcSettingsSection: { template: '<div><slot /></div>' },
59+
NcButton: true,
60+
NcIconSvgWrapper: true,
61+
NcCheckboxRadioSwitch: true,
62+
NcLoadingIcon: true,
63+
},
64+
},
65+
})
66+
}
67+
68+
it('loads active signings on mount', async () => {
69+
vi.mocked(axios.get).mockResolvedValue({
70+
data: {
71+
ocs: {
72+
data: [{ id: 7, name: 'Contract.pdf', signerEmail: 'signer@example.com', updatedAt: 123 }],
73+
},
74+
},
75+
})
76+
77+
const wrapper = createWrapper()
78+
await flushPromises()
79+
80+
expect(wrapper.vm.signings).toHaveLength(1)
81+
expect(wrapper.vm.lastUpdateTime).toBe('12:00:00')
82+
})
83+
84+
it('starts and stops auto refresh with the toggle', async () => {
85+
vi.mocked(axios.get).mockResolvedValue({ data: { ocs: { data: [] } } })
86+
const wrapper = createWrapper()
87+
await flushPromises()
88+
89+
expect(wrapper.vm.refreshInterval).not.toBeNull()
90+
wrapper.vm.autoRefresh = false
91+
await wrapper.vm.$nextTick()
92+
expect(wrapper.vm.refreshInterval).toBeNull()
93+
})
94+
95+
it('formats the file URL consistently', async () => {
96+
vi.mocked(axios.get).mockResolvedValue({ data: { ocs: { data: [] } } })
97+
const wrapper = createWrapper()
98+
await flushPromises()
99+
100+
expect(wrapper.vm.getFileUrl(42)).toBe('/index.php/apps/files/?fileid=42')
101+
})
102+
})

src/views/Settings/ActiveSignings.vue

Lines changed: 110 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -67,117 +67,127 @@
6767
</NcSettingsSection>
6868
</template>
6969

70-
<script>
70+
<script setup lang="ts">
7171
import axios from '@nextcloud/axios'
72-
import {
73-
mdiRefresh,
74-
} from '@mdi/js'
72+
import { t } from '@nextcloud/l10n'
7573
import Moment from '@nextcloud/moment'
7674
import { generateOcsUrl } from '@nextcloud/router'
77-
import { t } from '@nextcloud/l10n'
75+
import { mdiRefresh } from '@mdi/js'
76+
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
7877
7978
import NcButton from '@nextcloud/vue/components/NcButton'
80-
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
8179
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
80+
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
8281
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
8382
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
8483
85-
export default {
86-
name: 'ActiveSignings',
87-
components: {
88-
NcButton,
89-
NcSettingsSection,
90-
NcIconSvgWrapper,
91-
NcCheckboxRadioSwitch,
92-
NcLoadingIcon,
93-
},
94-
setup() {
95-
return {
96-
mdiRefresh,
97-
}
98-
},
99-
data() {
100-
return {
101-
signings: [],
102-
loading: false,
103-
autoRefresh: true,
104-
lastUpdateTime: '',
105-
refreshInterval: null,
106-
}
107-
},
108-
computed: {
109-
// Auto-refresh every 10 seconds when enabled
110-
shouldRefresh() {
111-
return this.autoRefresh
112-
},
113-
},
114-
watch: {
115-
autoRefresh(newValue) {
116-
if (newValue) {
117-
this.startAutoRefresh()
118-
} else {
119-
this.stopAutoRefresh()
120-
}
121-
},
122-
},
123-
mounted() {
124-
this.refresh()
125-
if (this.autoRefresh) {
126-
this.startAutoRefresh()
84+
type SigningItem = {
85+
id: number
86+
name: string
87+
signerDisplayName?: string
88+
signerEmail?: string
89+
updatedAt: number
90+
}
91+
92+
type OcsResponse = {
93+
data?: {
94+
ocs?: {
95+
data?: SigningItem[]
12796
}
128-
},
129-
beforeUnmount() {
130-
this.stopAutoRefresh()
131-
},
132-
methods: {
133-
t,
134-
135-
async refresh() {
136-
this.loading = true
137-
try {
138-
const response = await axios.get(
139-
generateOcsUrl('/apps/libresign/api/v1/admin/active-signings')
140-
)
141-
this.signings = response.data.ocs.data || []
142-
this.updateLastRefreshTime()
143-
} catch (error) {
144-
console.error('Failed to fetch active signings:', error)
145-
this.signings = []
146-
} finally {
147-
this.loading = false
148-
}
149-
},
150-
151-
startAutoRefresh() {
152-
if (this.refreshInterval) {
153-
clearInterval(this.refreshInterval)
154-
}
155-
this.refreshInterval = setInterval(() => {
156-
this.refresh()
157-
}, 10000) // Refresh every 10 seconds
158-
},
159-
160-
stopAutoRefresh() {
161-
if (this.refreshInterval) {
162-
clearInterval(this.refreshInterval)
163-
this.refreshInterval = null
164-
}
165-
},
166-
167-
updateLastRefreshTime() {
168-
this.lastUpdateTime = Moment().format('HH:mm:ss')
169-
},
170-
171-
formatTime(timestamp) {
172-
return Moment(timestamp * 1000).fromNow()
173-
},
174-
175-
getFileUrl(fileId) {
176-
// Build URL to view the file
177-
return `/index.php/apps/files/?fileid=${fileId}`
178-
},
179-
},
97+
}
18098
}
99+
100+
defineOptions({
101+
name: 'ActiveSignings',
102+
})
103+
104+
const signings = ref<SigningItem[]>([])
105+
const loading = ref(false)
106+
const autoRefresh = ref(true)
107+
const lastUpdateTime = ref('')
108+
const refreshInterval = ref<ReturnType<typeof setInterval> | null>(null)
109+
110+
const shouldRefresh = computed(() => autoRefresh.value)
111+
112+
async function refresh() {
113+
loading.value = true
114+
try {
115+
const response = await axios.get(
116+
generateOcsUrl('/apps/libresign/api/v1/admin/active-signings'),
117+
) as OcsResponse
118+
signings.value = response.data?.ocs?.data || []
119+
updateLastRefreshTime()
120+
} catch (error) {
121+
console.error('Failed to fetch active signings:', error)
122+
signings.value = []
123+
} finally {
124+
loading.value = false
125+
}
126+
}
127+
128+
function startAutoRefresh() {
129+
if (refreshInterval.value) {
130+
clearInterval(refreshInterval.value)
131+
}
132+
refreshInterval.value = setInterval(() => {
133+
void refresh()
134+
}, 10000)
135+
}
136+
137+
function stopAutoRefresh() {
138+
if (refreshInterval.value) {
139+
clearInterval(refreshInterval.value)
140+
refreshInterval.value = null
141+
}
142+
}
143+
144+
function updateLastRefreshTime() {
145+
lastUpdateTime.value = Moment().format('HH:mm:ss')
146+
}
147+
148+
function formatTime(timestamp: number) {
149+
return Moment(timestamp * 1000).fromNow()
150+
}
151+
152+
function getFileUrl(fileId: number) {
153+
return `/index.php/apps/files/?fileid=${fileId}`
154+
}
155+
156+
watch(autoRefresh, (newValue) => {
157+
if (newValue) {
158+
startAutoRefresh()
159+
} else {
160+
stopAutoRefresh()
161+
}
162+
})
163+
164+
onMounted(() => {
165+
void refresh()
166+
if (autoRefresh.value) {
167+
startAutoRefresh()
168+
}
169+
})
170+
171+
onBeforeUnmount(() => {
172+
stopAutoRefresh()
173+
})
174+
175+
defineExpose({
176+
t,
177+
mdiRefresh,
178+
signings,
179+
loading,
180+
autoRefresh,
181+
lastUpdateTime,
182+
refreshInterval,
183+
shouldRefresh,
184+
refresh,
185+
startAutoRefresh,
186+
stopAutoRefresh,
187+
updateLastRefreshTime,
188+
formatTime,
189+
getFileUrl,
190+
})
181191
</script>
182192

183193
<style lang="scss" scoped>

0 commit comments

Comments
 (0)