mirror of
https://github.com/documenso/documenso.git
synced 2026-08-27 00:32:29 +10:00
fix: wip
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { seedPendingDocumentWithFullFields } from '@documenso/prisma/seed/documents';
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { DocumentSigningOrder, RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
/**
|
||||
* A viewer's completion dialog activates the next-signer validator from
|
||||
* `allowDictateNextSigner` alone, while the name/email inputs only render
|
||||
* when a dictatable next recipient exists. When dictation is enabled but the
|
||||
* next step is not dictatable (a group of two or more, or the viewer is
|
||||
* last), submission must still work — historically it failed Zod validation
|
||||
* on the hidden inputs and "Mark as Viewed" silently did nothing.
|
||||
*/
|
||||
|
||||
const seedViewerDictationDocument = async (
|
||||
recipientsCreateOptions: {
|
||||
signingOrder: number;
|
||||
role?: RecipientRole;
|
||||
sendStatus?: SendStatus;
|
||||
}[],
|
||||
) => {
|
||||
const { user, team } = await seedUser();
|
||||
|
||||
const signers = await Promise.all(recipientsCreateOptions.map(async () => (await seedUser()).user));
|
||||
|
||||
const { recipients, document } = await seedPendingDocumentWithFullFields({
|
||||
owner: user,
|
||||
teamId: team.id,
|
||||
recipients: signers,
|
||||
recipientsCreateOptions,
|
||||
// No fields, so completion is not blocked by unsigned required fields.
|
||||
fields: [],
|
||||
updateDocumentOptions: {
|
||||
documentMeta: {
|
||||
upsert: {
|
||||
create: {
|
||||
signingOrder: DocumentSigningOrder.SEQUENTIAL,
|
||||
allowDictateNextSigner: true,
|
||||
},
|
||||
update: {
|
||||
signingOrder: DocumentSigningOrder.SEQUENTIAL,
|
||||
allowDictateNextSigner: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return { document, recipients };
|
||||
};
|
||||
|
||||
test('[NEXT_RECIPIENT_DICTATION]: viewer can mark as viewed when the next step is a group', async ({ page }) => {
|
||||
const { document, recipients } = await seedViewerDictationDocument([
|
||||
{ signingOrder: 1, role: RecipientRole.VIEWER },
|
||||
// The next step is a group of two, so there is no single dictatable next
|
||||
// recipient — the dialog must not demand one.
|
||||
{ signingOrder: 2, sendStatus: SendStatus.NOT_SENT },
|
||||
{ signingOrder: 2, sendStatus: SendStatus.NOT_SENT },
|
||||
]);
|
||||
|
||||
const [viewer, groupA, groupB] = recipients;
|
||||
|
||||
const signUrl = `/sign/${viewer.token}`;
|
||||
|
||||
await page.goto(signUrl);
|
||||
await expect(page.getByRole('heading', { name: 'View Document' })).toBeVisible();
|
||||
|
||||
const dialog = page.getByRole('dialog');
|
||||
|
||||
// Retry the click: it can land before hydration attaches the handler.
|
||||
await expect(async () => {
|
||||
await page.getByRole('button', { name: 'Mark as viewed', exact: true }).click();
|
||||
await expect(dialog).toBeVisible({ timeout: 2_000 });
|
||||
}).toPass();
|
||||
|
||||
// No dictation inputs: a group cannot be dictated over.
|
||||
await expect(dialog.getByText('Next Recipient Name')).not.toBeVisible();
|
||||
|
||||
await dialog.getByRole('button', { name: 'Mark as Viewed', exact: true }).click();
|
||||
|
||||
await page.waitForURL(`${signUrl}/complete`);
|
||||
|
||||
// The viewer completed and the group's step unlocked.
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const updatedRecipients = await prisma.recipient.findMany({
|
||||
where: { envelopeId: document.id },
|
||||
orderBy: { id: 'asc' },
|
||||
});
|
||||
|
||||
return updatedRecipients.map((recipient) => [recipient.signingStatus, recipient.sendStatus]);
|
||||
})
|
||||
.toEqual([
|
||||
[SigningStatus.SIGNED, SendStatus.SENT],
|
||||
[SigningStatus.NOT_SIGNED, SendStatus.SENT],
|
||||
[SigningStatus.NOT_SIGNED, SendStatus.SENT],
|
||||
]);
|
||||
|
||||
// Nobody was renamed: no next-signer values existed to apply.
|
||||
const groupAAfter = await prisma.recipient.findUniqueOrThrow({ where: { id: groupA.id } });
|
||||
const groupBAfter = await prisma.recipient.findUniqueOrThrow({ where: { id: groupB.id } });
|
||||
|
||||
expect(groupAAfter.email).toBe(groupA.email);
|
||||
expect(groupBAfter.email).toBe(groupB.email);
|
||||
});
|
||||
|
||||
test('[NEXT_RECIPIENT_DICTATION]: viewer can mark as viewed when they are the last recipient', async ({ page }) => {
|
||||
const { recipients } = await seedViewerDictationDocument([
|
||||
{ signingOrder: 1 },
|
||||
{ signingOrder: 2, role: RecipientRole.VIEWER, sendStatus: SendStatus.NOT_SENT },
|
||||
]);
|
||||
|
||||
const [signer, viewer] = recipients;
|
||||
|
||||
// Advance the flow to the viewer's turn.
|
||||
await completeDocumentWithToken({
|
||||
token: signer.token,
|
||||
id: { type: 'envelopeId', id: signer.envelopeId },
|
||||
});
|
||||
|
||||
const signUrl = `/sign/${viewer.token}`;
|
||||
|
||||
await page.goto(signUrl);
|
||||
await expect(page.getByRole('heading', { name: 'View Document' })).toBeVisible();
|
||||
|
||||
const dialog = page.getByRole('dialog');
|
||||
|
||||
// Retry the click: it can land before hydration attaches the handler.
|
||||
await expect(async () => {
|
||||
await page.getByRole('button', { name: 'Mark as viewed', exact: true }).click();
|
||||
await expect(dialog).toBeVisible({ timeout: 2_000 });
|
||||
}).toPass();
|
||||
|
||||
// No dictation inputs: there is nobody after the viewer.
|
||||
await expect(dialog.getByText('Next Recipient Name')).not.toBeVisible();
|
||||
|
||||
await dialog.getByRole('button', { name: 'Mark as Viewed', exact: true }).click();
|
||||
|
||||
await page.waitForURL(`${signUrl}/complete`);
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const viewerAfter = await prisma.recipient.findUniqueOrThrow({ where: { id: viewer.id } });
|
||||
|
||||
return viewerAfter.signingStatus;
|
||||
})
|
||||
.toBe(SigningStatus.SIGNED);
|
||||
});
|
||||
Reference in New Issue
Block a user