diff --git a/packages/app-tests/e2e/document-flow/settings-step.spec.ts b/packages/app-tests/e2e/document-flow/settings-step.spec.ts new file mode 100644 index 000000000..87a053622 --- /dev/null +++ b/packages/app-tests/e2e/document-flow/settings-step.spec.ts @@ -0,0 +1,74 @@ +import { expect, test } from '@playwright/test'; + +import { + seedBlankDocument, + seedDraftDocument, + seedPendingDocument, +} from '@documenso/prisma/seed/documents'; +import { seedUser, unseedUser } from '@documenso/prisma/seed/users'; + +import { apiSignin } from '../fixtures/authentication'; + +test.describe.configure({ mode: 'parallel' }); + +test('[DOCUMENT_FLOW]: add settings', async ({ page }) => { + const user = await seedUser(); + const document = await seedBlankDocument(user); + + await apiSignin({ + page, + email: user.email, + redirectPath: `/documents/${document.id}/edit`, + }); + + // Set title. + await page.getByLabel('Title').fill('New Title'); + + // Set access auth. + await page.getByTestId('documentAccessSelectValue').click(); + await page.getByLabel('Require account').getByText('Require account').click(); + await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account'); + + // Set action auth. + await page.getByTestId('documentActionSelectValue').click(); + await page.getByLabel('Require account').getByText('Require account').click(); + await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require account'); + + // Save the settings by going to the next step. + await page.getByRole('button', { name: 'Continue' }).click(); + await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible(); + + // Return to the settings step to check that the results are saved correctly. + await page.getByRole('button', { name: 'Go Back' }).click(); + await expect(page.getByRole('heading', { name: 'General' })).toBeVisible(); + + // Todo: Verify that the values are correct once we fix the issue where going back + // does not show the updated values. + // await expect(page.getByLabel('Title')).toContainText('New Title'); + // await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account'); + // await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require account'); + + await unseedUser(user.id); +}); + +test('[DOCUMENT_FLOW]: title should be disabled depending on document status', async ({ page }) => { + const user = await seedUser(); + + const pendingDocument = await seedPendingDocument(user, []); + const draftDocument = await seedDraftDocument(user, []); + + await apiSignin({ + page, + email: user.email, + redirectPath: `/documents/${pendingDocument.id}/edit`, + }); + + // Should be disabled for pending documents. + await expect(page.getByLabel('Title')).toBeDisabled(); + + // Should be enabled for draft documents. + await page.goto(`/documents/${draftDocument.id}/edit`); + await expect(page.getByLabel('Title')).toBeEnabled(); + + await unseedUser(user.id); +}); diff --git a/packages/app-tests/e2e/document-flow/signers-step.spec.ts b/packages/app-tests/e2e/document-flow/signers-step.spec.ts new file mode 100644 index 000000000..90c6e1d3d --- /dev/null +++ b/packages/app-tests/e2e/document-flow/signers-step.spec.ts @@ -0,0 +1,63 @@ +import { expect, test } from '@playwright/test'; + +import { seedBlankDocument } from '@documenso/prisma/seed/documents'; +import { seedUser, unseedUser } from '@documenso/prisma/seed/users'; + +import { apiSignin } from '../fixtures/authentication'; + +test.describe.configure({ mode: 'parallel' }); + +// Note: Not complete yet due to issue with back button. +test('[DOCUMENT_FLOW]: add signers', async ({ page }) => { + const user = await seedUser(); + const document = await seedBlankDocument(user); + + await apiSignin({ + page, + email: user.email, + redirectPath: `/documents/${document.id}/edit`, + }); + + // Save the settings by going to the next step. + await page.getByRole('button', { name: 'Continue' }).click(); + await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible(); + + // Add 2 signers. + await page.getByPlaceholder('Email').fill('recipient1@documenso.com'); + await page.getByPlaceholder('Name').fill('Recipient 1'); + await page.getByRole('button', { name: 'Add Signer' }).click(); + await page.getByRole('textbox', { name: 'Email', exact: true }).fill('recipient2@documenso.com'); + await page.getByRole('textbox', { name: 'Name', exact: true }).fill('Recipient 2'); + + // Display advanced settings. + await page.getByLabel('Show advanced settings').click(); + + // Navigate to the next step and back. + await page.getByRole('button', { name: 'Continue' }).click(); + await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible(); + await page.getByRole('button', { name: 'Go Back' }).click(); + await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible(); + + // Todo: Fix stepper component back issue before finishing test. + + // // Expect that the advanced settings is unchecked, since no advanced settings were applied. + // await expect(page.getByLabel('Show advanced settings')).toBeChecked({ checked: false }); + + // // Add advanced settings for a single recipient. + // await page.getByLabel('Show advanced settings').click(); + // await page.getByRole('combobox').first().click(); + // await page.getByLabel('Require account').click(); + + // // Navigate to the next step and back. + // await page.getByRole('button', { name: 'Continue' }).click(); + // await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible(); + // await page.getByRole('button', { name: 'Go Back' }).click(); + // await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible(); + + // Expect that the advanced settings is visible, and the checkbox is hidden. Since advanced + // settings were applied. + + // Todo: Fix stepper component back issue before finishing test. + + await unseedUser(user.id); +}); diff --git a/packages/app-tests/e2e/fixtures/authentication.ts b/packages/app-tests/e2e/fixtures/authentication.ts index d59fccd1c..3c0774da0 100644 --- a/packages/app-tests/e2e/fixtures/authentication.ts +++ b/packages/app-tests/e2e/fixtures/authentication.ts @@ -1,8 +1,8 @@ -import type { Page } from '@playwright/test'; +import { type Page } from '@playwright/test'; import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app'; -type ManualLoginOptions = { +type LoginOptions = { page: Page; email?: string; password?: string; @@ -18,7 +18,7 @@ export const manualLogin = async ({ email = 'example@documenso.com', password = 'password', redirectPath, -}: ManualLoginOptions) => { +}: LoginOptions) => { await page.goto(`${WEBAPP_BASE_URL}/signin`); await page.getByLabel('Email').click(); @@ -33,9 +33,63 @@ export const manualLogin = async ({ } }; -export const manualSignout = async ({ page }: ManualLoginOptions) => { +export const manualSignout = async ({ page }: LoginOptions) => { await page.waitForTimeout(1000); await page.getByTestId('menu-switcher').click(); await page.getByRole('menuitem', { name: 'Sign Out' }).click(); await page.waitForURL(`${WEBAPP_BASE_URL}/signin`); }; + +export const apiSignin = async ({ + page, + email = 'example@documenso.com', + password = 'password', + redirectPath, +}: LoginOptions) => { + const { request } = page.context(); + + const csrfToken = await getCsrfToken(page); + + await request.post(`${WEBAPP_BASE_URL}/api/auth/callback/credentials`, { + form: { + email, + password, + json: true, + csrfToken, + }, + }); + + if (redirectPath) { + await page.goto(`${WEBAPP_BASE_URL}${redirectPath}`); + } +}; + +export const apiSignout = async ({ page }: { page: Page }) => { + const { request } = page.context(); + + const csrfToken = await getCsrfToken(page); + + await request.post(`${WEBAPP_BASE_URL}/api/auth/signout`, { + form: { + csrfToken, + json: true, + }, + }); + + await page.goto(`${WEBAPP_BASE_URL}/signin`); +}; + +const getCsrfToken = async (page: Page) => { + const { request } = page.context(); + + const response = await request.fetch(`${WEBAPP_BASE_URL}/api/auth/csrf`, { + method: 'get', + }); + + const { csrfToken } = await response.json(); + if (!csrfToken) { + throw new Error('Invalid session'); + } + + return csrfToken; +}; diff --git a/packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts b/packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts index 6e03979c0..781b9eb3d 100644 --- a/packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts +++ b/packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts @@ -28,8 +28,8 @@ test(`[PR-718]: should be able to create a document`, async ({ page }) => { // Wait to be redirected to the edit page await page.waitForURL(/\/documents\/\d+/); - // Set title - await expect(page.getByRole('heading', { name: 'Add Title' })).toBeVisible(); + // Set general settings + await expect(page.getByRole('heading', { name: 'General' })).toBeVisible(); await page.getByLabel('Title').fill(documentTitle); diff --git a/packages/app-tests/e2e/teams/manage-team.spec.ts b/packages/app-tests/e2e/teams/manage-team.spec.ts index aed56b2bc..a1deb1995 100644 --- a/packages/app-tests/e2e/teams/manage-team.spec.ts +++ b/packages/app-tests/e2e/teams/manage-team.spec.ts @@ -4,14 +4,14 @@ import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app'; import { seedTeam, unseedTeam } from '@documenso/prisma/seed/teams'; import { seedUser } from '@documenso/prisma/seed/users'; -import { manualLogin } from '../fixtures/authentication'; +import { apiSignin } from '../fixtures/authentication'; test.describe.configure({ mode: 'parallel' }); test('[TEAMS]: create team', async ({ page }) => { const user = await seedUser(); - await manualLogin({ + await apiSignin({ page, email: user.email, redirectPath: '/settings/teams', @@ -38,7 +38,7 @@ test('[TEAMS]: create team', async ({ page }) => { test('[TEAMS]: delete team', async ({ page }) => { const team = await seedTeam(); - await manualLogin({ + await apiSignin({ page, email: team.owner.email, redirectPath: `/t/${team.url}/settings`, @@ -56,7 +56,7 @@ test('[TEAMS]: delete team', async ({ page }) => { test('[TEAMS]: update team', async ({ page }) => { const team = await seedTeam(); - await manualLogin({ + await apiSignin({ page, email: team.owner.email, }); diff --git a/packages/app-tests/e2e/teams/team-documents.spec.ts b/packages/app-tests/e2e/teams/team-documents.spec.ts index 210189ca7..8f70befc8 100644 --- a/packages/app-tests/e2e/teams/team-documents.spec.ts +++ b/packages/app-tests/e2e/teams/team-documents.spec.ts @@ -6,7 +6,7 @@ import { seedDocuments, seedTeamDocuments } from '@documenso/prisma/seed/documen import { seedTeamEmail, unseedTeam, unseedTeamEmail } from '@documenso/prisma/seed/teams'; import { seedUser } from '@documenso/prisma/seed/users'; -import { manualLogin, manualSignout } from '../fixtures/authentication'; +import { apiSignin, apiSignout } from '../fixtures/authentication'; test.describe.configure({ mode: 'parallel' }); @@ -30,7 +30,7 @@ test('[TEAMS]: check team documents count', async ({ page }) => { // Run the test twice, once with the team owner and once with a team member to ensure the counts are the same. for (const user of [team.owner, teamMember2]) { - await manualLogin({ + await apiSignin({ page, email: user.email, redirectPath: `/t/${team.url}/documents`, @@ -55,7 +55,7 @@ test('[TEAMS]: check team documents count', async ({ page }) => { await checkDocumentTabCount(page, 'Draft', 1); await checkDocumentTabCount(page, 'All', 3); - await manualSignout({ page }); + await apiSignout({ page }); } await unseedTeam(team.url); @@ -126,7 +126,7 @@ test('[TEAMS]: check team documents count with internal team email', async ({ pa // Run the test twice, one with the team owner and once with the team member email to ensure the counts are the same. for (const user of [team.owner, teamEmailMember]) { - await manualLogin({ + await apiSignin({ page, email: user.email, redirectPath: `/t/${team.url}/documents`, @@ -151,7 +151,7 @@ test('[TEAMS]: check team documents count with internal team email', async ({ pa await checkDocumentTabCount(page, 'Draft', 1); await checkDocumentTabCount(page, 'All', 3); - await manualSignout({ page }); + await apiSignout({ page }); } await unseedTeamEmail({ teamId: team.id }); @@ -216,7 +216,7 @@ test('[TEAMS]: check team documents count with external team email', async ({ pa }, ]); - await manualLogin({ + await apiSignin({ page, email: teamMember2.email, redirectPath: `/t/${team.url}/documents`, @@ -248,7 +248,7 @@ test('[TEAMS]: check team documents count with external team email', async ({ pa test('[TEAMS]: delete pending team document', async ({ page }) => { const { team, teamMember2: currentUser } = await seedTeamDocuments(); - await manualLogin({ + await apiSignin({ page, email: currentUser.email, redirectPath: `/t/${team.url}/documents?status=PENDING`, @@ -266,7 +266,7 @@ test('[TEAMS]: delete pending team document', async ({ page }) => { test('[TEAMS]: resend pending team document', async ({ page }) => { const { team, teamMember2: currentUser } = await seedTeamDocuments(); - await manualLogin({ + await apiSignin({ page, email: currentUser.email, redirectPath: `/t/${team.url}/documents?status=PENDING`, diff --git a/packages/app-tests/e2e/teams/team-email.spec.ts b/packages/app-tests/e2e/teams/team-email.spec.ts index 953be5aaf..6ae820f59 100644 --- a/packages/app-tests/e2e/teams/team-email.spec.ts +++ b/packages/app-tests/e2e/teams/team-email.spec.ts @@ -4,14 +4,14 @@ import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app'; import { seedTeam, seedTeamEmailVerification, unseedTeam } from '@documenso/prisma/seed/teams'; import { seedUser, unseedUser } from '@documenso/prisma/seed/users'; -import { manualLogin } from '../fixtures/authentication'; +import { apiSignin } from '../fixtures/authentication'; test.describe.configure({ mode: 'parallel' }); test('[TEAMS]: send team email request', async ({ page }) => { const team = await seedTeam(); - await manualLogin({ + await apiSignin({ page, email: team.owner.email, password: 'password', @@ -57,7 +57,7 @@ test('[TEAMS]: delete team email', async ({ page }) => { createTeamEmail: true, }); - await manualLogin({ + await apiSignin({ page, email: team.owner.email, redirectPath: `/t/${team.url}/settings`, @@ -86,7 +86,7 @@ test('[TEAMS]: team email owner removes access', async ({ page }) => { email: team.teamEmail.email, }); - await manualLogin({ + await apiSignin({ page, email: teamEmailOwner.email, redirectPath: `/settings/teams`, diff --git a/packages/app-tests/e2e/teams/team-members.spec.ts b/packages/app-tests/e2e/teams/team-members.spec.ts index 05f096c09..c85717729 100644 --- a/packages/app-tests/e2e/teams/team-members.spec.ts +++ b/packages/app-tests/e2e/teams/team-members.spec.ts @@ -4,7 +4,7 @@ import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app'; import { seedTeam, seedTeamInvite, unseedTeam } from '@documenso/prisma/seed/teams'; import { seedUser } from '@documenso/prisma/seed/users'; -import { manualLogin } from '../fixtures/authentication'; +import { apiSignin } from '../fixtures/authentication'; test.describe.configure({ mode: 'parallel' }); @@ -13,7 +13,7 @@ test('[TEAMS]: update team member role', async ({ page }) => { createTeamMembers: 1, }); - await manualLogin({ + await apiSignin({ page, email: team.owner.email, password: 'password', @@ -75,7 +75,7 @@ test('[TEAMS]: member can leave team', async ({ page }) => { const teamMember = team.members[1]; - await manualLogin({ + await apiSignin({ page, email: teamMember.user.email, password: 'password', @@ -97,7 +97,7 @@ test('[TEAMS]: owner cannot leave team', async ({ page }) => { createTeamMembers: 1, }); - await manualLogin({ + await apiSignin({ page, email: team.owner.email, password: 'password', diff --git a/packages/app-tests/e2e/teams/transfer-team.spec.ts b/packages/app-tests/e2e/teams/transfer-team.spec.ts index a5d95b720..c8460baf8 100644 --- a/packages/app-tests/e2e/teams/transfer-team.spec.ts +++ b/packages/app-tests/e2e/teams/transfer-team.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test'; import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app'; import { seedTeam, seedTeamTransfer, unseedTeam } from '@documenso/prisma/seed/teams'; -import { manualLogin } from '../fixtures/authentication'; +import { apiSignin } from '../fixtures/authentication'; test.describe.configure({ mode: 'parallel' }); @@ -14,7 +14,7 @@ test('[TEAMS]: initiate and cancel team transfer', async ({ page }) => { const teamMember = team.members[1]; - await manualLogin({ + await apiSignin({ page, email: team.owner.email, password: 'password', diff --git a/packages/app-tests/e2e/templates/manage-templates.spec.ts b/packages/app-tests/e2e/templates/manage-templates.spec.ts index f89583097..a89b308eb 100644 --- a/packages/app-tests/e2e/templates/manage-templates.spec.ts +++ b/packages/app-tests/e2e/templates/manage-templates.spec.ts @@ -4,7 +4,7 @@ import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app'; import { seedTeam, unseedTeam } from '@documenso/prisma/seed/teams'; import { seedTemplate } from '@documenso/prisma/seed/templates'; -import { manualLogin } from '../fixtures/authentication'; +import { apiSignin } from '../fixtures/authentication'; test.describe.configure({ mode: 'parallel' }); @@ -36,7 +36,7 @@ test('[TEMPLATES]: view templates', async ({ page }) => { teamId: team.id, }); - await manualLogin({ + await apiSignin({ page, email: owner.email, redirectPath: '/templates', @@ -81,7 +81,7 @@ test('[TEMPLATES]: delete template', async ({ page }) => { teamId: team.id, }); - await manualLogin({ + await apiSignin({ page, email: owner.email, redirectPath: '/templates', @@ -135,7 +135,7 @@ test('[TEMPLATES]: duplicate template', async ({ page }) => { teamId: team.id, }); - await manualLogin({ + await apiSignin({ page, email: owner.email, redirectPath: '/templates', @@ -181,7 +181,7 @@ test('[TEMPLATES]: use template', async ({ page }) => { teamId: team.id, }); - await manualLogin({ + await apiSignin({ page, email: owner.email, redirectPath: '/templates', diff --git a/packages/prisma/seed/documents.ts b/packages/prisma/seed/documents.ts index 1f1f5cab8..2ae5145aa 100644 --- a/packages/prisma/seed/documents.ts +++ b/packages/prisma/seed/documents.ts @@ -33,19 +33,19 @@ export const seedDocuments = async (documents: DocumentToSeed[]) => { documents.map(async (document, i) => match(document.type) .with(DocumentStatus.DRAFT, async () => - createDraftDocument(document.sender, document.recipients, { + seedDraftDocument(document.sender, document.recipients, { key: i, createDocumentOptions: document.documentOptions, }), ) .with(DocumentStatus.PENDING, async () => - createPendingDocument(document.sender, document.recipients, { + seedPendingDocument(document.sender, document.recipients, { key: i, createDocumentOptions: document.documentOptions, }), ) .with(DocumentStatus.COMPLETED, async () => - createCompletedDocument(document.sender, document.recipients, { + seedCompletedDocument(document.sender, document.recipients, { key: i, createDocumentOptions: document.documentOptions, }), @@ -55,7 +55,37 @@ export const seedDocuments = async (documents: DocumentToSeed[]) => { ); }; -const createDraftDocument = async ( +export const seedBlankDocument = async (owner: User, options: CreateDocumentOptions = {}) => { + const { key, createDocumentOptions = {} } = options; + + const documentData = await prisma.documentData.create({ + data: { + type: DocumentDataType.BYTES_64, + data: examplePdf, + initialData: examplePdf, + }, + }); + + return await prisma.document.create({ + data: { + title: `[TEST] Document ${key} - Draft`, + status: DocumentStatus.DRAFT, + documentDataId: documentData.id, + userId: owner.id, + ...createDocumentOptions, + }, + }); +}; + +export const unseedDocument = async (documentId: number) => { + await prisma.document.delete({ + where: { + id: documentId, + }, + }); +}; + +export const seedDraftDocument = async ( sender: User, recipients: (User | string)[], options: CreateDocumentOptions = {}, @@ -114,6 +144,8 @@ const createDraftDocument = async ( }, }); } + + return document; }; type CreateDocumentOptions = { @@ -121,7 +153,7 @@ type CreateDocumentOptions = { createDocumentOptions?: Partial; }; -const createPendingDocument = async ( +export const seedPendingDocument = async ( sender: User, recipients: (User | string)[], options: CreateDocumentOptions = {}, @@ -180,9 +212,11 @@ const createPendingDocument = async ( }, }); } + + return document; }; -const createCompletedDocument = async ( +export const seedCompletedDocument = async ( sender: User, recipients: (User | string)[], options: CreateDocumentOptions = {}, @@ -241,6 +275,8 @@ const createCompletedDocument = async ( }, }); } + + return document; }; /** diff --git a/packages/ui/primitives/document-flow/add-settings.tsx b/packages/ui/primitives/document-flow/add-settings.tsx index 3cecc3c4d..2a66cd056 100644 --- a/packages/ui/primitives/document-flow/add-settings.tsx +++ b/packages/ui/primitives/document-flow/add-settings.tsx @@ -160,7 +160,7 @@ export const AddSettingsFormPartial = ({ - +