Skip to content

Commit 7c6ac41

Browse files
authored
Merge pull request #7197 from LibreSign/backport/7191/stable32
[stable32] chore: migration to vue3
2 parents 4a94faf + 9e24a1d commit 7c6ac41

25 files changed

Lines changed: 2318 additions & 2026 deletions

playwright/e2e/visible-element-persistence.spec.ts

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@ import { login } from '../support/nc-login'
88
import { configureOpenSsl, setAppConfig } from '../support/nc-provisioning'
99

1010
test('visible signature element persists and can be deleted', async ({ page }) => {
11+
const requestSignatureTab = page.locator('#request-signature-tab')
12+
const setupSignaturePositionsButton = requestSignatureTab.getByRole('button', { name: 'Setup signature positions' })
13+
const openSidebarButton = page.getByRole('button', { name: 'Open sidebar' })
14+
15+
async function reopenFileFromUuid(uuid: string) {
16+
await page.goto(`./apps/libresign/f/filelist/sign?uuid=${uuid}`)
17+
if (await openSidebarButton.isVisible()) {
18+
await openSidebarButton.click()
19+
}
20+
await expect(setupSignaturePositionsButton).toBeVisible()
21+
await setupSignaturePositionsButton.click()
22+
}
23+
1124
await login(
1225
page.request,
1326
process.env.NEXTCLOUD_ADMIN_USER ?? 'admin',
@@ -44,8 +57,16 @@ test('visible signature element persists and can be deleted', async ({ page }) =
4457
await page.getByRole('textbox', { name: 'Signer name' }).fill('Admin Name')
4558

4659
// Save the signer first, then open the signature positions modal
60+
const createRequestResponsePromise = page.waitForResponse(response =>
61+
response.url().includes('/apps/libresign/api/v1/request-signature')
62+
&& ['POST', 'PATCH'].includes(response.request().method()),
63+
)
4764
await page.getByRole('button', { name: 'Save' }).click()
48-
await page.getByRole('button', { name: 'Setup signature positions' }).click()
65+
const createRequestResponse = await createRequestResponsePromise
66+
const createRequestBody = await createRequestResponse.json()
67+
const requestUuid = createRequestBody.ocs.data.uuid as string
68+
await expect(setupSignaturePositionsButton).toBeVisible()
69+
await setupSignaturePositionsButton.click()
4970
await expect(page.getByLabel('Page 1 of 1.')).toBeVisible()
5071

5172
// Select the signer to enter element-placement mode
@@ -68,23 +89,8 @@ test('visible signature element persists and can be deleted', async ({ page }) =
6889
// Save closes the modal and persists the element via API
6990
await page.getByLabel('Signature positions').getByRole('button', { name: 'Save' }).click()
7091

71-
// Navigate to the Files list and ensure it is sorted by Created at, newest first (descending)
72-
await page.locator('#fileslist').getByRole('link', { name: 'Files' }).click()
73-
const createdAtTh = page.locator('th.files-list__row-created_at')
74-
const sortDirection = await createdAtTh.getAttribute('aria-sort')
75-
if (sortDirection !== 'descending') {
76-
await page.getByRole('button', { name: 'Created at' }).click()
77-
if (sortDirection === 'none') {
78-
// Column was sortable but not active: first click set it to ascending, one more for descending
79-
await page.getByRole('button', { name: 'Created at' }).click()
80-
}
81-
}
82-
const firstRow = page.locator('[data-cy-files-list-tbody] tr.files-list__row').first()
83-
await expect(firstRow.getByRole('button', { name: 'small_valid' })).toBeVisible()
84-
85-
// Re-open the document by clicking the file name — the sidebar opens automatically
86-
await firstRow.getByRole('button', { name: 'small_valid' }).click()
87-
await page.getByRole('button', { name: 'Setup signature positions' }).click()
92+
// Open the document again through the Files route using the request uuid to force a fresh load
93+
await reopenFileFromUuid(requestUuid)
8894

8995
// Verify the element survived the round-trip to the server
9096
await expect(
@@ -104,22 +110,9 @@ test('visible signature element persists and can be deleted', async ({ page }) =
104110

105111
// Navigate away and back to force a fresh load from the server
106112
await page.getByRole('link', { name: 'Request' }).click()
107-
await page.locator('#fileslist').getByRole('link', { name: 'Files' }).click()
108-
const createdAtTh2 = page.getByRole('columnheader', { name: 'Created at' })
109-
const sortDirection2 = await createdAtTh2.evaluate((el: HTMLElement) => el.ariaSort)
110-
if (sortDirection2 !== 'descending') {
111-
await page.getByRole('button', { name: 'Created at' }).click()
112-
if (sortDirection2 === 'none') {
113-
// Column was sortable but not active: first click set it to ascending, one more for descending
114-
await page.getByRole('button', { name: 'Created at' }).click()
115-
}
116-
}
117-
const lastRow = page.locator('[data-cy-files-list-tbody] tr.files-list__row').first()
118-
await expect(lastRow.getByRole('button', { name: 'small_valid' })).toBeVisible()
119113

120114
// Re-open the document one last time and confirm the element is gone
121-
await lastRow.getByRole('button', { name: 'small_valid' }).click()
122-
await page.getByRole('button', { name: 'Setup signature positions' }).click()
115+
await reopenFileFromUuid(requestUuid)
123116
await expect(page.getByLabel('Page 1 of 1.')).toBeVisible()
124117

125118
await expect(

src/components/Common/EditNameDialog.vue

Lines changed: 109 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
</NcDialog>
2323
</template>
2424

25-
<script>
25+
<script setup lang="ts">
2626
import { t } from '@nextcloud/l10n'
27+
import { computed, ref, watch } from 'vue'
2728
2829
import NcButton from '@nextcloud/vue/components/NcButton'
2930
import NcDialog from '@nextcloud/vue/components/NcDialog'
@@ -32,117 +33,120 @@ import NcTextField from '@nextcloud/vue/components/NcTextField'
3233
3334
import { ENVELOPE_NAME_MIN_LENGTH, ENVELOPE_NAME_MAX_LENGTH } from '../../constants.js'
3435
35-
export default {
36+
defineOptions({
3637
name: 'EditNameDialog',
37-
components: {
38-
NcButton,
39-
NcDialog,
40-
NcNoteCard,
41-
NcTextField,
42-
},
43-
props: {
44-
name: {
45-
type: String,
46-
default: '',
47-
},
48-
title: {
49-
type: String,
50-
default: 'Edit name',
51-
},
52-
label: {
53-
type: String,
54-
default: 'Name',
55-
},
56-
placeholder: {
57-
type: String,
58-
default: 'Enter name',
59-
},
60-
},
61-
emits: ['close'],
62-
data() {
63-
return {
64-
localName: this.name || '',
65-
localSuccessMessage: '',
66-
localErrorMessage: '',
67-
ENVELOPE_NAME_MIN_LENGTH,
68-
ENVELOPE_NAME_MAX_LENGTH,
69-
}
70-
},
71-
computed: {
72-
isNameValid() {
73-
const trimmedName = this.localName.trim()
74-
return trimmedName.length >= ENVELOPE_NAME_MIN_LENGTH && trimmedName.length <= ENVELOPE_NAME_MAX_LENGTH
75-
},
76-
dialogButtons() {
77-
return [
78-
{
79-
label: this.t('libresign', 'Cancel'),
80-
callback: () => {
81-
this.handleClose()
82-
},
83-
},
84-
{
85-
label: this.t('libresign', 'Save'),
86-
type: 'primary',
87-
disabled: !this.isNameValid,
88-
callback: () => {
89-
this.handleSave()
90-
},
91-
},
92-
]
93-
},
94-
},
95-
watch: {
96-
name(newVal) {
97-
this.localName = newVal || ''
98-
},
99-
},
100-
methods: {
101-
t,
102-
clearMessages() {
103-
this.localSuccessMessage = ''
104-
this.localErrorMessage = ''
105-
},
106-
showSuccess(message) {
107-
this.clearMessages()
108-
this.localSuccessMessage = message
109-
setTimeout(() => {
110-
this.localSuccessMessage = ''
111-
}, 5000)
112-
},
113-
showError(message) {
114-
this.clearMessages()
115-
this.localErrorMessage = message
116-
},
117-
handleClose() {
118-
this.$emit('close', null)
119-
},
120-
handleSave() {
121-
if (!this.isNameValid) {
122-
return
123-
}
38+
})
12439
125-
const trimmedName = this.localName.trim()
40+
const props = withDefaults(defineProps<{
41+
name?: string | null
42+
title?: string
43+
label?: string
44+
placeholder?: string
45+
}>(), {
46+
name: '',
47+
title: 'Edit name',
48+
label: 'Name',
49+
placeholder: 'Enter name',
50+
})
12651
127-
if (!trimmedName) {
128-
this.showError(this.t('libresign', 'Name cannot be empty'))
129-
return
130-
}
52+
const emit = defineEmits<{
53+
(event: 'close', value: string | null): void
54+
}>()
13155
132-
if (trimmedName.length < ENVELOPE_NAME_MIN_LENGTH) {
133-
this.showError(this.t('libresign', 'Name must be at least {min} characters', { min: ENVELOPE_NAME_MIN_LENGTH }))
134-
return
135-
}
56+
const localName = ref(props.name || '')
57+
const localSuccessMessage = ref('')
58+
const localErrorMessage = ref('')
13659
137-
if (trimmedName.length > ENVELOPE_NAME_MAX_LENGTH) {
138-
this.showError(this.t('libresign', 'Name must not exceed {max} characters', { max: ENVELOPE_NAME_MAX_LENGTH }))
139-
return
140-
}
60+
const isNameValid = computed(() => {
61+
const trimmedName = localName.value.trim()
62+
return trimmedName.length >= ENVELOPE_NAME_MIN_LENGTH && trimmedName.length <= ENVELOPE_NAME_MAX_LENGTH
63+
})
14164
142-
this.$emit('close', trimmedName)
143-
},
144-
},
65+
function clearMessages() {
66+
localSuccessMessage.value = ''
67+
localErrorMessage.value = ''
68+
}
69+
70+
function showSuccess(message: string) {
71+
clearMessages()
72+
localSuccessMessage.value = message
73+
setTimeout(() => {
74+
localSuccessMessage.value = ''
75+
}, 5000)
76+
}
77+
78+
function showError(message: string) {
79+
clearMessages()
80+
localErrorMessage.value = message
81+
}
82+
83+
function handleClose() {
84+
emit('close', null)
14585
}
86+
87+
function handleSave() {
88+
if (!isNameValid.value) {
89+
return
90+
}
91+
92+
const trimmedName = localName.value.trim()
93+
94+
if (!trimmedName) {
95+
showError(t('libresign', 'Name cannot be empty'))
96+
return
97+
}
98+
99+
if (trimmedName.length < ENVELOPE_NAME_MIN_LENGTH) {
100+
showError(t('libresign', 'Name must be at least {min} characters', { min: ENVELOPE_NAME_MIN_LENGTH }))
101+
return
102+
}
103+
104+
if (trimmedName.length > ENVELOPE_NAME_MAX_LENGTH) {
105+
showError(t('libresign', 'Name must not exceed {max} characters', { max: ENVELOPE_NAME_MAX_LENGTH }))
106+
return
107+
}
108+
109+
emit('close', trimmedName)
110+
}
111+
112+
const dialogButtons = computed(() => {
113+
return [
114+
{
115+
label: t('libresign', 'Cancel'),
116+
callback: () => {
117+
handleClose()
118+
},
119+
},
120+
{
121+
label: t('libresign', 'Save'),
122+
type: 'primary',
123+
disabled: !isNameValid.value,
124+
callback: () => {
125+
handleSave()
126+
},
127+
},
128+
]
129+
})
130+
131+
watch(() => props.name, (newVal) => {
132+
localName.value = newVal || ''
133+
})
134+
135+
defineExpose({
136+
localName,
137+
localSuccessMessage,
138+
localErrorMessage,
139+
ENVELOPE_NAME_MIN_LENGTH,
140+
ENVELOPE_NAME_MAX_LENGTH,
141+
isNameValid,
142+
dialogButtons,
143+
t,
144+
clearMessages,
145+
showSuccess,
146+
showError,
147+
handleClose,
148+
handleSave,
149+
})
146150
</script>
147151

148152
<style scoped>

0 commit comments

Comments
 (0)