Files
documenso/packages/app-tests/e2e/envelope-editor-v2/envelope-attachments.spec.ts
T
2026-05-08 16:04:22 +10:00

322 lines
10 KiB
TypeScript

import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import { expect, type Page, test } from '@playwright/test';
import {
addEnvelopeItemPdf,
getEnvelopeEditorSettingsTrigger,
openDocumentEnvelopeEditor,
openEmbeddedEnvelopeEditor,
openTemplateEnvelopeEditor,
persistEmbeddedEnvelope,
type TEnvelopeEditorSurface,
} from '../fixtures/envelope-editor';
import { expectToastTextToBeVisible } from '../fixtures/generic';
const TEST_ATTACHMENTS = {
first: {
label: 'E2E First Attachment',
url: 'https://example.com/first-attachment',
},
second: {
label: 'E2E Second Attachment',
url: 'https://example.com/second-attachment',
},
third: {
label: 'E2E Third Attachment',
url: 'https://example.com/third-attachment',
},
};
type AttachmentFlowResult = {
externalId: string;
expectedAttachments: Array<{ label: string; url: string }>;
deletedAttachment: { label: string; url: string };
};
const openSettingsDialog = async (root: Page) => {
await getEnvelopeEditorSettingsTrigger(root).click();
await expect(root.getByRole('heading', { name: 'Document Settings' })).toBeVisible();
};
const updateExternalId = async (surface: TEnvelopeEditorSurface, externalId: string) => {
await openSettingsDialog(surface.root);
await surface.root.locator('input[name="externalId"]').fill(externalId);
await surface.root.getByRole('button', { name: 'Update' }).click();
if (!surface.isEmbedded) {
await expectToastTextToBeVisible(surface.root, 'Envelope updated');
}
};
const openAttachmentsPopover = async (root: Page) => {
await root.getByRole('button', { name: 'Attachments' }).click();
await expect(root.getByRole('heading', { name: 'Attachments', level: 4 })).toBeVisible();
};
const addAttachment = async (root: Page, { label, url }: { label: string; url: string }) => {
await root.getByRole('button', { name: 'Add Attachment' }).click();
await root.getByPlaceholder('Label').fill(label);
await root.getByPlaceholder('URL').fill(url);
await root.getByRole('button', { name: 'Add', exact: true }).click();
};
const getAttachmentItems = (root: Page) => root.locator('.rounded-md.border.border-border.p-2');
const getAttachmentDeleteButtons = (root: Page) => getAttachmentItems(root).locator('button');
const assertAttachmentVisibleInPopover = async (root: Page, { label, url }: { label: string; url: string }) => {
const items = getAttachmentItems(root);
const matchingItem = items.filter({ hasText: label });
await expect(matchingItem).toBeVisible();
await expect(matchingItem.locator(`a[href="${url}"]`)).toBeVisible();
};
const assertAttachmentNotVisibleInPopover = async (root: Page, label: string) => {
await expect(getAttachmentItems(root).filter({ hasText: label })).toHaveCount(0);
};
const assertAttachmentCount = async (root: Page, count: number) => {
const button = root.getByRole('button', { name: 'Attachments' });
if (count > 0) {
await expect(button).toContainText(`(${count})`);
} else {
await expect(button).not.toContainText('(');
}
};
const assertAttachmentsInDatabase = async ({
externalId,
surface,
expectedAttachments,
deletedLabel,
}: {
externalId: string;
surface: TEnvelopeEditorSurface;
expectedAttachments: Array<{ label: string; url: string }>;
deletedLabel?: string;
}) => {
const envelope = await prisma.envelope.findFirstOrThrow({
where: {
externalId,
userId: surface.userId,
teamId: surface.teamId,
type: surface.envelopeType,
},
include: {
envelopeAttachments: {
orderBy: {
createdAt: 'asc',
},
},
},
orderBy: {
createdAt: 'desc',
},
});
expect(envelope.envelopeAttachments).toHaveLength(expectedAttachments.length);
for (const expected of expectedAttachments) {
const found = envelope.envelopeAttachments.find((a) => a.label === expected.label);
expect(found).toBeDefined();
expect(found!.data).toBe(expected.url);
expect(found!.type).toBe('link');
}
if (deletedLabel) {
expect(envelope.envelopeAttachments.some((a) => a.label === deletedLabel)).toBe(false);
}
};
const runAttachmentFlow = async (surface: TEnvelopeEditorSurface): Promise<AttachmentFlowResult> => {
const externalId = `e2e-attachments-${nanoid()}`;
await updateExternalId(surface, externalId);
await openAttachmentsPopover(surface.root);
// Create first attachment.
await addAttachment(surface.root, TEST_ATTACHMENTS.first);
await expectToastTextToBeVisible(surface.root, 'Attachment added successfully.');
await assertAttachmentVisibleInPopover(surface.root, TEST_ATTACHMENTS.first);
await assertAttachmentCount(surface.root, 1);
await assertAttachmentsInDatabase({
externalId,
surface,
expectedAttachments: [TEST_ATTACHMENTS.first],
});
// Create second attachment.
await addAttachment(surface.root, TEST_ATTACHMENTS.second);
await expectToastTextToBeVisible(surface.root, 'Attachment added successfully.');
await assertAttachmentVisibleInPopover(surface.root, TEST_ATTACHMENTS.second);
await assertAttachmentCount(surface.root, 2);
await assertAttachmentsInDatabase({
externalId,
surface,
expectedAttachments: [TEST_ATTACHMENTS.first, TEST_ATTACHMENTS.second],
});
// Create third attachment.
await addAttachment(surface.root, TEST_ATTACHMENTS.third);
await expectToastTextToBeVisible(surface.root, 'Attachment added successfully.');
await assertAttachmentVisibleInPopover(surface.root, TEST_ATTACHMENTS.third);
await assertAttachmentCount(surface.root, 3);
await assertAttachmentsInDatabase({
externalId,
surface,
expectedAttachments: [TEST_ATTACHMENTS.first, TEST_ATTACHMENTS.second, TEST_ATTACHMENTS.third],
});
// Delete first attachment.
await getAttachmentDeleteButtons(surface.root).first().click();
await expectToastTextToBeVisible(surface.root, 'Attachment removed successfully.');
await expect(getAttachmentItems(surface.root)).toHaveCount(2);
await assertAttachmentNotVisibleInPopover(surface.root, TEST_ATTACHMENTS.first.label);
await assertAttachmentVisibleInPopover(surface.root, TEST_ATTACHMENTS.second);
await assertAttachmentVisibleInPopover(surface.root, TEST_ATTACHMENTS.third);
await assertAttachmentCount(surface.root, 2);
await assertAttachmentsInDatabase({
externalId,
surface,
expectedAttachments: [TEST_ATTACHMENTS.second, TEST_ATTACHMENTS.third],
deletedLabel: TEST_ATTACHMENTS.first.label,
});
return {
externalId,
expectedAttachments: [TEST_ATTACHMENTS.second, TEST_ATTACHMENTS.third],
deletedAttachment: TEST_ATTACHMENTS.first,
};
};
const runEmbeddedAttachmentFlow = async (surface: TEnvelopeEditorSurface): Promise<AttachmentFlowResult> => {
const externalId = `e2e-attachments-${nanoid()}`;
await updateExternalId(surface, externalId);
await openAttachmentsPopover(surface.root);
// Create first attachment.
await addAttachment(surface.root, TEST_ATTACHMENTS.first);
await expectToastTextToBeVisible(surface.root, 'Attachment added successfully.');
await assertAttachmentVisibleInPopover(surface.root, TEST_ATTACHMENTS.first);
await assertAttachmentCount(surface.root, 1);
// Create second attachment.
await addAttachment(surface.root, TEST_ATTACHMENTS.second);
await expectToastTextToBeVisible(surface.root, 'Attachment added successfully.');
await assertAttachmentVisibleInPopover(surface.root, TEST_ATTACHMENTS.second);
await assertAttachmentCount(surface.root, 2);
// Create third attachment.
await addAttachment(surface.root, TEST_ATTACHMENTS.third);
await expectToastTextToBeVisible(surface.root, 'Attachment added successfully.');
await assertAttachmentVisibleInPopover(surface.root, TEST_ATTACHMENTS.third);
await assertAttachmentCount(surface.root, 3);
// Delete first attachment.
await getAttachmentDeleteButtons(surface.root).first().click();
await expectToastTextToBeVisible(surface.root, 'Attachment removed successfully.');
await expect(getAttachmentItems(surface.root)).toHaveCount(2);
await assertAttachmentNotVisibleInPopover(surface.root, TEST_ATTACHMENTS.first.label);
await assertAttachmentVisibleInPopover(surface.root, TEST_ATTACHMENTS.second);
await assertAttachmentVisibleInPopover(surface.root, TEST_ATTACHMENTS.third);
await assertAttachmentCount(surface.root, 2);
return {
externalId,
expectedAttachments: [TEST_ATTACHMENTS.second, TEST_ATTACHMENTS.third],
deletedAttachment: TEST_ATTACHMENTS.first,
};
};
const assertAttachmentsPersistedInDatabase = async ({
surface,
externalId,
expectedAttachments,
deletedAttachment,
}: {
surface: TEnvelopeEditorSurface;
externalId: string;
expectedAttachments: AttachmentFlowResult['expectedAttachments'];
deletedAttachment: AttachmentFlowResult['deletedAttachment'];
}) => {
await assertAttachmentsInDatabase({
externalId,
surface,
expectedAttachments,
deletedLabel: deletedAttachment.label,
});
};
test.describe('document editor', () => {
test('add, verify and delete attachments', async ({ page }) => {
const surface = await openDocumentEnvelopeEditor(page);
const result = await runAttachmentFlow(surface);
await assertAttachmentsPersistedInDatabase({
surface,
...result,
});
});
});
test.describe('template editor', () => {
test('add, verify and delete attachments', async ({ page }) => {
const surface = await openTemplateEnvelopeEditor(page);
const result = await runAttachmentFlow(surface);
await assertAttachmentsPersistedInDatabase({
surface,
...result,
});
});
});
test.describe('embedded create', () => {
test('add, verify and delete attachments', async ({ page }) => {
const surface = await openEmbeddedEnvelopeEditor(page, {
envelopeType: 'DOCUMENT',
tokenNamePrefix: 'e2e-embed-attachments',
});
await addEnvelopeItemPdf(surface.root, 'embedded-document-attachments.pdf');
const result = await runEmbeddedAttachmentFlow(surface);
await persistEmbeddedEnvelope(surface);
await assertAttachmentsPersistedInDatabase({
surface,
...result,
});
});
});
test.describe('embedded edit', () => {
test('add, verify and delete attachments', async ({ page }) => {
const surface = await openEmbeddedEnvelopeEditor(page, {
envelopeType: 'TEMPLATE',
mode: 'edit',
tokenNamePrefix: 'e2e-embed-attachments',
});
const result = await runEmbeddedAttachmentFlow(surface);
await persistEmbeddedEnvelope(surface);
await assertAttachmentsPersistedInDatabase({
surface,
...result,
});
});
});