chore(plugin-seo): scaffolds tests (#4531)

This commit is contained in:
Jacob Fletcher
2023-12-17 23:49:02 -05:00
committed by GitHub
parent 00daf728f4
commit dd32f5e450
36 changed files with 543 additions and 13388 deletions

View File

@@ -23,6 +23,11 @@
"import": "./src/exports/*.ts", "import": "./src/exports/*.ts",
"require": "./src/exports/*.ts", "require": "./src/exports/*.ts",
"types": "./src/exports/*.ts" "types": "./src/exports/*.ts"
},
"./dist/*": {
"import": "./src/*.ts",
"require": "./src/*.ts",
"types": "./src/*.ts"
} }
}, },
"scripts": { "scripts": {

View File

@@ -40,6 +40,10 @@ export { default as Select } from '../../admin/components/forms/field-types/Sele
export { default as SelectInput } from '../../admin/components/forms/field-types/Select/Input' export { default as SelectInput } from '../../admin/components/forms/field-types/Select/Input'
export { default as Text } from '../../admin/components/forms/field-types/Text' export { default as Text } from '../../admin/components/forms/field-types/Text'
export { default as TextInput } from '../../admin/components/forms/field-types/Text/Input' export { default as TextInput } from '../../admin/components/forms/field-types/Text/Input'
export { default as Textarea } from '../../admin/components/forms/field-types/Textarea'
export { default as TextareaInput } from '../../admin/components/forms/field-types/Textarea/Input'
export { default as Upload } from '../../admin/components/forms/field-types/Upload'
export { default as UploadInput } from '../../admin/components/forms/field-types/Upload/Input'
/** /**
* @deprecated This method is now called useField. The useFieldType alias will be removed in an upcoming version. * @deprecated This method is now called useField. The useFieldType alias will be removed in an upcoming version.

View File

@@ -1,2 +0,0 @@
MONGODB_URI=mongodb://localhost/payload-plugin-seo
PAYLOAD_SECRET=kjnsaldkjfnasdljkfghbnseanljnuadlrigjandrg

View File

@@ -1,4 +0,0 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts"
}

View File

@@ -1,27 +0,0 @@
{
"name": "payload-starter-typescript",
"description": "Blank template - no collections",
"version": "1.0.0",
"main": "dist/server.js",
"license": "MIT",
"scripts": {
"dev": "cross-env PAYLOAD_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
"build:server": "tsc",
"build": "yarn build:payload && yarn build:server",
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types"
},
"dependencies": {
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "^1.8.2"
},
"devDependencies": {
"@types/express": "^4.17.9",
"cross-env": "^7.0.3",
"nodemon": "^2.0.6",
"ts-node": "^9.1.1",
"typescript": "^4.1.3"
}
}

View File

@@ -1,25 +0,0 @@
import path from 'path'
import type { CollectionConfig } from 'payload/types'
const Media: CollectionConfig = {
slug: 'media',
access: {
read: (): boolean => true,
},
admin: {
useAsTitle: 'filename',
},
upload: {
staticDir: path.resolve(__dirname, '../../uploads'),
},
fields: [
{
name: 'alt',
label: 'Alt Text',
type: 'text',
required: true,
},
],
}
export default Media

View File

@@ -1,41 +0,0 @@
import type { CollectionConfig } from 'payload/types'
const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
},
versions: true,
fields: [
{
type: 'tabs',
tabs: [
{
label: 'Content',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'excerpt',
type: 'text',
},
],
},
],
},
{
name: 'slug',
label: 'Slug',
type: 'text',
required: true,
admin: {
position: 'sidebar',
},
},
],
}
export default Posts

View File

@@ -1,18 +0,0 @@
import type { GlobalConfig } from 'payload/types'
const Settings: GlobalConfig = {
slug: 'settings',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'excerpt',
type: 'text',
},
],
}
export default Settings

View File

@@ -1,62 +0,0 @@
import path from 'path'
import { buildConfig } from 'payload/config'
// import seo from '../../dist';
import seo from '../../src'
import Media from './collections/Media'
import Pages from './collections/Pages'
import Posts from './collections/Posts'
import Users from './collections/Users'
import HomePage from './globals/Settings'
export default buildConfig({
serverURL: 'http://localhost:3000',
admin: {
user: Users.slug,
webpack: config => {
const newConfig = {
...config,
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
react: path.join(__dirname, '../node_modules/react'),
'react-dom': path.join(__dirname, '../node_modules/react-dom'),
payload: path.join(__dirname, '../node_modules/payload'),
},
},
}
return newConfig
},
},
collections: [Users, Pages, Posts, Media],
globals: [HomePage],
localization: {
locales: ['en', 'es', 'de'],
defaultLocale: 'en',
fallback: true,
},
plugins: [
seo({
collections: ['pages', 'posts'],
globals: ['settings'],
tabbedUI: true,
uploadsCollection: 'media',
fields: [
{
name: 'ogTitle',
type: 'text',
label: 'og:title',
},
],
generateTitle: (data: any) => `Website.com — ${data?.doc?.title?.value}`,
generateDescription: ({ doc }: any) => doc?.excerpt?.value,
generateURL: ({ doc, locale }: any) =>
`https://yoursite.com/${locale ? locale + '/' : ''}${doc?.slug?.value || ''}`,
}),
],
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},
})

View File

@@ -1,46 +0,0 @@
import path from 'path'
import type { Payload } from 'payload'
export const seed = async (payload: Payload) => {
payload.logger.info('Seeding data...')
await payload.create({
collection: 'users',
data: {
email: 'dev@payloadcms.com',
password: 'test',
},
})
const { id: mountainPhotoID } = await payload.create({
collection: 'media',
filePath: path.resolve(__dirname, 'mountain-range.jpg'),
data: {
alt: 'Mountains',
},
})
await payload.create({
collection: 'pages',
data: {
title: 'Home Page',
slug: 'home',
excerpt: 'This is the home page',
meta: {
image: mountainPhotoID,
},
},
})
await payload.create({
collection: 'posts',
data: {
title: 'Hello, world!',
slug: 'hello-world',
excerpt: 'This is a post',
meta: {
image: mountainPhotoID,
},
},
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -1,33 +0,0 @@
import dotenv from 'dotenv'
import express from 'express'
import payload from 'payload'
import { seed } from './seed'
dotenv.config()
const app = express()
// Redirect root to Admin panel
app.get('/', (_, res) => {
res.redirect('/admin')
})
// Initialize Payload
const start = async (): Promise<void> => {
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
},
})
if (process.env.PAYLOAD_SEED === 'true') {
await seed(payload)
}
app.listen(3000)
}
start()

View File

@@ -1,19 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "../",
"jsx": "react",
},
"ts-node": {
"transpileOnly": true
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -30,28 +30,23 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}, },
"devDependencies": { "devDependencies": {
"@payloadcms/eslint-config": "^0.0.1", "@payloadcms/eslint-config": "workspace:*",
"@types/express": "^4.17.9", "@types/express": "^4.17.9",
"@types/node": "18.11.3",
"@types/react": "18.0.21", "@types/react": "18.0.21",
"@typescript-eslint/eslint-plugin": "^5.51.0", "payload": "workspace:*",
"@typescript-eslint/parser": "^5.51.0", "react": "^18.0.0"
"copyfiles": "^2.4.1", },
"cross-env": "^7.0.3", "exports": {
"eslint": "^8.19.0", ".": {
"eslint-config-prettier": "^8.5.0", "default": "./src/index.ts",
"eslint-plugin-filenames": "^1.3.2", "types": "./src/index.ts"
"eslint-plugin-import": "2.25.4", }
"eslint-plugin-prettier": "^4.0.0", },
"eslint-plugin-react-hooks": "^4.6.0", "publishConfig": {
"eslint-plugin-simple-import-sort": "^10.0.0", "exports": null,
"nodemon": "^2.0.6", "main": "./dist/index.js",
"payload": "^1.8.2", "registry": "https://registry.npmjs.org/",
"prettier": "^2.7.1", "types": "./dist/index.d.ts"
"react": "^18.0.0",
"rimraf": "^5.0.5",
"ts-node": "^9.1.1",
"typescript": "^4.8.4"
}, },
"files": [ "files": [
"dist", "dist",

View File

@@ -1,11 +1,13 @@
'use client' 'use client'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { useAllFormFields, useField } from 'payload/components/forms' // TODO: fix this import to work in dev mode within the monorepo in a way that is backwards compatible with 1.x
// import TextareaInput from 'payload/dist/admin/components/forms/field-types/Textarea/Input'
import { TextareaInput, useAllFormFields, useField } from 'payload/components/forms'
import { useDocumentInfo, useLocale } from 'payload/components/utilities' import { useDocumentInfo, useLocale } from 'payload/components/utilities'
import TextareaInput from 'payload/dist/admin/components/forms/field-types/Textarea/Input' import type { TextareaField } from 'payload/types'
import { FieldType, Options } from 'payload/dist/admin/components/forms/useField/types'
import { TextareaField } from 'payload/dist/fields/config/types' import type { FieldType, Options } from 'payload/dist/admin/components/forms/useField/types'
import { defaults } from '../defaults' import { defaults } from '../defaults'
import { PluginConfig } from '../types' import { PluginConfig } from '../types'
@@ -45,7 +47,7 @@ export const MetaDescription: React.FC<
generatedDescription = await generateDescription({ generatedDescription = await generateDescription({
...docInfo, ...docInfo,
doc: { ...fields }, doc: { ...fields },
locale, locale: typeof locale === 'object' ? locale?.code : locale,
}) })
} }

View File

@@ -1,16 +1,18 @@
'use client' 'use client'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { useAllFormFields, useField } from 'payload/components/forms' // TODO: fix this import to work in dev mode within the monorepo in a way that is backwards compatible with 1.x
// import UploadInput from 'payload/dist/admin/components/forms/field-types/Upload/Input'
import { UploadInput, useAllFormFields, useField } from 'payload/components/forms'
import { useConfig, useDocumentInfo, useLocale } from 'payload/components/utilities' import { useConfig, useDocumentInfo, useLocale } from 'payload/components/utilities'
import UploadInput from 'payload/dist/admin/components/forms/field-types/Upload/Input'
import { Props as UploadFieldType } from 'payload/dist/admin/components/forms/field-types/Upload/types'
import { FieldType, Options } from 'payload/dist/admin/components/forms/useField/types' import { FieldType, Options } from 'payload/dist/admin/components/forms/useField/types'
import type { UploadInputProps } from 'payload/src/admin/components/forms/field-types/Upload/Input'
import { PluginConfig } from '../types' import { PluginConfig } from '../types'
import { Pill } from '../ui/Pill' import { Pill } from '../ui/Pill'
type UploadFieldWithProps = UploadFieldType & { type UploadFieldWithProps = UploadInputProps & {
path: string path: string
pluginConfig: PluginConfig pluginConfig: PluginConfig
} }
@@ -35,7 +37,7 @@ export const MetaImage: React.FC<UploadFieldWithProps | {}> = props => {
generatedImage = await generateImage({ generatedImage = await generateImage({
...docInfo, ...docInfo,
doc: { ...fields }, doc: { ...fields },
locale, locale: typeof locale === 'object' ? locale?.code : locale,
}) })
} }

View File

@@ -1,10 +1,12 @@
'use client' 'use client'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { useAllFormFields, useField } from 'payload/components/forms' // TODO: fix this import to work in dev mode within the monorepo in a way that is backwards compatible with 1.x
// import TextInput from 'payload/dist/admin/components/forms/field-types/Text/Input'
import { TextInput, useAllFormFields, useField } from 'payload/components/forms'
import { useDocumentInfo, useLocale } from 'payload/components/utilities' import { useDocumentInfo, useLocale } from 'payload/components/utilities'
import TextInputField from 'payload/dist/admin/components/forms/field-types/Text/Input' import { TextField as TextFieldType } from 'payload/types'
import { Props as TextFieldType } from 'payload/dist/admin/components/forms/field-types/Text/types'
import { FieldType as FieldType, Options } from 'payload/dist/admin/components/forms/useField/types' import { FieldType as FieldType, Options } from 'payload/dist/admin/components/forms/useField/types'
import { defaults } from '../defaults' import { defaults } from '../defaults'
@@ -41,7 +43,7 @@ export const MetaTitle: React.FC<TextFieldWithProps | {}> = props => {
generatedTitle = await generateTitle({ generatedTitle = await generateTitle({
...docInfo, ...docInfo,
doc: { ...fields }, doc: { ...fields },
locale, locale: typeof locale === 'object' ? locale?.code : locale,
}) })
} }
@@ -105,7 +107,7 @@ export const MetaTitle: React.FC<TextFieldWithProps | {}> = props => {
marginBottom: '10px', marginBottom: '10px',
}} }}
> >
<TextInputField <TextInput
path={name} path={name}
name={name} name={name}
onChange={setValue} onChange={setValue}

View File

@@ -2,7 +2,7 @@
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { useAllFormFields, useForm } from 'payload/components/forms' import { useAllFormFields, useForm } from 'payload/components/forms'
import { Field } from 'payload/dist/admin/components/forms/Form/types' import type { Field } from 'payload/types'
import { defaults } from '../defaults' import { defaults } from '../defaults'

View File

@@ -3,7 +3,7 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useAllFormFields } from 'payload/components/forms' import { useAllFormFields } from 'payload/components/forms'
import { useDocumentInfo, useLocale } from 'payload/components/utilities' import { useDocumentInfo, useLocale } from 'payload/components/utilities'
import { Field } from 'payload/dist/admin/components/forms/Form/types' import type { Field } from 'payload/types'
import { PluginConfig } from '../types' import { PluginConfig } from '../types'

View File

@@ -12,7 +12,6 @@
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
}, },
"include": [ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
"src/**/*" "references": [{ "path": "../payload" }]
],
} }

