mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 17:04:12 +10:00
312 lines
9.9 KiB
TypeScript
312 lines
9.9 KiB
TypeScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { nanoid } from '@documenso/lib/universal/id';
|
|
import { prisma } from '@documenso/prisma';
|
|
import { expect, type Page, test } from '@playwright/test';
|
|
|
|
import {
|
|
addEnvelopeItemPdf,
|
|
clickAddMyselfButton,
|
|
clickEnvelopeEditorStep,
|
|
getEnvelopeEditorSettingsTrigger,
|
|
openDocumentEnvelopeEditor,
|
|
openEmbeddedEnvelopeEditor,
|
|
openTemplateEnvelopeEditor,
|
|
persistEmbeddedEnvelope,
|
|
setRecipientEmail,
|
|
setRecipientName,
|
|
type TEnvelopeEditorSurface,
|
|
} from '../fixtures/envelope-editor';
|
|
import { expectToastTextToBeVisible } from '../fixtures/generic';
|
|
|
|
test.use({
|
|
storageState: {
|
|
cookies: [],
|
|
origins: [],
|
|
},
|
|
});
|
|
|
|
const examplePdfBuffer = fs.readFileSync(path.join(__dirname, '../../../../assets/example.pdf'));
|
|
|
|
const multiPagePdfBuffer = fs.readFileSync(path.join(__dirname, '../../../../assets/field-font-alignment.pdf'));
|
|
|
|
// --- Shared helpers ---
|
|
|
|
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 getEditButton = (root: Page, index: number) =>
|
|
root.locator('[data-testid^="envelope-item-edit-button-"]').nth(index);
|
|
|
|
const getEditDialog = (root: Page) => root.getByRole('dialog');
|
|
|
|
const getEditDialogTitleInput = (root: Page) => root.locator('[data-testid="envelope-item-edit-title-input"]');
|
|
|
|
const getEditDialogDropzone = (root: Page) => root.locator('[data-testid="envelope-item-edit-dropzone"]');
|
|
|
|
const getEditDialogSelectedFile = (root: Page) => root.locator('[data-testid="envelope-item-edit-selected-file"]');
|
|
|
|
const getEditDialogClearFileButton = (root: Page) => root.locator('[data-testid="envelope-item-edit-clear-file"]');
|
|
|
|
const getEditDialogUpdateButton = (root: Page) => root.locator('[data-testid="envelope-item-edit-update-button"]');
|
|
|
|
const assertPdfPageCount = async (root: Page, expectedCount: number) => {
|
|
await expect(root.locator('[data-pdf-content]').first()).toHaveAttribute('data-page-count', String(expectedCount), {
|
|
timeout: 15000,
|
|
});
|
|
};
|
|
|
|
const navigateToFieldsPage = async (surface: TEnvelopeEditorSurface) => {
|
|
// Set up a recipient first so the fields page is functional.
|
|
if (surface.isEmbedded) {
|
|
await setRecipientEmail(surface.root, 0, `test-${nanoid(4)}@example.com`);
|
|
await setRecipientName(surface.root, 0, 'Test User');
|
|
} else {
|
|
await clickAddMyselfButton(surface.root);
|
|
}
|
|
|
|
await clickEnvelopeEditorStep(surface.root, 'addFields');
|
|
|
|
// Wait for the file selector to be visible on the fields page.
|
|
await expect(getEditButton(surface.root, 0)).toBeAttached({ timeout: 10000 });
|
|
};
|
|
|
|
const openEditDialogOnFieldsPage = async (root: Page, index = 0) => {
|
|
// Hover to reveal the edit button, then click it.
|
|
const editButton = getEditButton(root, index);
|
|
await editButton.click({ force: true });
|
|
await expect(getEditDialog(root)).toBeVisible();
|
|
};
|
|
|
|
// --- Flows ---
|
|
|
|
const runRenameFlow = async (surface: TEnvelopeEditorSurface) => {
|
|
const externalId = `e2e-edit-rename-${nanoid()}`;
|
|
|
|
if (surface.isEmbedded && !surface.envelopeId) {
|
|
await addEnvelopeItemPdf(surface.root, 'rename-test.pdf');
|
|
}
|
|
|
|
await updateExternalId(surface, externalId);
|
|
await navigateToFieldsPage(surface);
|
|
|
|
// Open edit dialog and change the title.
|
|
await openEditDialogOnFieldsPage(surface.root);
|
|
|
|
const titleInput = getEditDialogTitleInput(surface.root);
|
|
await expect(titleInput).toBeVisible();
|
|
|
|
await titleInput.clear();
|
|
await titleInput.fill('Renamed Document');
|
|
|
|
// Update button should be disabled without a replacement file.
|
|
await expect(getEditDialogUpdateButton(surface.root)).toBeDisabled();
|
|
|
|
// A replacement file is required for the Update button to be enabled.
|
|
const dropzone = getEditDialogDropzone(surface.root);
|
|
await expect(dropzone).toBeVisible();
|
|
|
|
const fileInput = dropzone.locator('input[type="file"]');
|
|
await fileInput.setInputFiles({
|
|
name: 'example.pdf',
|
|
mimeType: 'application/pdf',
|
|
buffer: examplePdfBuffer,
|
|
});
|
|
|
|
await expect(getEditDialogSelectedFile(surface.root)).toBeVisible();
|
|
|
|
await getEditDialogUpdateButton(surface.root).click();
|
|
|
|
// Dialog should close.
|
|
await expect(getEditDialog(surface.root)).not.toBeVisible({ timeout: 10000 });
|
|
|
|
return { externalId };
|
|
};
|
|
|
|
const runReplacePdfFlow = async (surface: TEnvelopeEditorSurface) => {
|
|
const externalId = `e2e-edit-replace-${nanoid()}`;
|
|
|
|
if (surface.isEmbedded && !surface.envelopeId) {
|
|
await addEnvelopeItemPdf(surface.root, 'replace-test.pdf');
|
|
}
|
|
|
|
await updateExternalId(surface, externalId);
|
|
await navigateToFieldsPage(surface);
|
|
|
|
// First, assert the page count is 1 (example.pdf has 1 page).
|
|
await assertPdfPageCount(surface.root, 1);
|
|
|
|
// Open edit dialog.
|
|
await openEditDialogOnFieldsPage(surface.root);
|
|
|
|
// Select a multi-page PDF via the dropzone.
|
|
const dropzone = getEditDialogDropzone(surface.root);
|
|
await expect(dropzone).toBeVisible();
|
|
|
|
const fileInput = dropzone.locator('input[type="file"]');
|
|
await fileInput.setInputFiles({
|
|
name: 'field-font-alignment.pdf',
|
|
mimeType: 'application/pdf',
|
|
buffer: multiPagePdfBuffer,
|
|
});
|
|
|
|
// Verify file is shown in the selected file display.
|
|
await expect(getEditDialogSelectedFile(surface.root)).toBeVisible();
|
|
|
|
// Click Update.
|
|
await getEditDialogUpdateButton(surface.root).click();
|
|
|
|
// Dialog should close.
|
|
await expect(getEditDialog(surface.root)).not.toBeVisible({ timeout: 15000 });
|
|
|
|
// After replacement, the page count should be 3 (field-font-alignment.pdf has 3 pages).
|
|
await assertPdfPageCount(surface.root, 3);
|
|
|
|
return { externalId };
|
|
};
|
|
|
|
const runClearFileSelectionFlow = async (surface: TEnvelopeEditorSurface) => {
|
|
if (surface.isEmbedded && !surface.envelopeId) {
|
|
await addEnvelopeItemPdf(surface.root, 'clear-file-test.pdf');
|
|
}
|
|
|
|
await navigateToFieldsPage(surface);
|
|
|
|
// Open edit dialog.
|
|
await openEditDialogOnFieldsPage(surface.root);
|
|
|
|
// Select a file.
|
|
const dropzone = getEditDialogDropzone(surface.root);
|
|
await expect(dropzone).toBeVisible();
|
|
|
|
const fileInput = dropzone.locator('input[type="file"]');
|
|
await fileInput.setInputFiles({
|
|
name: 'to-be-cleared.pdf',
|
|
mimeType: 'application/pdf',
|
|
buffer: examplePdfBuffer,
|
|
});
|
|
|
|
// Verify file appears.
|
|
await expect(getEditDialogSelectedFile(surface.root)).toBeVisible();
|
|
await expect(getEditDialogDropzone(surface.root)).not.toBeVisible();
|
|
|
|
// Click X to clear.
|
|
await getEditDialogClearFileButton(surface.root).click();
|
|
|
|
// Dropzone should reappear.
|
|
await expect(getEditDialogDropzone(surface.root)).toBeVisible();
|
|
await expect(getEditDialogSelectedFile(surface.root)).not.toBeVisible();
|
|
};
|
|
|
|
// --- DB assertion ---
|
|
|
|
const assertRenamePersistedInDatabase = async ({
|
|
surface,
|
|
externalId,
|
|
}: {
|
|
surface: TEnvelopeEditorSurface;
|
|
externalId: string;
|
|
}) => {
|
|
const envelope = await prisma.envelope.findFirstOrThrow({
|
|
where: {
|
|
externalId,
|
|
userId: surface.userId,
|
|
teamId: surface.teamId,
|
|
type: surface.envelopeType,
|
|
},
|
|
include: {
|
|
envelopeItems: {
|
|
orderBy: { order: 'asc' },
|
|
},
|
|
},
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
|
|
expect(envelope.envelopeItems.length).toBeGreaterThanOrEqual(1);
|
|
expect(envelope.envelopeItems[0].title).toBe('Renamed Document');
|
|
};
|
|
|
|
// --- Tests ---
|
|
|
|
test.describe('document editor', () => {
|
|
test('should rename an envelope item from the fields page', async ({ page }) => {
|
|
const surface = await openDocumentEnvelopeEditor(page);
|
|
const result = await runRenameFlow(surface);
|
|
|
|
await assertRenamePersistedInDatabase({ surface, ...result });
|
|
});
|
|
|
|
test('should replace a PDF from the fields page', async ({ page }) => {
|
|
const surface = await openDocumentEnvelopeEditor(page);
|
|
await runReplacePdfFlow(surface);
|
|
});
|
|
|
|
test('should clear a selected file before submitting', async ({ page }) => {
|
|
const surface = await openDocumentEnvelopeEditor(page);
|
|
await runClearFileSelectionFlow(surface);
|
|
});
|
|
});
|
|
|
|
test.describe('template editor', () => {
|
|
test('should rename an envelope item from the fields page', async ({ page }) => {
|
|
const surface = await openTemplateEnvelopeEditor(page);
|
|
const result = await runRenameFlow(surface);
|
|
|
|
await assertRenamePersistedInDatabase({ surface, ...result });
|
|
});
|
|
});
|
|
|
|
test.describe('embedded create', () => {
|
|
test('should rename an envelope item from the fields page', async ({ page }) => {
|
|
const surface = await openEmbeddedEnvelopeEditor(page, {
|
|
envelopeType: 'DOCUMENT',
|
|
tokenNamePrefix: 'e2e-edit-dialog-create',
|
|
});
|
|
|
|
const result = await runRenameFlow(surface);
|
|
|
|
await clickEnvelopeEditorStep(surface.root, 'upload');
|
|
await persistEmbeddedEnvelope(surface);
|
|
|
|
await assertRenamePersistedInDatabase({ surface, ...result });
|
|
});
|
|
|
|
test('should replace a PDF from the fields page', async ({ page }) => {
|
|
const surface = await openEmbeddedEnvelopeEditor(page, {
|
|
envelopeType: 'DOCUMENT',
|
|
tokenNamePrefix: 'e2e-edit-dialog-replace',
|
|
});
|
|
|
|
await runReplacePdfFlow(surface);
|
|
});
|
|
});
|
|
|
|
test.describe('embedded edit', () => {
|
|
test('should rename an envelope item from the fields page', async ({ page }) => {
|
|
const surface = await openEmbeddedEnvelopeEditor(page, {
|
|
envelopeType: 'TEMPLATE',
|
|
mode: 'edit',
|
|
tokenNamePrefix: 'e2e-edit-dialog-edit',
|
|
});
|
|
|
|
const result = await runRenameFlow(surface);
|
|
|
|
await clickEnvelopeEditorStep(surface.root, 'upload');
|
|
await persistEmbeddedEnvelope(surface);
|
|
|
|
await assertRenamePersistedInDatabase({ surface, ...result });
|
|
});
|
|
});
|