feat: collection, global and field props for hooks, fix request context initialization, add context to global hooks (#3780)

* feat: pass collection, global and field props to collection, global and field hooks - where applicable

* fix: initial request context not set for all operations

* chore: add tests which check the collection prop for collection hooks

* feat: add context to props of global hooks

* chore: add global tests for global and field props

* chore: int tests: use JSON instead of object hashes
This commit is contained in:
Alessio Gravili
2023-10-21 11:40:57 +02:00
committed by GitHub
parent 67d61df563
commit f6adbae0c7
52 changed files with 752 additions and 71 deletions

View File

@@ -0,0 +1,111 @@
/* eslint-disable no-param-reassign */
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
export const dataHooksSlug = 'data-hooks'
export const DataHooks: CollectionConfig = {
slug: dataHooksSlug,
access: {
read: () => true,
create: () => true,
delete: () => true,
update: () => true,
},
hooks: {
beforeOperation: [
async ({ context, collection, args }) => {
context['collection_beforeOperation_collection'] = JSON.stringify(collection)
return args
},
],
beforeChange: [
({ context, data, collection }) => {
context['collection_beforeChange_collection'] = JSON.stringify(collection)
return data
},
],
afterChange: [
async ({ context, collection }) => {
context['collection_afterChange_collection'] = JSON.stringify(collection)
},
],
beforeRead: [
async ({ context, collection }) => {
context['collection_beforeRead_collection'] = JSON.stringify(collection)
},
],
afterRead: [
({ context, collection, doc }) => {
context['collection_afterRead_collection'] = JSON.stringify(collection)
return doc
},
],
afterOperation: [
({ args, result, collection }) => {
args.req.context['collection_afterOperation_collection'] = JSON.stringify(collection)
for (const contextKey in args.req.context) {
if (contextKey.startsWith('collection_')) {
result[contextKey] = args.req.context[contextKey]
}
}
return result
},
],
},
fields: [
{
name: 'field_collectionAndField',
type: 'text',
hooks: {
beforeChange: [
({ collection, field, context, value }) => {
context['field_beforeChange_CollectionAndField'] =
JSON.stringify(collection) + JSON.stringify(field)
return value
},
],
afterRead: [
({ collection, field, context }) => {
return (
(context['field_beforeChange_CollectionAndField'] as string) +
JSON.stringify(collection) +
JSON.stringify(field)
)
},
],
},
},
{
name: 'collection_beforeOperation_collection',
type: 'text',
},
{
name: 'collection_beforeChange_collection',
type: 'text',
},
{
name: 'collection_afterChange_collection',
type: 'text',
},
{
name: 'collection_beforeRead_collection',
type: 'text',
},
{
name: 'collection_afterRead_collection',
type: 'text',
},
{
name: 'collection_afterOperation_collection',
type: 'text',
},
],
}

View File

@@ -1,14 +1,17 @@
import type { SanitizedConfig } from '../../packages/payload/src/config/types'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import AfterOperation from './collections/AfterOperation'
import ChainingHooks from './collections/ChainingHooks'
import ContextHooks from './collections/ContextHooks'
import { DataHooks } from './collections/Data'
import Hooks, { hooksSlug } from './collections/Hook'
import NestedAfterReadHooks from './collections/NestedAfterReadHooks'
import Relations from './collections/Relations'
import TransformHooks from './collections/Transform'
import Users, { seedHooksUsers } from './collections/Users'
export default buildConfigWithDefaults({
import { DataHooksGlobal } from './globals/Data'
export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
collections: [
AfterOperation,
ContextHooks,
@@ -18,7 +21,9 @@ export default buildConfigWithDefaults({
ChainingHooks,
Relations,
Users,
DataHooks,
],
globals: [DataHooksGlobal],
onInit: async (payload) => {
await seedHooksUsers(payload)
await payload.create({
@@ -38,3 +43,5 @@ export default buildConfigWithDefaults({
})
},
})
export default HooksConfig

View File

@@ -0,0 +1,97 @@
/* eslint-disable no-param-reassign */
import type { GlobalConfig } from '../../../../packages/payload/src/globals/config/types'
export const dataHooksGlobalSlug = 'data-hooks-global'
export const DataHooksGlobal: GlobalConfig = {
slug: dataHooksGlobalSlug,
access: {
read: () => true,
update: () => true,
},
hooks: {
beforeChange: [
({ data, global, context }) => {
context['global_beforeChange_global'] = JSON.stringify(global)
return data
},
],
beforeRead: [
async ({ context, global }) => {
context['global_beforeRead_global'] = JSON.stringify(global)
},
],
afterRead: [
({ context, global, doc }) => {
context['global_afterRead_global'] = JSON.stringify(global)
// Needs to be done for both afterRead (for findOne test) and afterChange (for update test)
for (const contextKey in context) {
if (contextKey.startsWith('global_')) {
doc[contextKey] = context[contextKey]
}
}
return doc
},
],
afterChange: [
async ({ context, global, doc }) => {
context['global_afterChange_global'] = JSON.stringify(global)
// Needs to be done for both afterRead (for findOne test) and afterChange (for update test), as afterChange is called after afterRead
for (const contextKey in context) {
if (contextKey.startsWith('global_')) {
doc[contextKey] = context[contextKey]
}
}
return doc
},
],
},
fields: [
{
name: 'field_globalAndField',
type: 'text',
hooks: {
beforeChange: [
({ global, field, context, value }) => {
context['field_beforeChange_GlobalAndField'] =
JSON.stringify(global) + JSON.stringify(field)
return value
},
],
afterRead: [
({ global, field, context }) => {
return (
(context['field_beforeChange_GlobalAndField'] as string) +
JSON.stringify(global) +
JSON.stringify(field)
)
},
],
},
},
{
name: 'global_beforeChange_global',
type: 'text',
},
{
name: 'global_afterChange_global',
type: 'text',
},
{
name: 'global_beforeRead_global',
type: 'text',
},
{
name: 'global_afterRead_global',
type: 'text',
},
],
}

View File

@@ -8,6 +8,7 @@ import { RESTClient } from '../helpers/rest'
import { afterOperationSlug } from './collections/AfterOperation'
import { chainingHooksSlug } from './collections/ChainingHooks'
import { contextHooksSlug } from './collections/ContextHooks'
import { dataHooksSlug } from './collections/Data'
import { hooksSlug } from './collections/Hook'
import {
generatedAfterReadText,
@@ -16,7 +17,8 @@ import {
import { relationsSlug } from './collections/Relations'
import { transformSlug } from './collections/Transform'
import { hooksUsersSlug } from './collections/Users'
import configPromise from './config'
import configPromise, { HooksConfig } from './config'
import { dataHooksGlobalSlug } from './globals/Data'
let client: RESTClient
let apiUrl
@@ -293,4 +295,111 @@ describe('Hooks', () => {
).rejects.toThrow(AuthenticationError)
})
})
describe('hook parameter data', () => {
it('should pass collection prop to collection hooks', async () => {
const sanitizedConfig = await HooksConfig
const sanitizedHooksCollection = JSON.parse(
JSON.stringify(sanitizedConfig.collections.find(({ slug }) => slug === dataHooksSlug)),
)
const doc = await payload.create({
collection: dataHooksSlug,
data: {},
})
expect(JSON.parse(doc.collection_beforeOperation_collection)).toStrictEqual(
sanitizedHooksCollection,
)
expect(JSON.parse(doc.collection_beforeChange_collection)).toStrictEqual(
sanitizedHooksCollection,
)
expect(JSON.parse(doc.collection_afterChange_collection)).toStrictEqual(
sanitizedHooksCollection,
)
expect(JSON.parse(doc.collection_afterRead_collection)).toStrictEqual(
sanitizedHooksCollection,
)
expect(JSON.parse(doc.collection_afterOperation_collection)).toStrictEqual(
sanitizedHooksCollection,
)
// BeforeRead is only run for find operations
const foundDoc = await payload.findByID({
collection: dataHooksSlug,
id: doc.id,
})
expect(JSON.parse(foundDoc.collection_beforeRead_collection)).toStrictEqual(
sanitizedHooksCollection,
)
})
it('should pass collection and field props to field hooks', async () => {
const sanitizedConfig = await HooksConfig
const sanitizedHooksCollection = sanitizedConfig.collections.find(
({ slug }) => slug === dataHooksSlug,
)
const field = sanitizedHooksCollection.fields.find(
(field) => 'name' in field && field.name === 'field_collectionAndField',
)
const doc = await payload.create({
collection: dataHooksSlug,
data: {},
})
const collectionAndField = JSON.stringify(sanitizedHooksCollection) + JSON.stringify(field)
expect(doc.field_collectionAndField).toStrictEqual(collectionAndField + collectionAndField)
})
it('should pass global prop to global hooks', async () => {
const sanitizedConfig = await HooksConfig
const sanitizedHooksGlobal = JSON.parse(
JSON.stringify(sanitizedConfig.globals.find(({ slug }) => slug === dataHooksGlobalSlug)),
)
const doc = await payload.updateGlobal({
slug: dataHooksGlobalSlug,
data: {},
})
expect(JSON.parse(doc.global_beforeChange_global)).toStrictEqual(sanitizedHooksGlobal)
expect(JSON.parse(doc.global_afterRead_global)).toStrictEqual(sanitizedHooksGlobal)
expect(JSON.parse(doc.global_afterChange_global)).toStrictEqual(sanitizedHooksGlobal)
// beforeRead is only run for findOne operations
const foundDoc = await payload.findGlobal({
slug: dataHooksGlobalSlug,
})
expect(JSON.parse(foundDoc.global_beforeRead_global)).toStrictEqual(sanitizedHooksGlobal)
})
it('should pass global and field props to global hooks', async () => {
const sanitizedConfig = await HooksConfig
const sanitizedHooksGlobal = sanitizedConfig.globals.find(
({ slug }) => slug === dataHooksGlobalSlug,
)
const globalString = JSON.stringify(sanitizedHooksGlobal)
const fieldString = JSON.stringify(
sanitizedHooksGlobal.fields.find(
(field) => 'name' in field && field.name === 'field_globalAndField',
),
)
const doc = await payload.updateGlobal({
slug: dataHooksGlobalSlug,
data: {},
})
const globalAndFieldString = globalString + fieldString
expect(doc.field_globalAndField).toStrictEqual(globalAndFieldString + globalAndFieldString)
})
})
})