File diff suppressed because it is too large Load Diff

139
pnpm-lock.yaml generated
View File

@@ -1038,6 +1038,24 @@ importers:
specifier: ^5.78.0 specifier: ^5.78.0
version: 5.88.2(@swc/core@1.3.78)(webpack-cli@4.10.0) version: 5.88.2(@swc/core@1.3.78)(webpack-cli@4.10.0)
packages/plugin-seo:
devDependencies:
'@payloadcms/eslint-config':
specifier: workspace:*
version: link:../eslint-config-payload
'@types/express':
specifier: ^4.17.9
version: 4.17.17
'@types/react':
specifier: 18.0.21
version: 18.0.21
payload:
specifier: workspace:*
version: link:../payload
react:
specifier: ^18.0.0
version: 18.2.0
packages/richtext-lexical: packages/richtext-lexical:
dependencies: dependencies:
'@faceless-ui/modal': '@faceless-ui/modal':
@@ -3337,7 +3355,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.6.2 '@types/node': 16.18.58
chalk: 4.1.2 chalk: 4.1.2
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@@ -3357,14 +3375,14 @@ packages:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.6.2 '@types/node': 16.18.58
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
chalk: 4.1.2 chalk: 4.1.2
ci-info: 3.8.0 ci-info: 3.8.0
exit: 0.1.2 exit: 0.1.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jest-changed-files: 29.7.0 jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@20.6.2)(ts-node@10.9.1) jest-config: 29.7.0(@types/node@16.18.58)(ts-node@10.9.1)
jest-haste-map: 29.7.0 jest-haste-map: 29.7.0
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-regex-util: 29.6.3 jest-regex-util: 29.6.3
@@ -3398,7 +3416,7 @@ packages:
dependencies: dependencies:
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.6.2 '@types/node': 16.18.58
jest-mock: 29.7.0 jest-mock: 29.7.0
/@jest/expect-utils@29.7.0: /@jest/expect-utils@29.7.0:
@@ -3422,7 +3440,7 @@ packages:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0 '@sinonjs/fake-timers': 10.3.0
'@types/node': 20.6.2 '@types/node': 16.18.58
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@@ -3453,7 +3471,7 @@ packages:
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.19 '@jridgewell/trace-mapping': 0.3.19
'@types/node': 20.6.2 '@types/node': 16.18.58
chalk: 4.1.2 chalk: 4.1.2
collect-v8-coverage: 1.0.2 collect-v8-coverage: 1.0.2
exit: 0.1.2 exit: 0.1.2
@@ -3534,7 +3552,7 @@ packages:
dependencies: dependencies:
'@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-lib-coverage': 2.0.4
'@types/istanbul-reports': 3.0.1 '@types/istanbul-reports': 3.0.1
'@types/node': 20.6.2 '@types/node': 16.18.58
'@types/yargs': 16.0.5 '@types/yargs': 16.0.5
chalk: 4.1.2 chalk: 4.1.2
dev: true dev: true
@@ -5309,7 +5327,7 @@ packages:
/@types/busboy@1.5.1: /@types/busboy@1.5.1:
resolution: {integrity: sha512-JAymE2skNionWnBUwby3MatzPUw4D/6/7FX1qxBXLzmRnFxmqU0luIof7om0I8R3B/rSr9FKUnFCqxZ/NeGbrw==} resolution: {integrity: sha512-JAymE2skNionWnBUwby3MatzPUw4D/6/7FX1qxBXLzmRnFxmqU0luIof7om0I8R3B/rSr9FKUnFCqxZ/NeGbrw==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: true dev: true
/@types/cacheable-request@6.0.3: /@types/cacheable-request@6.0.3:
@@ -5317,14 +5335,14 @@ packages:
dependencies: dependencies:
'@types/http-cache-semantics': 4.0.2 '@types/http-cache-semantics': 4.0.2
'@types/keyv': 3.1.4 '@types/keyv': 3.1.4
'@types/node': 20.6.2 '@types/node': 16.18.58
'@types/responselike': 1.0.0 '@types/responselike': 1.0.0
dev: true dev: true
/@types/clean-css@4.2.7: /@types/clean-css@4.2.7:
resolution: {integrity: sha512-lcoZHjUAANLTACLGi+O/0pN+oKQAQ8zAMWJSxiBRNLxqZG/WE8hfXJUs1eYwJOvOnDJrvxU1kR77UiVJ3+9N0Q==} resolution: {integrity: sha512-lcoZHjUAANLTACLGi+O/0pN+oKQAQ8zAMWJSxiBRNLxqZG/WE8hfXJUs1eYwJOvOnDJrvxU1kR77UiVJ3+9N0Q==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
source-map: 0.6.1 source-map: 0.6.1
dev: true dev: true
@@ -5341,7 +5359,7 @@ packages:
/@types/connect@3.4.36: /@types/connect@3.4.36:
resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: true dev: true
/@types/degit@2.8.4: /@types/degit@2.8.4:
@@ -5408,7 +5426,7 @@ packages:
resolution: {integrity: sha512-c0hrgAOVYr21EX8J0jBMXGLMgJqVf/v6yxi0dLaJboW9aQPh16Id+z6w2Tx1hm+piJOLv8xPfVKZCLfjPw/IMQ==} resolution: {integrity: sha512-c0hrgAOVYr21EX8J0jBMXGLMgJqVf/v6yxi0dLaJboW9aQPh16Id+z6w2Tx1hm+piJOLv8xPfVKZCLfjPw/IMQ==}
dependencies: dependencies:
'@types/jsonfile': 6.1.2 '@types/jsonfile': 6.1.2
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: true dev: true
/@types/fs-extra@9.0.13: /@types/fs-extra@9.0.13:
@@ -5421,12 +5439,12 @@ packages:
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
dependencies: dependencies:
'@types/minimatch': 5.1.2 '@types/minimatch': 5.1.2
'@types/node': 20.6.2 '@types/node': 16.18.58
/@types/graceful-fs@4.1.6: /@types/graceful-fs@4.1.6:
resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
/@types/hapi__joi@17.1.9: /@types/hapi__joi@17.1.9:
resolution: {integrity: sha512-oOMFT8vmCTFncsF1engrs04jatz8/Anwx3De9uxnOK4chgSEgWBvFtpSoJo8u3784JNO+ql5tzRR6phHoRnscQ==} resolution: {integrity: sha512-oOMFT8vmCTFncsF1engrs04jatz8/Anwx3De9uxnOK4chgSEgWBvFtpSoJo8u3784JNO+ql5tzRR6phHoRnscQ==}
@@ -5510,7 +5528,7 @@ packages:
/@types/jsdom@20.0.1: /@types/jsdom@20.0.1:
resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
'@types/tough-cookie': 4.0.3 '@types/tough-cookie': 4.0.3
parse5: 7.1.2 parse5: 7.1.2
dev: true dev: true
@@ -5525,7 +5543,7 @@ packages:
/@types/jsonfile@6.1.2: /@types/jsonfile@6.1.2:
resolution: {integrity: sha512-8t92P+oeW4d/CRQfJaSqEwXujrhH4OEeHRjGU3v1Q8mUS8GPF3yiX26sw4svv6faL2HfBtGTe2xWIoVgN3dy9w==} resolution: {integrity: sha512-8t92P+oeW4d/CRQfJaSqEwXujrhH4OEeHRjGU3v1Q8mUS8GPF3yiX26sw4svv6faL2HfBtGTe2xWIoVgN3dy9w==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: true dev: true
/@types/jsonwebtoken@8.5.9: /@types/jsonwebtoken@8.5.9:
@@ -5537,7 +5555,7 @@ packages:
/@types/keyv@3.1.4: /@types/keyv@3.1.4:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: true dev: true
/@types/lodash@4.14.198: /@types/lodash@4.14.198:
@@ -5595,7 +5613,7 @@ packages:
/@types/needle@3.2.0: /@types/needle@3.2.0:
resolution: {integrity: sha512-6XzvzEyJ2ozFNfPajFmqH9JOt0Hp+9TawaYpJT59iIP/zR0U37cfWCRwosyIeEBBZBi021Osq4jGAD3AOju5fg==} resolution: {integrity: sha512-6XzvzEyJ2ozFNfPajFmqH9JOt0Hp+9TawaYpJT59iIP/zR0U37cfWCRwosyIeEBBZBi021Osq4jGAD3AOju5fg==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: true dev: true
/@types/node-fetch@2.6.4: /@types/node-fetch@2.6.4:
@@ -5765,6 +5783,14 @@ packages:
dependencies: dependencies:
'@types/react': 18.2.15 '@types/react': 18.2.15
/@types/react@18.0.21:
resolution: {integrity: sha512-7QUCOxvFgnD5Jk8ZKlUAhVcRj7GuJRjnjjiY/IUBWKgOlnvDvTMLD4RTF7NPyVmbRhNrbomZiOepg7M/2Kj1mA==}
dependencies:
'@types/prop-types': 15.7.5
'@types/scheduler': 0.16.3
csstype: 3.1.2
dev: true
/@types/react@18.2.15: /@types/react@18.2.15:
resolution: {integrity: sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==} resolution: {integrity: sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==}
dependencies: dependencies:
@@ -5779,7 +5805,7 @@ packages:
/@types/responselike@1.0.0: /@types/responselike@1.0.0:
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: true dev: true
/@types/scheduler@0.16.3: /@types/scheduler@0.16.3:
@@ -5793,7 +5819,7 @@ packages:
resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==}
dependencies: dependencies:
'@types/mime': 1.3.2 '@types/mime': 1.3.2
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: true dev: true
/@types/serve-static@1.15.2: /@types/serve-static@1.15.2:
@@ -5801,7 +5827,7 @@ packages:
dependencies: dependencies:
'@types/http-errors': 2.0.2 '@types/http-errors': 2.0.2
'@types/mime': 2.0.3 '@types/mime': 2.0.3
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: true dev: true
/@types/sharp@0.31.1: /@types/sharp@0.31.1:
@@ -5814,7 +5840,7 @@ packages:
resolution: {integrity: sha512-ZA8U81/gldY+rR5zl/7HSHrG2KDfEb3lzG6uCUDhW1DTQE9yC/VBQ45fXnXq8f3CgInfhZmjtdu/WOUlrXRQUg==} resolution: {integrity: sha512-ZA8U81/gldY+rR5zl/7HSHrG2KDfEb3lzG6uCUDhW1DTQE9yC/VBQ45fXnXq8f3CgInfhZmjtdu/WOUlrXRQUg==}
dependencies: dependencies:
'@types/glob': 7.2.0 '@types/glob': 7.2.0
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: true dev: true
/@types/source-list-map@0.1.2: /@types/source-list-map@0.1.2:
@@ -5888,7 +5914,7 @@ packages:
/@types/webpack-sources@3.2.0: /@types/webpack-sources@3.2.0:
resolution: {integrity: sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==} resolution: {integrity: sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
'@types/source-list-map': 0.1.2 '@types/source-list-map': 0.1.2
source-map: 0.7.4 source-map: 0.7.4
dev: true dev: true
@@ -5896,7 +5922,7 @@ packages:
/@types/webpack@4.41.33: /@types/webpack@4.41.33:
resolution: {integrity: sha512-PPajH64Ft2vWevkerISMtnZ8rTs4YmRbs+23c402J0INmxDKCrhZNvwZYtzx96gY2wAtXdrK1BS2fiC8MlLr3g==} resolution: {integrity: sha512-PPajH64Ft2vWevkerISMtnZ8rTs4YmRbs+23c402J0INmxDKCrhZNvwZYtzx96gY2wAtXdrK1BS2fiC8MlLr3g==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
'@types/tapable': 1.0.9 '@types/tapable': 1.0.9
'@types/uglify-js': 3.17.2 '@types/uglify-js': 3.17.2
'@types/webpack-sources': 3.2.0 '@types/webpack-sources': 3.2.0
@@ -5907,13 +5933,13 @@ packages:
/@types/whatwg-url@8.2.2: /@types/whatwg-url@8.2.2:
resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==} resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
'@types/webidl-conversions': 7.0.0 '@types/webidl-conversions': 7.0.0
/@types/ws@8.5.5: /@types/ws@8.5.5:
resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==} resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
dev: false dev: false
/@types/yargs-parser@21.0.0: /@types/yargs-parser@21.0.0:
@@ -11027,7 +11053,7 @@ packages:
'@jest/expect': 29.7.0 '@jest/expect': 29.7.0
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.6.2 '@types/node': 16.18.58
chalk: 4.1.2 chalk: 4.1.2
co: 4.6.0 co: 4.6.0
dedent: 1.5.1 dedent: 1.5.1
@@ -11141,7 +11167,6 @@ packages:
transitivePeerDependencies: transitivePeerDependencies:
- babel-plugin-macros - babel-plugin-macros
- supports-color - supports-color
dev: true
/jest-config@29.7.0(@types/node@20.5.7)(ts-node@10.9.1): /jest-config@29.7.0(@types/node@20.5.7)(ts-node@10.9.1):
resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
@@ -11183,46 +11208,6 @@ packages:
- babel-plugin-macros - babel-plugin-macros
- supports-color - supports-color
/jest-config@29.7.0(@types/node@20.6.2)(ts-node@10.9.1):
resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
'@types/node': '*'
ts-node: '>=9.0.0'
peerDependenciesMeta:
'@types/node':
optional: true
ts-node:
optional: true
dependencies:
'@babel/core': 7.22.20
'@jest/test-sequencer': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.6.2
babel-jest: 29.7.0(@babel/core@7.22.20)
chalk: 4.1.2
ci-info: 3.8.0
deepmerge: 4.3.1
glob: 7.2.3
graceful-fs: 4.2.11
jest-circus: 29.7.0
jest-environment-node: 29.7.0
jest-get-type: 29.6.3
jest-regex-util: 29.6.3
jest-resolve: 29.7.0
jest-runner: 29.7.0
jest-util: 29.7.0
jest-validate: 29.7.0
micromatch: 4.0.5
parse-json: 5.2.0
pretty-format: 29.7.0
slash: 3.0.0
strip-json-comments: 3.1.1
ts-node: 10.9.1(@swc/core@1.3.76)(@types/node@20.5.7)(typescript@5.2.2)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
/jest-diff@27.5.1: /jest-diff@27.5.1:
resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==} resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@@ -11271,7 +11256,7 @@ packages:
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/jsdom': 20.0.1 '@types/jsdom': 20.0.1
'@types/node': 20.6.2 '@types/node': 16.18.58
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
jsdom: 20.0.3 jsdom: 20.0.3
@@ -11288,7 +11273,7 @@ packages:
'@jest/environment': 29.7.0 '@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.6.2 '@types/node': 16.18.58
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@@ -11307,7 +11292,7 @@ packages:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/graceful-fs': 4.1.6 '@types/graceful-fs': 4.1.6
'@types/node': 20.6.2 '@types/node': 16.18.58
anymatch: 3.1.3 anymatch: 3.1.3
fb-watchman: 2.0.2 fb-watchman: 2.0.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -11364,7 +11349,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.6.2 '@types/node': 16.18.58
jest-util: 29.7.0 jest-util: 29.7.0
/jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
@@ -11414,7 +11399,7 @@ packages:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.6.2 '@types/node': 16.18.58
chalk: 4.1.2 chalk: 4.1.2
emittery: 0.13.1 emittery: 0.13.1
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -11444,7 +11429,7 @@ packages:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.6.2 '@types/node': 16.18.58
chalk: 4.1.2 chalk: 4.1.2
cjs-module-lexer: 1.2.3 cjs-module-lexer: 1.2.3
collect-v8-coverage: 1.0.2 collect-v8-coverage: 1.0.2
@@ -11517,7 +11502,7 @@ packages:
dependencies: dependencies:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.6.2 '@types/node': 16.18.58
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
chalk: 4.1.2 chalk: 4.1.2
emittery: 0.13.1 emittery: 0.13.1
@@ -11528,7 +11513,7 @@ packages:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'} engines: {node: '>= 10.13.0'}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
merge-stream: 2.0.0 merge-stream: 2.0.0
supports-color: 8.1.1 supports-color: 8.1.1
@@ -11536,7 +11521,7 @@ packages:
resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@types/node': 20.6.2 '@types/node': 16.18.58
jest-util: 29.7.0 jest-util: 29.7.0
merge-stream: 2.0.0 merge-stream: 2.0.0
supports-color: 8.1.1 supports-color: 8.1.1

1
test/plugin-seo/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/media

View File

@@ -0,0 +1,32 @@
import path from 'path'
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import { mediaSlug } from '../shared'
export const Media: CollectionConfig = {
slug: mediaSlug,
upload: {
staticDir: path.resolve(__dirname, '../media'),
},
fields: [
{
type: 'upload',
name: 'media',
relationTo: 'media',
filterOptions: {
mimeType: {
equals: 'image/png',
},
},
},
{
type: 'richText',
name: 'richText',
},
],
}
export const uploadsDoc = {
text: 'An upload here',
}

View File

@@ -1,18 +1,29 @@
import type { CollectionConfig } from 'payload/types' import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
const Pages: CollectionConfig = { import { pagesSlug } from '../shared'
slug: 'pages',
export const Pages: CollectionConfig = {
slug: pagesSlug,
labels: {
singular: 'Page',
plural: 'Pages',
},
admin: { admin: {
useAsTitle: 'title', useAsTitle: 'title',
}, },
versions: {
drafts: true,
},
fields: [ fields: [
{ {
name: 'title', name: 'title',
label: 'Title',
type: 'text', type: 'text',
required: true, required: true,
}, },
{ {
name: 'excerpt', name: 'excerpt',
label: 'Excerpt',
type: 'text', type: 'text',
}, },
{ {
@@ -29,5 +40,3 @@ const Pages: CollectionConfig = {
}, },
], ],
} }
export default Pages

View File

@@ -1,6 +1,6 @@
import type { CollectionConfig } from 'payload/types' import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
const Users: CollectionConfig = { export const Users: CollectionConfig = {
slug: 'users', slug: 'users',
auth: true, auth: true,
admin: { admin: {
@@ -14,5 +14,3 @@ const Users: CollectionConfig = {
// Add more fields as needed // Add more fields as needed
], ],
} }
export default Users

62
test/plugin-seo/config.ts Normal file
View File

@@ -0,0 +1,62 @@
import path from 'path'
import seoPlugin from '../../packages/plugin-seo/src'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials'
import { Media } from './collections/Media'
import { Pages } from './collections/Pages'
import { Users } from './collections/Users'
import { seed } from './seed'
const mockModulePath = path.resolve(__dirname, './mocks/mockFSModule.js')
export default buildConfigWithDefaults({
collections: [Users, Pages, Media],
localization: {
defaultLocale: 'en',
fallback: true,
locales: ['en', 'es', 'de'],
},
admin: {
webpack: (config) => ({
...config,
resolve: {
...config.resolve,
alias: {
...config?.resolve?.alias,
fs: mockModulePath,
},
},
}),
},
plugins: [
seoPlugin({
collections: ['pages', 'posts'],
globals: ['settings'],
tabbedUI: true,
uploadsCollection: 'media',
fields: [
{
name: 'ogTitle',
type: 'text',
label: 'og:title',
},
],
generateTitle: (data: any) => `Website.com — ${data?.doc?.title?.value}`,
generateDescription: ({ doc }: any) => doc?.excerpt?.value,
generateURL: ({ doc, locale }: any) =>
`https://yoursite.com/${locale ? locale + '/' : ''}${doc?.slug?.value || ''}`,
}),
],
onInit: async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
await seed(payload)
},
})

View File

@@ -0,0 +1,28 @@
import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
const { beforeAll, describe } = test
let url: AdminUrlUtil
describe('SEO Plugin', () => {
beforeAll(async () => {
const { serverURL } = await initPayloadE2E(__dirname)
url = new AdminUrlUtil(serverURL, 'pages')
})
test('test', async () => {
await expect(true).toBe(true)
})
// it.todo('e2e: should auto-generate meta title when button is clicked')
// it.todo('e2e: should properly merge top-level tabs when `tabbedUI` is enabled')
// it.todo('e2e: should render a preview image of a mock search engine result')
// it.todo('e2e: should render the length indicator with the correct progress and color')
})

BIN
test/plugin-seo/image-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

140
test/plugin-seo/int.spec.ts Normal file
View File

@@ -0,0 +1,140 @@
import path from 'path'
import payload from '../../packages/payload/src'
import getFileByPath from '../../packages/payload/src/uploads/getFileByPath'
import { initPayloadTest } from '../helpers/configHelpers'
import removeFiles from '../helpers/removeFiles'
import { mediaSlug } from './shared'
describe('SEO Plugin', () => {
let page = null
let mediaDoc = null
beforeAll(async () => {
const uploadsDir = path.resolve(__dirname, './media')
removeFiles(path.normalize(uploadsDir))
await initPayloadTest({ __dirname, init: { local: true } })
// Create image
const filePath = path.resolve(__dirname, './image-1.jpg')
const file = await getFileByPath(filePath)
mediaDoc = await payload.create({
collection: mediaSlug,
data: {},
file,
})
page = await payload.create({
collection: 'pages',
data: {
title: 'Test page',
slug: 'test-page',
},
depth: 0,
})
})
it('should add meta title', async () => {
const pageWithTitle = await payload.update({
collection: 'pages',
id: page.id,
data: {
meta: {
title: 'Hello, world!',
},
},
depth: 0,
})
expect(pageWithTitle).toHaveProperty('meta')
expect(pageWithTitle.meta).toHaveProperty('title')
expect(pageWithTitle.meta.title).toBe('Hello, world!')
})
it('should add meta description', async () => {
const pageWithDescription = await payload.update({
collection: 'pages',
id: page.id,
data: {
meta: {
description: 'This is a test page',
},
},
depth: 0,
})
expect(pageWithDescription).toHaveProperty('meta')
expect(pageWithDescription.meta).toHaveProperty('description')
expect(pageWithDescription.meta.description).toBe('This is a test page')
})
it('should add meta image', async () => {
const pageWithImage = await payload.update({
collection: 'pages',
id: page.id,
data: {
meta: {
image: mediaDoc.id,
},
},
depth: 0,
})
expect(pageWithImage).toHaveProperty('meta')
expect(pageWithImage.meta).toHaveProperty('image')
expect(pageWithImage.meta.image).toBe(mediaDoc.id)
})
it('should add custom meta field', async () => {
const pageWithCustomField = await payload.update({
collection: 'pages',
id: page.id,
data: {
meta: {
ogTitle: 'Hello, world!',
},
},
depth: 0,
})
expect(pageWithCustomField).toHaveProperty('meta')
expect(pageWithCustomField.meta).toHaveProperty('ogTitle')
expect(pageWithCustomField.meta.ogTitle).toBe('Hello, world!')
})
it('should localize meta fields', async () => {
const pageWithLocalizedMeta = await payload.update({
collection: 'pages',
id: page.id,
data: {
meta: {
title: 'Hola, mundo!',
description: 'Esta es una página de prueba',
},
},
locale: 'es',
depth: 0,
})
expect(pageWithLocalizedMeta).toHaveProperty('meta')
expect(pageWithLocalizedMeta.meta).toHaveProperty('title')
expect(pageWithLocalizedMeta.meta.title).toBe('Hola, mundo!')
expect(pageWithLocalizedMeta.meta).toHaveProperty('description')
expect(pageWithLocalizedMeta.meta.description).toBe('Esta es una página de prueba')
// query the page in the default locale
const pageInDefaultLocale = await payload.findByID({
collection: 'pages',
id: page.id,
depth: 0,
})
expect(pageInDefaultLocale).toHaveProperty('meta')
expect(pageInDefaultLocale.meta).toHaveProperty('title')
expect(pageInDefaultLocale.meta.title).toBe('Hello, world!')
expect(pageInDefaultLocale.meta).toHaveProperty('description')
expect(pageInDefaultLocale.meta.description).toBe('This is a test page')
})
})

View File

@@ -0,0 +1,4 @@
export default {
readdirSync: () => {},
rmSync: () => {},
}

View File

@@ -0,0 +1,99 @@
/* tslint:disable */
/* eslint-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: {
users: User
pages: Page
media: Media
'payload-preferences': PayloadPreference
'payload-migrations': PayloadMigration
}
globals: {}
}
export interface User {
id: string
updatedAt: string
createdAt: string
email: string
resetPasswordToken?: string
resetPasswordExpiration?: string
salt?: string
hash?: string
loginAttempts?: number
lockUntil?: string
password: string
}
export interface Page {
id: string
title: string
excerpt?: string
slug: string
meta?: {
title?: string
description?: string
image?: string | Media
ogTitle?: string
}
updatedAt: string
createdAt: string
_status?: 'draft' | 'published'
}
export interface Media {
id: string
media?: string | Media
richText?: {
[k: string]: unknown
}[]
updatedAt: string
createdAt: string
url?: string
filename?: string
mimeType?: string
filesize?: number
width?: number
height?: number
}
export interface PayloadPreference {
id: string
user: {
relationTo: 'users'
value: string | User
}
key?: string
value?:
| {
[k: string]: unknown
}
| unknown[]
| string
| number
| boolean
| null
updatedAt: string
createdAt: string
}
export interface PayloadMigration {
id: string
name?: string
batch?: number
updatedAt: string
createdAt: string
}
declare module 'payload' {
export interface GeneratedTypes {
collections: {
users: User
pages: Page
media: Media
'payload-preferences': PayloadPreference
'payload-migrations': PayloadMigration
}
}
}

View File

@@ -0,0 +1,45 @@
import type { Payload } from 'payload'
import path from 'path'
import type { PayloadRequest } from '../../../packages/payload/types'
import getFileByPath from '../../../packages/payload/src/uploads/getFileByPath'
import { mediaSlug } from '../shared'
export const seed = async (payload: Payload): Promise<boolean> => {
payload.logger.info('Seeding data...')
const req = {} as PayloadRequest
try {
// Create image
const filePath = path.resolve(__dirname, '../image-1.jpg')
const file = await getFileByPath(filePath)
const mediaDoc = await payload.create({
collection: mediaSlug,
data: {},
file,
})
await payload.create({
collection: 'pages',
data: {
title: 'Test Page',
slug: 'test-page',
meta: {
title: 'This is a test meta title',
description: 'This is a test meta description',
ogTitle: 'This is a custom og:title field',
image: mediaDoc.id,
},
},
req,
})
return true
} catch (err) {
console.error(err)
return false
}
}

View File

@@ -0,0 +1,3 @@
export const pagesSlug = 'pages'
export const mediaSlug = 'media'

View File

@@ -38,6 +38,7 @@
{ "path": "./packages/richtext-lexical" }, { "path": "./packages/richtext-lexical" },
{ "path": "./packages/payload" }, { "path": "./packages/payload" },
{ "path": "./packages/live-preview" }, { "path": "./packages/live-preview" },
{ "path": "./packages/live-preview-react" } { "path": "./packages/live-preview-react" },
{ "path": "./packages/plugin-seo" }
] ]
} }