From 21eb19edd1b1f34cb93b02ff336df42098d20cf4 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 11 Sep 2022 20:57:30 -0700 Subject: [PATCH] chore: further refinements --- docs/hooks/fields.mdx | 2 +- src/fields/config/types.ts | 9 ++++ src/fields/hooks/afterChange/promise.ts | 12 +++--- src/fields/hooks/afterRead/promise.ts | 3 +- src/fields/hooks/beforeChange/promise.ts | 11 +++-- test/access-control/payload-types.ts | 53 ++++++++++++++++-------- test/fields/collections/Tabs/index.ts | 7 ++-- test/fields/int.spec.ts | 12 +++--- test/fields/payload-types.ts | 29 +++++++++++++ test/uploads/payload-types.ts | 2 + 10 files changed, 100 insertions(+), 40 deletions(-) diff --git a/docs/hooks/fields.mdx b/docs/hooks/fields.mdx index 6dbec80125..ca8879d759 100644 --- a/docs/hooks/fields.mdx +++ b/docs/hooks/fields.mdx @@ -61,7 +61,7 @@ Field Hooks receive one `args` argument that contains the following properties: | **`data`** | The data passed to update the document within `create` and `update` operations, and the full document itself in the `afterRead` hook. | | **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. | | **`operation`** | A string relating to which operation the field type is currently executing within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. | -| **`originalDoc`** | The full original document in `update` operations. | +| **`originalDoc`** | The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. | | **`req`** | The Express `request` object. It is mocked for Local API operations. | | **`siblingData`** | The sibling data passed to a field that the hook is running against. | | **`value`** | The value of the field. | diff --git a/src/fields/config/types.ts b/src/fields/config/types.ts index 2ec3e8a0d8..d94bce189e 100644 --- a/src/fields/config/types.ts +++ b/src/fields/config/types.ts @@ -10,12 +10,19 @@ import { User } from '../../auth'; import { Payload } from '../..'; export type FieldHookArgs = { + /** The data passed to update the document within create and update operations, and the full document itself in the afterRead hook. */ data?: Partial, + /** Boolean to denote if this hook is running against finding one, or finding many within the afterRead hook. */ findMany?: boolean + /** The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. */ originalDoc?: T, + /** A string relating to which operation the field type is currently executing within. Useful within beforeValidate, beforeChange, and afterChange hooks to differentiate between create and update operations. */ operation?: 'create' | 'read' | 'update' | 'delete', + /** The Express request object. It is mocked for Local API operations. */ req: PayloadRequest + /** The sibling data passed to a field that the hook is running against. */ siblingData: Partial + /** The value of the field. */ value?: P, } @@ -203,6 +210,7 @@ export type TabsField = Omit & { export type TabAsField = Tab & { type: 'tab' + name?: string }; export type UIField = { @@ -389,6 +397,7 @@ export type FieldAffectingData = | UploadField | CodeField | PointField + | TabAsField export type NonPresentationalField = TextField diff --git a/src/fields/hooks/afterChange/promise.ts b/src/fields/hooks/afterChange/promise.ts index 615108b6a0..1f9d6f9ce1 100644 --- a/src/fields/hooks/afterChange/promise.ts +++ b/src/fields/hooks/afterChange/promise.ts @@ -126,14 +126,12 @@ export const promise = async ({ } case 'tab': { - let tabSiblingData; - let tabSiblingDoc; + let tabSiblingData = siblingData; + let tabSiblingDoc = siblingDoc; + if (tabHasName(field)) { - tabSiblingData = siblingData[field.name] || {}; - tabSiblingDoc = siblingDoc[field.name]; - } else { - tabSiblingData = siblingData || {}; - tabSiblingDoc = { ...siblingDoc }; + tabSiblingData = siblingData[field.name] as Record; + tabSiblingDoc = siblingDoc[field.name] as Record; } await traverseFields({ diff --git a/src/fields/hooks/afterRead/promise.ts b/src/fields/hooks/afterRead/promise.ts index d9d51009fa..8cfcfd3f72 100644 --- a/src/fields/hooks/afterRead/promise.ts +++ b/src/fields/hooks/afterRead/promise.ts @@ -49,14 +49,13 @@ export const promise = async ({ const hasLocalizedValue = flattenLocales && fieldAffectsData(field) && (typeof siblingDoc[field.name] === 'object' && siblingDoc[field.name] !== null) - && field.name && field.localized && req.locale !== 'all'; if (hasLocalizedValue) { let localizedValue = siblingDoc[field.name][req.locale]; if (typeof localizedValue === 'undefined' && req.fallbackLocale) localizedValue = siblingDoc[field.name][req.fallbackLocale]; - if (typeof localizedValue === 'undefined' && field.type === 'group') localizedValue = {}; + if (typeof localizedValue === 'undefined' && (field.type === 'group' || field.type === 'tab')) localizedValue = {}; if (typeof localizedValue === 'undefined') localizedValue = null; siblingDoc[field.name] = localizedValue; } diff --git a/src/fields/hooks/beforeChange/promise.ts b/src/fields/hooks/beforeChange/promise.ts index 66e13f63f9..7f459e49f3 100644 --- a/src/fields/hooks/beforeChange/promise.ts +++ b/src/fields/hooks/beforeChange/promise.ts @@ -286,11 +286,16 @@ export const promise = async ({ let tabSiblingData = siblingData; let tabSiblingDoc = siblingDoc; let tabSiblingDocWithLocales = siblingDocWithLocales; + if (tabHasName(field)) { tabPath = `${path}${field.name}.`; - tabSiblingData = typeof siblingData[field.name] === 'object' ? siblingData[field.name] as Record : {}; - tabSiblingDoc = typeof siblingDoc[field.name] === 'object' ? siblingDoc[field.name] as Record : {}; - tabSiblingDocWithLocales = typeof siblingDocWithLocales[field.name] === 'object' ? siblingDocWithLocales[field.name] as Record : {}; + if (typeof siblingData[field.name] !== 'object') siblingData[field.name] = {}; + if (typeof siblingDoc[field.name] !== 'object') siblingDoc[field.name] = {}; + if (typeof siblingDocWithLocales[field.name] !== 'object') siblingDocWithLocales[field.name] = {}; + + tabSiblingData = siblingData[field.name] as Record; + tabSiblingDoc = siblingDoc[field.name] as Record; + tabSiblingDocWithLocales = siblingDocWithLocales[field.name] as Record; } await traverseFields({ diff --git a/test/access-control/payload-types.ts b/test/access-control/payload-types.ts index cbbace518a..87bc313598 100644 --- a/test/access-control/payload-types.ts +++ b/test/access-control/payload-types.ts @@ -5,33 +5,52 @@ * and re-run `payload generate:types` to regenerate this file. */ -export interface Config { } +export interface Config {} /** * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "autosave-global". + * via the `definition` "posts". */ -export interface AutosaveGlobal { +export interface Post { id: string; - _status?: 'draft' | 'published'; - title: string; -} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "autosave-posts". - */ -export interface AutosavePost { - id: string; - _status?: 'draft' | 'published'; - title: string; - description: string; + restrictedField?: string; createdAt: string; updatedAt: string; } /** * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "draft-posts". + * via the `definition` "restricted". */ -export interface DraftPost { +export interface Restricted { + id: string; + name?: string; + createdAt: string; + updatedAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "read-only-collection". + */ +export interface ReadOnlyCollection { + id: string; + name?: string; + createdAt: string; + updatedAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "restricted-versions". + */ +export interface RestrictedVersion { + id: string; + name?: string; + createdAt: string; + updatedAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "sibling-data". + */ +export interface SiblingDatum { id: string; array: { allowPublicReadability?: boolean; diff --git a/test/fields/collections/Tabs/index.ts b/test/fields/collections/Tabs/index.ts index 2b36a99591..d742e433c9 100644 --- a/test/fields/collections/Tabs/index.ts +++ b/test/fields/collections/Tabs/index.ts @@ -190,10 +190,9 @@ const TabsFields: CollectionConfig = { }, ], afterChange: [ - ({ data = {} }) => { - if (!data.hooksTab) data.hooksTab = {}; - data.hooksTab.afterChange = true; - return data.hooksTab; + ({ originalDoc }) => { + originalDoc.hooksTab.afterChange = true; + return originalDoc.hooksTab; }, ], afterRead: [ diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index 85a8591d7b..5466e2802f 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -357,14 +357,14 @@ describe('Fields', () => { }); it('should allow hooks on a named tab', async () => { - document = await payload.findByID({ + const newDocument = await payload.create({ collection: tabsSlug, - id: document.id, + data: tabsDoc, }); - expect(document.hooksTab.beforeValidate).toBe(true); - expect(document.hooksTab.beforeChange).toBe(true); - expect(document.hooksTab.afterChange).toBe(true); - expect(document.hooksTab.afterRead).toBe(true); + expect(newDocument.hooksTab.beforeValidate).toBe(true); + expect(newDocument.hooksTab.beforeChange).toBe(true); + expect(newDocument.hooksTab.afterChange).toBe(true); + expect(newDocument.hooksTab.afterRead).toBe(true); }); it('should return empty object for groups when no data present', async () => { diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index fbb64ed0f3..1183cad976 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -72,6 +72,13 @@ export interface BlockField { blockName?: string; blockType: 'subBlocks'; } + | { + textInCollapsible?: string; + textInRow?: string; + id?: string; + blockName?: string; + blockType: 'tabs'; + } )[]; localizedBlocks: ( | { @@ -108,6 +115,13 @@ export interface BlockField { blockName?: string; blockType: 'subBlocks'; } + | { + textInCollapsible?: string; + textInRow?: string; + id?: string; + blockName?: string; + blockType: 'tabs'; + } )[]; createdAt: string; updatedAt: string; @@ -273,12 +287,25 @@ export interface TabsField { blockName?: string; blockType: 'subBlocks'; } + | { + textInCollapsible?: string; + textInRow?: string; + id?: string; + blockName?: string; + blockType: 'tabs'; + } )[]; group: { number: number; }; textInRow: string; numberInRow: number; + text?: string; + defaultValue?: string; + beforeValidate?: boolean; + beforeChange?: boolean; + afterChange?: boolean; + afterRead?: boolean; textarea?: string; anotherText: string; createdAt: string; @@ -325,6 +352,8 @@ export interface Upload { filename?: string; mimeType?: string; filesize?: number; + width?: number; + height?: number; createdAt: string; updatedAt: string; } diff --git a/test/uploads/payload-types.ts b/test/uploads/payload-types.ts index 8c8ceb6ed4..ffb7080e8c 100644 --- a/test/uploads/payload-types.ts +++ b/test/uploads/payload-types.ts @@ -75,6 +75,8 @@ export interface UnstoredMedia { filename?: string; mimeType?: string; filesize?: number; + width?: number; + height?: number; createdAt: string; updatedAt: string; }