From 89e7c305e79c159ab109ea2424cceffd2803fcfa Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 5 Apr 2024 16:50:47 -0300 Subject: [PATCH] chore: emails e2e suite (#5698) fix: move 'email' configuration to server only --- .github/workflows/main.yml | 1 + packages/payload/src/config/client.ts | 2 ++ test/email/.eslintrc.cjs | 8 +++++ test/email/.gitignore | 2 ++ test/email/collections/Media/index.ts | 35 +++++++++++++++++++ test/email/collections/Posts/index.ts | 34 ++++++++++++++++++ test/email/config.ts | 49 ++++++++++++++++++++++++++ test/email/e2e.spec.ts | 45 ++++++++++++++++++++++++ test/email/globals/Menu/index.ts | 13 +++++++ test/email/payload-types.ts | 50 +++++++++++++++++++++++++++ test/email/tsconfig.eslint.json | 13 +++++++ test/helpers/sdk/index.ts | 9 +++++ test/helpers/sdk/types.ts | 2 +- 13 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 test/email/.eslintrc.cjs create mode 100644 test/email/.gitignore create mode 100644 test/email/collections/Media/index.ts create mode 100644 test/email/collections/Posts/index.ts create mode 100644 test/email/config.ts create mode 100644 test/email/e2e.spec.ts create mode 100644 test/email/globals/Menu/index.ts create mode 100644 test/email/payload-types.ts create mode 100644 test/email/tsconfig.eslint.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cc8af141cd..4dbafcd988 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -248,6 +248,7 @@ jobs: - access-control # - admin - auth + - email - field-error-states - fields-relationship # - fields diff --git a/packages/payload/src/config/client.ts b/packages/payload/src/config/client.ts index a596724efa..dad2ffd5e2 100644 --- a/packages/payload/src/config/client.ts +++ b/packages/payload/src/config/client.ts @@ -18,6 +18,7 @@ export type ServerOnlyRootProperties = keyof Pick< | 'csrf' | 'db' | 'editor' + | 'email' | 'endpoints' | 'hooks' | 'onInit' @@ -59,6 +60,7 @@ export const createClientConfig = async ( 'typescript', 'cors', 'csrf', + 'email', // `admin`, `onInit`, `localization`, `collections`, and `globals` are all handled separately ] diff --git a/test/email/.eslintrc.cjs b/test/email/.eslintrc.cjs new file mode 100644 index 0000000000..39a96642f1 --- /dev/null +++ b/test/email/.eslintrc.cjs @@ -0,0 +1,8 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + ignorePatterns: ['payload-types.ts'], + parserOptions: { + project: ['./tsconfig.eslint.json'], + tsconfigRootDir: __dirname, + }, +} diff --git a/test/email/.gitignore b/test/email/.gitignore new file mode 100644 index 0000000000..cce01755f4 --- /dev/null +++ b/test/email/.gitignore @@ -0,0 +1,2 @@ +/media +/media-gif diff --git a/test/email/collections/Media/index.ts b/test/email/collections/Media/index.ts new file mode 100644 index 0000000000..3c523e01ef --- /dev/null +++ b/test/email/collections/Media/index.ts @@ -0,0 +1,35 @@ +import type { CollectionConfig } from 'payload/types' + +import { getPayload } from 'payload' + +export const mediaSlug = 'media' + +export const MediaCollection: CollectionConfig = { + slug: mediaSlug, + access: { + create: () => true, + read: () => true, + }, + fields: [], + upload: { + crop: true, + focalPoint: true, + imageSizes: [ + { + name: 'thumbnail', + height: 200, + width: 200, + }, + { + name: 'medium', + height: 800, + width: 800, + }, + { + name: 'large', + height: 1200, + width: 1200, + }, + ], + }, +} diff --git a/test/email/collections/Posts/index.ts b/test/email/collections/Posts/index.ts new file mode 100644 index 0000000000..1284ab8a27 --- /dev/null +++ b/test/email/collections/Posts/index.ts @@ -0,0 +1,34 @@ +import type { CollectionConfig } from 'payload/types' + +import { mediaSlug } from '../Media/index.js' + +export const postsSlug = 'posts' + +export const PostsCollection: CollectionConfig = { + slug: postsSlug, + admin: { + useAsTitle: 'text', + }, + fields: [ + { + name: 'text', + type: 'text', + }, + { + type: 'row', + fields: [], + }, + { + name: 'associatedMedia', + type: 'upload', + access: { + create: () => true, + update: () => false, + }, + relationTo: mediaSlug, + }, + ], + versions: { + drafts: true, + }, +} diff --git a/test/email/config.ts b/test/email/config.ts new file mode 100644 index 0000000000..e767d737c9 --- /dev/null +++ b/test/email/config.ts @@ -0,0 +1,49 @@ +import path from 'path' +import { getFileByPath } from 'payload/uploads' +import { fileURLToPath } from 'url' + +import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js' +import { devUser } from '../credentials.js' +import { MediaCollection } from './collections/Media/index.js' +import { PostsCollection, postsSlug } from './collections/Posts/index.js' +import { MenuGlobal } from './globals/Menu/index.js' + +const filename = fileURLToPath(import.meta.url) +const dirname = path.dirname(filename) + +export default buildConfigWithDefaults({ + // ...extend config here + collections: [PostsCollection, MediaCollection], + globals: [MenuGlobal], + onInit: async (payload) => { + await payload.create({ + collection: 'users', + data: { + email: devUser.email, + password: devUser.password, + }, + }) + + await payload.create({ + collection: postsSlug, + data: { + text: 'example post', + }, + }) + + const email = await payload.sendEmail({ + to: 'test@example.com', + subject: 'This was sent on init', + }) + + // Create image + const imageFilePath = path.resolve(dirname, '../uploads/image.png') + const imageFile = await getFileByPath(imageFilePath) + + await payload.create({ + collection: 'media', + data: {}, + file: imageFile, + }) + }, +}) diff --git a/test/email/e2e.spec.ts b/test/email/e2e.spec.ts new file mode 100644 index 0000000000..18a8952628 --- /dev/null +++ b/test/email/e2e.spec.ts @@ -0,0 +1,45 @@ +import type { Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' +import * as path from 'path' +import { fileURLToPath } from 'url' + +import type { PayloadTestSDK } from '../helpers/sdk/index.js' +import type { Config } from './payload-types.js' + +import { ensureAutoLoginAndCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js' +import { AdminUrlUtil } from '../helpers/adminUrlUtil.js' +import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js' +import { POLL_TOPASS_TIMEOUT } from '../playwright.config.js' + +const filename = fileURLToPath(import.meta.url) +const dirname = path.dirname(filename) + +test.describe('Emails', () => { + let page: Page + let url: AdminUrlUtil + let payload: PayloadTestSDK + + test.beforeAll(async ({ browser }) => { + const { payload: payloadFromConfig, serverURL } = await initPayloadE2ENoConfig({ dirname }) + url = new AdminUrlUtil(serverURL, 'posts') + + payload = payloadFromConfig + + const context = await browser.newContext() + page = await context.newPage() + initPageConsoleErrorCatch(page) + await ensureAutoLoginAndCompilationIsDone({ page, serverURL }) + }) + + test('can send email via API', async () => { + const email = (await payload.sendEmail({ + to: 'test@example.com', + subject: 'hello', + })) as { response: string } + + await expect(() => expect(email?.response).toContain('Accepted')).toPass({ + timeout: POLL_TOPASS_TIMEOUT, + }) + }) +}) diff --git a/test/email/globals/Menu/index.ts b/test/email/globals/Menu/index.ts new file mode 100644 index 0000000000..cd9ec96dea --- /dev/null +++ b/test/email/globals/Menu/index.ts @@ -0,0 +1,13 @@ +import type { GlobalConfig } from 'payload/types' + +export const menuSlug = 'menu' + +export const MenuGlobal: GlobalConfig = { + slug: menuSlug, + fields: [ + { + name: 'globalText', + type: 'text', + }, + ], +} diff --git a/test/email/payload-types.ts b/test/email/payload-types.ts new file mode 100644 index 0000000000..7cf43254ea --- /dev/null +++ b/test/email/payload-types.ts @@ -0,0 +1,50 @@ +/* tslint:disable */ +/** + * This file was automatically generated by Payload. + * DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config, + * and re-run `payload generate:types` to regenerate this file. + */ + +export interface Config { + collections: { + posts: Post + media: Media + users: User + } + globals: { + menu: Menu + } +} +export interface Post { + id: string + text?: string + associatedMedia?: string | Media + updatedAt: string + createdAt: string +} +export interface Media { + id: string + updatedAt: string + createdAt: string + url?: string + filename?: string + mimeType?: string + filesize?: number + width?: number + height?: number +} +export interface User { + id: string + updatedAt: string + createdAt: string + email?: string + resetPasswordToken?: string + resetPasswordExpiration?: string + loginAttempts?: number + lockUntil?: string + password?: string +} +export interface Menu { + id: string + globalText?: string +} diff --git a/test/email/tsconfig.eslint.json b/test/email/tsconfig.eslint.json new file mode 100644 index 0000000000..b34cc7afbb --- /dev/null +++ b/test/email/tsconfig.eslint.json @@ -0,0 +1,13 @@ +{ + // extend your base config to share compilerOptions, etc + //"extends": "./tsconfig.json", + "compilerOptions": { + // ensure that nobody can accidentally use this config for a build + "noEmit": true + }, + "include": [ + // whatever paths you intend to lint + "./**/*.ts", + "./**/*.tsx" + ] +} diff --git a/test/helpers/sdk/index.ts b/test/helpers/sdk/index.ts index 6b8a7b2e70..041481e3e9 100644 --- a/test/helpers/sdk/index.ts +++ b/test/helpers/sdk/index.ts @@ -1,3 +1,4 @@ +import type { SendMailOptions } from 'nodemailer' import type { PaginatedDocs } from 'payload/database' import type { @@ -68,6 +69,14 @@ export class PayloadTestSDK => { + return this.fetch({ + method: 'sendEmail', + args, + jwt, + }) + } + serverURL: string update = async ({ diff --git a/test/helpers/sdk/types.ts b/test/helpers/sdk/types.ts index 6f53b9c3bb..f7317b5f30 100644 --- a/test/helpers/sdk/types.ts +++ b/test/helpers/sdk/types.ts @@ -25,7 +25,7 @@ export type GeneratedTypes = { export type FetchOptions = { args?: Record jwt?: string - method: 'create' | 'delete' | 'find' | 'update' + method: 'create' | 'delete' | 'find' | 'sendEmail' | 'update' reduceJSON?: (json: any) => R }