feat: signing reminders (#1749)

This commit is contained in:
Ephraim Duncan
2026-04-14 11:01:53 +00:00
committed by GitHub
parent 6d7bd212bf
commit 4935f387bf
69 changed files with 1426 additions and 156 deletions
@@ -31,6 +31,12 @@ const TEST_SETTINGS_VALUES = {
expirationMode: 'Custom duration',
expirationAmount: 5,
expirationUnit: 'Weeks',
reminderMode: 'Enabled',
reminderSendAfterAmount: 3,
reminderSendAfterUnit: 'Days',
reminderRepeatMode: 'Custom interval',
reminderRepeatAmount: 7,
reminderRepeatUnit: 'Days',
accessAuth: 'Require account',
actionAuth: 'Require password',
visibility: 'Managers and above',
@@ -42,6 +48,10 @@ const DB_EXPECTED_VALUES = {
timezone: 'Europe/London',
distributionMethod: DocumentDistributionMethod.NONE,
envelopeExpirationPeriod: { unit: 'week', amount: 5 },
reminderSettings: {
sendAfter: { unit: 'day', amount: 3 },
repeatEvery: { unit: 'day', amount: 7 },
},
visibility: DocumentVisibility.MANAGER_AND_ABOVE,
globalAccessAuth: ['ACCOUNT'],
globalActionAuth: ['PASSWORD'],
@@ -130,6 +140,66 @@ const runSettingsFlow = async (
await root.getByRole('option', { name: TEST_SETTINGS_VALUES.expirationUnit }).click();
await clickSettingsDialogHeader(root);
// Configure reminder settings.
await root.getByRole('button', { name: 'Reminders' }).click();
await root.locator('[data-testid="reminder-mode-select"]').click();
await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderMode }).click();
await clickSettingsDialogHeader(root);
await root.locator('[data-testid="reminder-send-after-amount"]').clear();
await root
.locator('[data-testid="reminder-send-after-amount"]')
.fill(String(TEST_SETTINGS_VALUES.reminderSendAfterAmount));
await root.locator('[data-testid="reminder-send-after-unit"]').click();
await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderSendAfterUnit }).click();
await clickSettingsDialogHeader(root);
await root.locator('[data-testid="reminder-repeat-mode-select"]').click();
await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderRepeatMode }).click();
await clickSettingsDialogHeader(root);
await root.locator('[data-testid="reminder-repeat-amount"]').clear();
await root
.locator('[data-testid="reminder-repeat-amount"]')
.fill(String(TEST_SETTINGS_VALUES.reminderRepeatAmount));
await root.locator('[data-testid="reminder-repeat-unit"]').click();
await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderRepeatUnit }).click();
await clickSettingsDialogHeader(root);
const spinbuttons = root.getByRole('spinbutton');
await spinbuttons.first().clear();
await spinbuttons.first().fill(String(TEST_SETTINGS_VALUES.reminderSendAfterAmount));
const sendAfterUnitTrigger = root
.locator('button[role="combobox"]')
.filter({ hasText: /Days|Weeks|Months/ })
.first();
await sendAfterUnitTrigger.click();
await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderSendAfterUnit }).click();
await clickSettingsDialogHeader(root);
const repeatModeSelect = root
.locator('button[role="combobox"]')
.filter({ hasText: /Custom interval|Don't repeat/ })
.first();
await repeatModeSelect.click();
await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderRepeatMode }).click();
await clickSettingsDialogHeader(root);
await spinbuttons.last().clear();
await spinbuttons.last().fill(String(TEST_SETTINGS_VALUES.reminderRepeatAmount));
const repeatUnitTrigger = root
.locator('button[role="combobox"]')
.filter({ hasText: /Days|Weeks|Months/ })
.last();
await repeatUnitTrigger.click();
await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderRepeatUnit }).click();
await clickSettingsDialogHeader(root);
await root.getByRole('button', { name: 'Email' }).click();
await root.locator('#recipientSigned').click();
await root.locator('#recipientSigningRequest').click();
@@ -200,6 +270,27 @@ const runSettingsFlow = async (
.first(),
).toBeVisible();
// Verify reminder settings persisted in UI.
await root.getByRole('button', { name: 'Reminders' }).click();
await expect(root.locator('[data-testid="reminder-mode-select"]')).toContainText(
TEST_SETTINGS_VALUES.reminderMode,
);
await expect(root.locator('[data-testid="reminder-send-after-amount"]')).toHaveValue(
String(TEST_SETTINGS_VALUES.reminderSendAfterAmount),
);
await expect(root.locator('[data-testid="reminder-send-after-unit"]')).toContainText(
TEST_SETTINGS_VALUES.reminderSendAfterUnit,
);
await expect(root.locator('[data-testid="reminder-repeat-mode-select"]')).toContainText(
TEST_SETTINGS_VALUES.reminderRepeatMode,
);
await expect(root.locator('[data-testid="reminder-repeat-amount"]')).toHaveValue(
String(TEST_SETTINGS_VALUES.reminderRepeatAmount),
);
await expect(root.locator('[data-testid="reminder-repeat-unit"]')).toContainText(
TEST_SETTINGS_VALUES.reminderRepeatUnit,
);
await root.getByRole('button', { name: 'Email' }).click();
await expect(root.locator('#recipientSigned')).toHaveAttribute('aria-checked', 'false');
await expect(root.locator('#recipientSigningRequest')).toHaveAttribute('aria-checked', 'false');
@@ -285,6 +376,7 @@ const assertEnvelopeSettingsPersistedInDatabase = async ({
expect(envelope.documentMeta.envelopeExpirationPeriod).toEqual(
DB_EXPECTED_VALUES.envelopeExpirationPeriod,
);
expect(envelope.documentMeta.reminderSettings).toEqual(DB_EXPECTED_VALUES.reminderSettings);
expect(envelope.documentMeta.redirectUrl).toBe(TEST_SETTINGS_VALUES.redirectUrl);
expect(envelope.documentMeta.emailReplyTo).toBe(TEST_SETTINGS_VALUES.replyTo);
expect(envelope.documentMeta.subject).toBe(TEST_SETTINGS_VALUES.subject);
@@ -25,7 +25,7 @@ test('[ENVELOPE_EXPIRATION]: set custom expiration period at organisation level'
await expect(page.getByRole('button', { name: 'Update' }).first()).toBeVisible();
// Change the amount to 2.
const amountInput = page.getByRole('spinbutton');
const amountInput = page.getByTestId('envelope-expiration-amount');
await amountInput.clear();
await amountInput.fill('2');
@@ -33,9 +33,7 @@ test('[ENVELOPE_EXPIRATION]: set custom expiration period at organisation level'
// In the duration mode, there's a mode select and a unit select.
// The unit select is inside the duration row, after the number input.
// Let's find the select trigger that contains the unit text.
const unitTrigger = page
.locator('button[role="combobox"]')
.filter({ hasText: /Months|Days|Weeks|Years/ });
const unitTrigger = page.getByTestId('envelope-expiration-unit');
await unitTrigger.click();
await page.getByRole('option', { name: 'Weeks' }).click();
@@ -65,9 +63,7 @@ test('[ENVELOPE_EXPIRATION]: disable expiration at organisation level', async ({
await expect(page.getByRole('button', { name: 'Update' }).first()).toBeVisible();
// Find the mode select (shows "Custom duration") and change to "Never expires".
const modeTrigger = page
.locator('button[role="combobox"]')
.filter({ hasText: 'Custom duration' });
const modeTrigger = page.getByTestId('envelope-expiration-mode');
await modeTrigger.click();
await page.getByRole('option', { name: 'Never expires' }).click();
@@ -118,11 +114,8 @@ test('[ENVELOPE_EXPIRATION]: team overrides organisation expiration', async ({ p
await expect(page.getByRole('button', { name: 'Update' }).first()).toBeVisible();
// Scope to the "Default Envelope Expiration" form field section.
const expirationSection = page.getByText('Default Envelope Expiration').locator('..');
// The expiration picker mode select should show "Inherit from organisation" by default.
const modeTrigger = expirationSection.locator('button[role="combobox"]').first();
const modeTrigger = page.getByTestId('envelope-expiration-mode');
await expect(modeTrigger).toBeVisible();
// Switch to custom duration.
@@ -130,13 +123,11 @@ test('[ENVELOPE_EXPIRATION]: team overrides organisation expiration', async ({ p
await page.getByRole('option', { name: 'Custom duration' }).click();
// Set to 5 days.
const amountInput = expirationSection.getByRole('spinbutton');
const amountInput = page.getByTestId('envelope-expiration-amount');
await amountInput.clear();
await amountInput.fill('5');
const unitTrigger = expirationSection
.locator('button[role="combobox"]')
.filter({ hasText: /Months|Days|Weeks|Years/ });
const unitTrigger = page.getByTestId('envelope-expiration-unit');
await unitTrigger.click();
await page.getByRole('option', { name: 'Days' }).click();