feat: form state select (#11689)
Implements a select-like API into the form state endpoint. This follows the same spec as the Select API on existing Payload operations, but works on form state rather than at the db level. This means you can send the `select` argument through the form state handler, and it will only process and return the fields you've explicitly identified. This is especially useful when you only need to generate a partial form state, for example within the bulk edit form where you select only a subset of fields to edit. There is no need to iterate all fields of the schema, generate default values for each, and return them all through the network. This will also simplify and reduce the amount of client-side processing required, where we longer need to strip unselected fields before submission.
This commit is contained in:
@@ -13,6 +13,9 @@ const esModules = [
|
|||||||
'path-exists',
|
'path-exists',
|
||||||
'qs-esm',
|
'qs-esm',
|
||||||
'uint8array-extras',
|
'uint8array-extras',
|
||||||
|
'@faceless-ui/window-info',
|
||||||
|
'@faceless-ui/modal',
|
||||||
|
'@faceless-ui/scroll-info',
|
||||||
].join('|')
|
].join('|')
|
||||||
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { SanitizedDocumentPermissions } from '../../auth/types.js'
|
|||||||
import type { Field, Validate } from '../../fields/config/types.js'
|
import type { Field, Validate } from '../../fields/config/types.js'
|
||||||
import type { TypedLocale } from '../../index.js'
|
import type { TypedLocale } from '../../index.js'
|
||||||
import type { DocumentPreferences } from '../../preferences/types.js'
|
import type { DocumentPreferences } from '../../preferences/types.js'
|
||||||
import type { PayloadRequest, Where } from '../../types/index.js'
|
import type { PayloadRequest, SelectType, Where } from '../../types/index.js'
|
||||||
|
|
||||||
export type Data = {
|
export type Data = {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
@@ -91,6 +91,7 @@ export type BuildFormStateArgs = {
|
|||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
returnLockStatus?: boolean
|
returnLockStatus?: boolean
|
||||||
schemaPath: string
|
schemaPath: string
|
||||||
|
select?: SelectType
|
||||||
skipValidation?: boolean
|
skipValidation?: boolean
|
||||||
updateLastEdited?: boolean
|
updateLastEdited?: boolean
|
||||||
} & (
|
} & (
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import type {
|
|||||||
import type { Block, Field, TabAsField } from '../../config/types.js'
|
import type { Block, Field, TabAsField } from '../../config/types.js'
|
||||||
|
|
||||||
import { MissingEditorProp } from '../../../errors/index.js'
|
import { MissingEditorProp } from '../../../errors/index.js'
|
||||||
|
import { getBlockSelect } from '../../../utilities/getBlockSelect.js'
|
||||||
|
import { stripUnselectedFields } from '../../../utilities/stripUnselectedFields.js'
|
||||||
import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.js'
|
import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.js'
|
||||||
import { getDefaultValue } from '../../getDefaultValue.js'
|
import { getDefaultValue } from '../../getDefaultValue.js'
|
||||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||||
@@ -122,20 +124,16 @@ export const promise = async ({
|
|||||||
delete siblingDoc[field.name]
|
delete siblingDoc[field.name]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip unselected fields
|
if (path !== 'id') {
|
||||||
if (fieldAffectsData(field) && select && selectMode && path !== 'id') {
|
const shouldContinue = stripUnselectedFields({
|
||||||
if (selectMode === 'include') {
|
field,
|
||||||
if (!select[field.name]) {
|
select,
|
||||||
delete siblingDoc[field.name]
|
selectMode,
|
||||||
return
|
siblingDoc,
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
if (selectMode === 'exclude') {
|
if (!shouldContinue) {
|
||||||
if (select[field.name] === false) {
|
return
|
||||||
delete siblingDoc[field.name]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,8 +452,6 @@ export const promise = async ({
|
|||||||
case 'blocks': {
|
case 'blocks': {
|
||||||
const rows = siblingDoc[field.name]
|
const rows = siblingDoc[field.name]
|
||||||
|
|
||||||
let blocksSelect = select?.[field.name]
|
|
||||||
|
|
||||||
if (Array.isArray(rows)) {
|
if (Array.isArray(rows)) {
|
||||||
rows.forEach((row, rowIndex) => {
|
rows.forEach((row, rowIndex) => {
|
||||||
const blockTypeToMatch = (row as JsonObject).blockType
|
const blockTypeToMatch = (row as JsonObject).blockType
|
||||||
@@ -466,37 +462,11 @@ export const promise = async ({
|
|||||||
(curBlock) => typeof curBlock !== 'string' && curBlock.slug === blockTypeToMatch,
|
(curBlock) => typeof curBlock !== 'string' && curBlock.slug === blockTypeToMatch,
|
||||||
) as Block | undefined)
|
) as Block | undefined)
|
||||||
|
|
||||||
let blockSelectMode = selectMode
|
const { blockSelect, blockSelectMode } = getBlockSelect({
|
||||||
|
block,
|
||||||
if (typeof blocksSelect === 'object') {
|
select: select?.[field.name],
|
||||||
blocksSelect = {
|
selectMode,
|
||||||
...blocksSelect,
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// sanitize blocks: {cta: false} to blocks: {cta: {id: true, blockType: true}}
|
|
||||||
if (selectMode === 'exclude' && blocksSelect[block.slug] === false) {
|
|
||||||
blockSelectMode = 'include'
|
|
||||||
blocksSelect[block.slug] = {
|
|
||||||
id: true,
|
|
||||||
blockType: true,
|
|
||||||
}
|
|
||||||
} else if (selectMode === 'include') {
|
|
||||||
if (!blocksSelect[block.slug]) {
|
|
||||||
blocksSelect[block.slug] = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof blocksSelect[block.slug] === 'object') {
|
|
||||||
blocksSelect[block.slug] = {
|
|
||||||
...(blocksSelect[block.slug] as object),
|
|
||||||
}
|
|
||||||
|
|
||||||
blocksSelect[block.slug]['id'] = true
|
|
||||||
blocksSelect[block.slug]['blockType'] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const blockSelect = blocksSelect?.[block.slug]
|
|
||||||
|
|
||||||
if (block) {
|
if (block) {
|
||||||
traverseFields({
|
traverseFields({
|
||||||
|
|||||||
@@ -1458,6 +1458,7 @@ export { flattenAllFields } from './utilities/flattenAllFields.js'
|
|||||||
export { default as flattenTopLevelFields } from './utilities/flattenTopLevelFields.js'
|
export { default as flattenTopLevelFields } from './utilities/flattenTopLevelFields.js'
|
||||||
export { formatErrors } from './utilities/formatErrors.js'
|
export { formatErrors } from './utilities/formatErrors.js'
|
||||||
export { formatLabels, formatNames, toWords } from './utilities/formatLabels.js'
|
export { formatLabels, formatNames, toWords } from './utilities/formatLabels.js'
|
||||||
|
export { getBlockSelect } from './utilities/getBlockSelect.js'
|
||||||
export { getCollectionIDFieldTypes } from './utilities/getCollectionIDFieldTypes.js'
|
export { getCollectionIDFieldTypes } from './utilities/getCollectionIDFieldTypes.js'
|
||||||
export { getObjectDotNotation } from './utilities/getObjectDotNotation.js'
|
export { getObjectDotNotation } from './utilities/getObjectDotNotation.js'
|
||||||
export { getRequestLanguage } from './utilities/getRequestLanguage.js'
|
export { getRequestLanguage } from './utilities/getRequestLanguage.js'
|
||||||
@@ -1477,6 +1478,7 @@ export { sanitizeFallbackLocale } from './utilities/sanitizeFallbackLocale.js'
|
|||||||
export { sanitizeJoinParams } from './utilities/sanitizeJoinParams.js'
|
export { sanitizeJoinParams } from './utilities/sanitizeJoinParams.js'
|
||||||
export { sanitizePopulateParam } from './utilities/sanitizePopulateParam.js'
|
export { sanitizePopulateParam } from './utilities/sanitizePopulateParam.js'
|
||||||
export { sanitizeSelectParam } from './utilities/sanitizeSelectParam.js'
|
export { sanitizeSelectParam } from './utilities/sanitizeSelectParam.js'
|
||||||
|
export { stripUnselectedFields } from './utilities/stripUnselectedFields.js'
|
||||||
export { traverseFields } from './utilities/traverseFields.js'
|
export { traverseFields } from './utilities/traverseFields.js'
|
||||||
export type { TraverseFieldsCallback } from './utilities/traverseFields.js'
|
export type { TraverseFieldsCallback } from './utilities/traverseFields.js'
|
||||||
export { buildVersionCollectionFields } from './versions/buildCollectionFields.js'
|
export { buildVersionCollectionFields } from './versions/buildCollectionFields.js'
|
||||||
|
|||||||
@@ -101,8 +101,9 @@ type CreateLocalReq = (
|
|||||||
export const createLocalReq: CreateLocalReq = async (
|
export const createLocalReq: CreateLocalReq = async (
|
||||||
{ context, fallbackLocale, locale: localeArg, req = {} as PayloadRequest, urlSuffix, user },
|
{ context, fallbackLocale, locale: localeArg, req = {} as PayloadRequest, urlSuffix, user },
|
||||||
payload,
|
payload,
|
||||||
) => {
|
): Promise<PayloadRequest> => {
|
||||||
const localization = payload.config?.localization
|
const localization = payload.config?.localization
|
||||||
|
|
||||||
if (localization) {
|
if (localization) {
|
||||||
const locale = localeArg === '*' ? 'all' : localeArg
|
const locale = localeArg === '*' ? 'all' : localeArg
|
||||||
const defaultLocale = localization.defaultLocale
|
const defaultLocale = localization.defaultLocale
|
||||||
|
|||||||
54
packages/payload/src/utilities/getBlockSelect.ts
Normal file
54
packages/payload/src/utilities/getBlockSelect.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import type { Block } from '../fields/config/types.js'
|
||||||
|
import type { SelectMode, SelectType } from '../types/index.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used for the Select API to determine the select level of a block.
|
||||||
|
* It will ensure that `id` and `blockType` are always included in the select object.
|
||||||
|
* @returns { blockSelect: boolean | SelectType, blockSelectMode: SelectMode }
|
||||||
|
*/
|
||||||
|
export const getBlockSelect = ({
|
||||||
|
block,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
|
}: {
|
||||||
|
block: Block
|
||||||
|
select: SelectType[string]
|
||||||
|
selectMode: SelectMode
|
||||||
|
}): { blockSelect: boolean | SelectType; blockSelectMode: SelectMode } => {
|
||||||
|
if (typeof select === 'object') {
|
||||||
|
let blockSelectMode = selectMode
|
||||||
|
|
||||||
|
const blocksSelect = {
|
||||||
|
...select,
|
||||||
|
}
|
||||||
|
|
||||||
|
let blockSelect = blocksSelect[block.slug]
|
||||||
|
|
||||||
|
// sanitize `{ blocks: { cta: false }}` to `{ blocks: { cta: { id: true, blockType: true }}}`
|
||||||
|
if (selectMode === 'exclude' && blockSelect === false) {
|
||||||
|
blockSelectMode = 'include'
|
||||||
|
|
||||||
|
blockSelect = {
|
||||||
|
id: true,
|
||||||
|
blockType: true,
|
||||||
|
}
|
||||||
|
} else if (selectMode === 'include') {
|
||||||
|
if (!blockSelect) {
|
||||||
|
blockSelect = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof blockSelect === 'object') {
|
||||||
|
blockSelect = {
|
||||||
|
...blockSelect,
|
||||||
|
}
|
||||||
|
|
||||||
|
blockSelect['id'] = true
|
||||||
|
blockSelect['blockType'] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { blockSelect, blockSelectMode }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { blockSelect: select, blockSelectMode: selectMode }
|
||||||
|
}
|
||||||
43
packages/payload/src/utilities/stripUnselectedFields.ts
Normal file
43
packages/payload/src/utilities/stripUnselectedFields.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import type { Data } from '../admin/types.js'
|
||||||
|
import type { Field, TabAsField } from '../fields/config/types.js'
|
||||||
|
import type { SelectMode, SelectType } from '../types/index.js'
|
||||||
|
|
||||||
|
import { fieldAffectsData } from '../fields/config/types.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used for the Select API to strip out fields that are not selected.
|
||||||
|
* It will mutate the given data object and determine if your recursive function should continue to run.
|
||||||
|
* It is used within the `afterRead` hook as well as `getFormState`.
|
||||||
|
* @returns boolean - whether or not the recursive function should continue
|
||||||
|
*/
|
||||||
|
export const stripUnselectedFields = ({
|
||||||
|
field,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
|
siblingDoc,
|
||||||
|
}: {
|
||||||
|
field: Field | TabAsField
|
||||||
|
select: SelectType
|
||||||
|
selectMode: SelectMode
|
||||||
|
siblingDoc: Data
|
||||||
|
}): boolean => {
|
||||||
|
let shouldContinue = true
|
||||||
|
|
||||||
|
if (fieldAffectsData(field) && select && selectMode && field.name) {
|
||||||
|
if (selectMode === 'include') {
|
||||||
|
if (!select[field.name]) {
|
||||||
|
delete siblingDoc[field.name]
|
||||||
|
shouldContinue = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectMode === 'exclude') {
|
||||||
|
if (select[field.name] === false) {
|
||||||
|
delete siblingDoc[field.name]
|
||||||
|
shouldContinue = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldContinue
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
reduceFieldsToValues,
|
reduceFieldsToValues,
|
||||||
wait,
|
wait,
|
||||||
} from 'payload/shared'
|
} from 'payload/shared'
|
||||||
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ import type {
|
|||||||
PayloadRequest,
|
PayloadRequest,
|
||||||
SanitizedFieldPermissions,
|
SanitizedFieldPermissions,
|
||||||
SanitizedFieldsPermissions,
|
SanitizedFieldsPermissions,
|
||||||
|
SelectMode,
|
||||||
|
SelectType,
|
||||||
Validate,
|
Validate,
|
||||||
} from 'payload'
|
} from 'payload'
|
||||||
|
|
||||||
import ObjectIdImport from 'bson-objectid'
|
import ObjectIdImport from 'bson-objectid'
|
||||||
|
import { getBlockSelect } from 'payload'
|
||||||
import {
|
import {
|
||||||
deepCopyObjectSimple,
|
deepCopyObjectSimple,
|
||||||
fieldAffectsData,
|
fieldAffectsData,
|
||||||
@@ -86,6 +89,8 @@ export type AddFieldStatePromiseArgs = {
|
|||||||
*/
|
*/
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
schemaPath: string
|
schemaPath: string
|
||||||
|
select?: SelectType
|
||||||
|
selectMode?: SelectMode
|
||||||
/**
|
/**
|
||||||
* Whether to skip checking the field's condition. @default false
|
* Whether to skip checking the field's condition. @default false
|
||||||
*/
|
*/
|
||||||
@@ -130,6 +135,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
renderFieldFn,
|
renderFieldFn,
|
||||||
req,
|
req,
|
||||||
schemaPath,
|
schemaPath,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
skipConditionChecks = false,
|
skipConditionChecks = false,
|
||||||
skipValidation = false,
|
skipValidation = false,
|
||||||
state,
|
state,
|
||||||
@@ -247,6 +254,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
case 'array': {
|
case 'array': {
|
||||||
const arrayValue = Array.isArray(data[field.name]) ? data[field.name] : []
|
const arrayValue = Array.isArray(data[field.name]) ? data[field.name] : []
|
||||||
|
|
||||||
|
const arraySelect = select?.[field.name]
|
||||||
|
|
||||||
const { promises, rows } = arrayValue.reduce(
|
const { promises, rows } = arrayValue.reduce(
|
||||||
(acc, row, i: number) => {
|
(acc, row, i: number) => {
|
||||||
const parentPath = path + '.' + i
|
const parentPath = path + '.' + i
|
||||||
@@ -293,6 +302,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
renderAllFields: requiresRender,
|
renderAllFields: requiresRender,
|
||||||
renderFieldFn,
|
renderFieldFn,
|
||||||
req,
|
req,
|
||||||
|
select: typeof arraySelect === 'object' ? arraySelect : undefined,
|
||||||
|
selectMode,
|
||||||
skipConditionChecks,
|
skipConditionChecks,
|
||||||
skipValidation,
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
@@ -373,6 +384,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
const { promises, rowMetadata } = blocksValue.reduce(
|
const { promises, rowMetadata } = blocksValue.reduce(
|
||||||
(acc, row, i: number) => {
|
(acc, row, i: number) => {
|
||||||
const blockTypeToMatch: string = row.blockType
|
const blockTypeToMatch: string = row.blockType
|
||||||
|
|
||||||
const block =
|
const block =
|
||||||
req.payload.blocks[blockTypeToMatch] ??
|
req.payload.blocks[blockTypeToMatch] ??
|
||||||
((field.blockReferences ?? field.blocks).find(
|
((field.blockReferences ?? field.blocks).find(
|
||||||
@@ -385,6 +397,12 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { blockSelect, blockSelectMode } = getBlockSelect({
|
||||||
|
block,
|
||||||
|
select: select?.[field.name],
|
||||||
|
selectMode,
|
||||||
|
})
|
||||||
|
|
||||||
const parentPath = path + '.' + i
|
const parentPath = path + '.' + i
|
||||||
|
|
||||||
if (block) {
|
if (block) {
|
||||||
@@ -468,6 +486,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
renderAllFields: requiresRender,
|
renderAllFields: requiresRender,
|
||||||
renderFieldFn,
|
renderFieldFn,
|
||||||
req,
|
req,
|
||||||
|
select: typeof blockSelect === 'object' ? blockSelect : undefined,
|
||||||
|
selectMode: blockSelectMode,
|
||||||
skipConditionChecks,
|
skipConditionChecks,
|
||||||
skipValidation,
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
@@ -534,6 +554,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
state[path] = fieldState
|
state[path] = fieldState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupSelect = select?.[field.name]
|
||||||
|
|
||||||
await iterateFields({
|
await iterateFields({
|
||||||
id,
|
id,
|
||||||
addErrorPathToParent,
|
addErrorPathToParent,
|
||||||
@@ -561,6 +583,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
renderAllFields,
|
renderAllFields,
|
||||||
renderFieldFn,
|
renderFieldFn,
|
||||||
req,
|
req,
|
||||||
|
select: typeof groupSelect === 'object' ? groupSelect : undefined,
|
||||||
|
selectMode,
|
||||||
skipConditionChecks,
|
skipConditionChecks,
|
||||||
skipValidation,
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
@@ -685,6 +709,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
|
|
||||||
await iterateFields({
|
await iterateFields({
|
||||||
id,
|
id,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
// passthrough parent functionality
|
// passthrough parent functionality
|
||||||
addErrorPathToParent: addErrorPathToParentArg,
|
addErrorPathToParent: addErrorPathToParentArg,
|
||||||
anyParentLocalized: fieldIsLocalized(field) || anyParentLocalized,
|
anyParentLocalized: fieldIsLocalized(field) || anyParentLocalized,
|
||||||
@@ -717,6 +743,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
} else if (field.type === 'tabs') {
|
} else if (field.type === 'tabs') {
|
||||||
const promises = field.tabs.map((tab, tabIndex) => {
|
const promises = field.tabs.map((tab, tabIndex) => {
|
||||||
const isNamedTab = tabHasName(tab)
|
const isNamedTab = tabHasName(tab)
|
||||||
|
let tabSelect: SelectType | undefined
|
||||||
|
|
||||||
const {
|
const {
|
||||||
indexPath: tabIndexPath,
|
indexPath: tabIndexPath,
|
||||||
@@ -746,8 +773,13 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
childPermissions = tabPermissions?.fields
|
childPermissions = tabPermissions?.fields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof select?.[tab.name] === 'object') {
|
||||||
|
tabSelect = select?.[tab.name] as SelectType
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
childPermissions = parentPermissions
|
childPermissions = parentPermissions
|
||||||
|
tabSelect = select
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathSegments = path ? path.split('.') : []
|
const pathSegments = path ? path.split('.') : []
|
||||||
@@ -796,6 +828,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
renderAllFields,
|
renderAllFields,
|
||||||
renderFieldFn,
|
renderFieldFn,
|
||||||
req,
|
req,
|
||||||
|
select: tabSelect,
|
||||||
|
selectMode,
|
||||||
skipConditionChecks,
|
skipConditionChecks,
|
||||||
skipValidation,
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import type { Data, Field as FieldSchema, PayloadRequest, User } from 'payload'
|
import type {
|
||||||
|
Data,
|
||||||
|
Field as FieldSchema,
|
||||||
|
PayloadRequest,
|
||||||
|
SelectMode,
|
||||||
|
SelectType,
|
||||||
|
User,
|
||||||
|
} from 'payload'
|
||||||
|
|
||||||
import { iterateFields } from './iterateFields.js'
|
import { iterateFields } from './iterateFields.js'
|
||||||
|
|
||||||
@@ -8,6 +15,8 @@ type Args = {
|
|||||||
id?: number | string
|
id?: number | string
|
||||||
locale: string | undefined
|
locale: string | undefined
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
|
select?: SelectType
|
||||||
|
selectMode?: SelectMode
|
||||||
siblingData: Data
|
siblingData: Data
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
@@ -18,6 +27,8 @@ export const calculateDefaultValues = async ({
|
|||||||
fields,
|
fields,
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
user,
|
user,
|
||||||
}: Args): Promise<Data> => {
|
}: Args): Promise<Data> => {
|
||||||
await iterateFields({
|
await iterateFields({
|
||||||
@@ -26,6 +37,8 @@ export const calculateDefaultValues = async ({
|
|||||||
fields,
|
fields,
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
siblingData: data,
|
siblingData: data,
|
||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Data, Field, PayloadRequest, TabAsField, User } from 'payload'
|
import type { Data, Field, PayloadRequest, SelectMode, SelectType, TabAsField, User } from 'payload'
|
||||||
|
|
||||||
import { defaultValuePromise } from './promise.js'
|
import { defaultValuePromise } from './promise.js'
|
||||||
|
|
||||||
@@ -8,6 +8,8 @@ type Args<T> = {
|
|||||||
id?: number | string
|
id?: number | string
|
||||||
locale: string | undefined
|
locale: string | undefined
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
|
select?: SelectType
|
||||||
|
selectMode?: SelectMode
|
||||||
siblingData: Data
|
siblingData: Data
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
@@ -18,6 +20,8 @@ export const iterateFields = async <T>({
|
|||||||
fields,
|
fields,
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
siblingData,
|
siblingData,
|
||||||
user,
|
user,
|
||||||
}: Args<T>): Promise<void> => {
|
}: Args<T>): Promise<void> => {
|
||||||
@@ -31,6 +35,8 @@ export const iterateFields = async <T>({
|
|||||||
field,
|
field,
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
siblingData,
|
siblingData,
|
||||||
user,
|
user,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import type { Data, Field, FlattenedBlock, PayloadRequest, TabAsField, User } from 'payload'
|
import type {
|
||||||
|
Data,
|
||||||
|
Field,
|
||||||
|
FlattenedBlock,
|
||||||
|
PayloadRequest,
|
||||||
|
SelectMode,
|
||||||
|
SelectType,
|
||||||
|
TabAsField,
|
||||||
|
User,
|
||||||
|
} from 'payload'
|
||||||
|
|
||||||
import { getDefaultValue } from 'payload'
|
import { getBlockSelect, getDefaultValue, stripUnselectedFields } from 'payload'
|
||||||
import { fieldAffectsData, tabHasName } from 'payload/shared'
|
import { fieldAffectsData, tabHasName } from 'payload/shared'
|
||||||
|
|
||||||
import { iterateFields } from './iterateFields.js'
|
import { iterateFields } from './iterateFields.js'
|
||||||
@@ -11,6 +20,8 @@ type Args<T> = {
|
|||||||
id?: number | string
|
id?: number | string
|
||||||
locale: string | undefined
|
locale: string | undefined
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
|
select?: SelectType
|
||||||
|
selectMode?: SelectMode
|
||||||
siblingData: Data
|
siblingData: Data
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
@@ -22,9 +33,22 @@ export const defaultValuePromise = async <T>({
|
|||||||
field,
|
field,
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
siblingData,
|
siblingData,
|
||||||
user,
|
user,
|
||||||
}: Args<T>): Promise<void> => {
|
}: Args<T>): Promise<void> => {
|
||||||
|
const shouldContinue = stripUnselectedFields({
|
||||||
|
field,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
|
siblingDoc: siblingData,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!shouldContinue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
if (
|
if (
|
||||||
typeof siblingData[field.name] === 'undefined' &&
|
typeof siblingData[field.name] === 'undefined' &&
|
||||||
@@ -54,6 +78,7 @@ export const defaultValuePromise = async <T>({
|
|||||||
|
|
||||||
if (Array.isArray(rows)) {
|
if (Array.isArray(rows)) {
|
||||||
const promises = []
|
const promises = []
|
||||||
|
const arraySelect = select?.[field.name]
|
||||||
|
|
||||||
rows.forEach((row) => {
|
rows.forEach((row) => {
|
||||||
promises.push(
|
promises.push(
|
||||||
@@ -63,6 +88,8 @@ export const defaultValuePromise = async <T>({
|
|||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select: typeof arraySelect === 'object' ? arraySelect : undefined,
|
||||||
|
selectMode,
|
||||||
siblingData: row,
|
siblingData: row,
|
||||||
user,
|
user,
|
||||||
}),
|
}),
|
||||||
@@ -79,14 +106,22 @@ export const defaultValuePromise = async <T>({
|
|||||||
|
|
||||||
if (Array.isArray(rows)) {
|
if (Array.isArray(rows)) {
|
||||||
const promises = []
|
const promises = []
|
||||||
|
|
||||||
rows.forEach((row) => {
|
rows.forEach((row) => {
|
||||||
const blockTypeToMatch: string = row.blockType
|
const blockTypeToMatch: string = row.blockType
|
||||||
|
|
||||||
const block =
|
const block =
|
||||||
req.payload.blocks[blockTypeToMatch] ??
|
req.payload.blocks[blockTypeToMatch] ??
|
||||||
((field.blockReferences ?? field.blocks).find(
|
((field.blockReferences ?? field.blocks).find(
|
||||||
(blockType) => typeof blockType !== 'string' && blockType.slug === blockTypeToMatch,
|
(blockType) => typeof blockType !== 'string' && blockType.slug === blockTypeToMatch,
|
||||||
) as FlattenedBlock | undefined)
|
) as FlattenedBlock | undefined)
|
||||||
|
|
||||||
|
const { blockSelect, blockSelectMode } = getBlockSelect({
|
||||||
|
block,
|
||||||
|
select: select?.[field.name],
|
||||||
|
selectMode,
|
||||||
|
})
|
||||||
|
|
||||||
if (block) {
|
if (block) {
|
||||||
row.blockType = blockTypeToMatch
|
row.blockType = blockTypeToMatch
|
||||||
|
|
||||||
@@ -97,6 +132,8 @@ export const defaultValuePromise = async <T>({
|
|||||||
fields: block.fields,
|
fields: block.fields,
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select: typeof blockSelect === 'object' ? blockSelect : undefined,
|
||||||
|
selectMode: blockSelectMode,
|
||||||
siblingData: row,
|
siblingData: row,
|
||||||
user,
|
user,
|
||||||
}),
|
}),
|
||||||
@@ -117,6 +154,8 @@ export const defaultValuePromise = async <T>({
|
|||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
siblingData,
|
siblingData,
|
||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
@@ -130,12 +169,16 @@ export const defaultValuePromise = async <T>({
|
|||||||
|
|
||||||
const groupData = siblingData[field.name] as Record<string, unknown>
|
const groupData = siblingData[field.name] as Record<string, unknown>
|
||||||
|
|
||||||
|
const groupSelect = select?.[field.name]
|
||||||
|
|
||||||
await iterateFields({
|
await iterateFields({
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select: typeof groupSelect === 'object' ? groupSelect : undefined,
|
||||||
|
selectMode,
|
||||||
siblingData: groupData,
|
siblingData: groupData,
|
||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
@@ -145,14 +188,24 @@ export const defaultValuePromise = async <T>({
|
|||||||
|
|
||||||
case 'tab': {
|
case 'tab': {
|
||||||
let tabSiblingData
|
let tabSiblingData
|
||||||
if (tabHasName(field)) {
|
|
||||||
|
const isNamedTab = tabHasName(field)
|
||||||
|
|
||||||
|
let tabSelect: SelectType | undefined
|
||||||
|
|
||||||
|
if (isNamedTab) {
|
||||||
if (typeof siblingData[field.name] !== 'object') {
|
if (typeof siblingData[field.name] !== 'object') {
|
||||||
siblingData[field.name] = {}
|
siblingData[field.name] = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
tabSiblingData = siblingData[field.name] as Record<string, unknown>
|
tabSiblingData = siblingData[field.name] as Record<string, unknown>
|
||||||
|
|
||||||
|
if (typeof select?.[field.name] === 'object') {
|
||||||
|
tabSelect = select?.[field.name] as SelectType
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tabSiblingData = siblingData
|
tabSiblingData = siblingData
|
||||||
|
tabSelect = select
|
||||||
}
|
}
|
||||||
|
|
||||||
await iterateFields({
|
await iterateFields({
|
||||||
@@ -161,6 +214,8 @@ export const defaultValuePromise = async <T>({
|
|||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select: tabSelect,
|
||||||
|
selectMode,
|
||||||
siblingData: tabSiblingData,
|
siblingData: tabSiblingData,
|
||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
@@ -175,6 +230,8 @@ export const defaultValuePromise = async <T>({
|
|||||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||||
locale,
|
locale,
|
||||||
req,
|
req,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
siblingData,
|
siblingData,
|
||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import type {
|
|||||||
FormStateWithoutComponents,
|
FormStateWithoutComponents,
|
||||||
PayloadRequest,
|
PayloadRequest,
|
||||||
SanitizedFieldsPermissions,
|
SanitizedFieldsPermissions,
|
||||||
|
SelectMode,
|
||||||
|
SelectType,
|
||||||
} from 'payload'
|
} from 'payload'
|
||||||
|
|
||||||
import type { RenderFieldMethod } from './types.js'
|
import type { RenderFieldMethod } from './types.js'
|
||||||
@@ -70,6 +72,8 @@ type Args = {
|
|||||||
renderFieldFn?: RenderFieldMethod
|
renderFieldFn?: RenderFieldMethod
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
schemaPath: string
|
schemaPath: string
|
||||||
|
select?: SelectType
|
||||||
|
selectMode?: SelectMode
|
||||||
skipValidation?: boolean
|
skipValidation?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +94,8 @@ export const fieldSchemasToFormState = async ({
|
|||||||
renderFieldFn,
|
renderFieldFn,
|
||||||
req,
|
req,
|
||||||
schemaPath,
|
schemaPath,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
skipValidation,
|
skipValidation,
|
||||||
}: Args): Promise<FormState> => {
|
}: Args): Promise<FormState> => {
|
||||||
if (!clientFieldSchemaMap && renderFieldFn) {
|
if (!clientFieldSchemaMap && renderFieldFn) {
|
||||||
@@ -109,6 +115,8 @@ export const fieldSchemasToFormState = async ({
|
|||||||
fields,
|
fields,
|
||||||
locale: req.locale,
|
locale: req.locale,
|
||||||
req,
|
req,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
siblingData: dataWithDefaultValues,
|
siblingData: dataWithDefaultValues,
|
||||||
user: req.user,
|
user: req.user,
|
||||||
})
|
})
|
||||||
@@ -142,6 +150,8 @@ export const fieldSchemasToFormState = async ({
|
|||||||
renderAllFields,
|
renderAllFields,
|
||||||
renderFieldFn,
|
renderFieldFn,
|
||||||
req,
|
req,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
skipValidation,
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ import type {
|
|||||||
FormStateWithoutComponents,
|
FormStateWithoutComponents,
|
||||||
PayloadRequest,
|
PayloadRequest,
|
||||||
SanitizedFieldsPermissions,
|
SanitizedFieldsPermissions,
|
||||||
|
SelectMode,
|
||||||
|
SelectType,
|
||||||
} from 'payload'
|
} from 'payload'
|
||||||
|
|
||||||
|
import { stripUnselectedFields } from 'payload'
|
||||||
import { getFieldPaths } from 'payload/shared'
|
import { getFieldPaths } from 'payload/shared'
|
||||||
|
|
||||||
import type { AddFieldStatePromiseArgs } from './addFieldStatePromise.js'
|
import type { AddFieldStatePromiseArgs } from './addFieldStatePromise.js'
|
||||||
@@ -61,6 +64,8 @@ type Args = {
|
|||||||
renderAllFields: boolean
|
renderAllFields: boolean
|
||||||
renderFieldFn: RenderFieldMethod
|
renderFieldFn: RenderFieldMethod
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
|
select?: SelectType
|
||||||
|
selectMode?: SelectMode
|
||||||
/**
|
/**
|
||||||
* Whether to skip checking the field's condition. @default false
|
* Whether to skip checking the field's condition. @default false
|
||||||
*/
|
*/
|
||||||
@@ -101,6 +106,8 @@ export const iterateFields = async ({
|
|||||||
renderAllFields,
|
renderAllFields,
|
||||||
renderFieldFn: renderFieldFn,
|
renderFieldFn: renderFieldFn,
|
||||||
req,
|
req,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
skipConditionChecks = false,
|
skipConditionChecks = false,
|
||||||
skipValidation = false,
|
skipValidation = false,
|
||||||
state = {},
|
state = {},
|
||||||
@@ -118,6 +125,19 @@ export const iterateFields = async ({
|
|||||||
parentSchemaPath,
|
parentSchemaPath,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (path !== 'id') {
|
||||||
|
const shouldContinue = stripUnselectedFields({
|
||||||
|
field,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
|
siblingDoc: data,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!shouldContinue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const pathSegments = path ? path.split('.') : []
|
const pathSegments = path ? path.split('.') : []
|
||||||
|
|
||||||
if (!skipConditionChecks) {
|
if (!skipConditionChecks) {
|
||||||
@@ -174,6 +194,8 @@ export const iterateFields = async ({
|
|||||||
renderFieldFn,
|
renderFieldFn,
|
||||||
req,
|
req,
|
||||||
schemaPath,
|
schemaPath,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
skipConditionChecks,
|
skipConditionChecks,
|
||||||
skipValidation,
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { BuildFormStateArgs, ClientConfig, ClientUser, ErrorResult, FormState } from 'payload'
|
import type { BuildFormStateArgs, ClientConfig, ClientUser, ErrorResult, FormState } from 'payload'
|
||||||
|
|
||||||
import { formatErrors } from 'payload'
|
import { formatErrors } from 'payload'
|
||||||
import { reduceFieldsToValues } from 'payload/shared'
|
import { getSelectMode, reduceFieldsToValues } from 'payload/shared'
|
||||||
|
|
||||||
import { fieldSchemasToFormState } from '../forms/fieldSchemasToFormState/index.js'
|
import { fieldSchemasToFormState } from '../forms/fieldSchemasToFormState/index.js'
|
||||||
import { renderField } from '../forms/fieldSchemasToFormState/renderField.js'
|
import { renderField } from '../forms/fieldSchemasToFormState/renderField.js'
|
||||||
@@ -117,10 +117,13 @@ export const buildFormState = async (
|
|||||||
},
|
},
|
||||||
returnLockStatus,
|
returnLockStatus,
|
||||||
schemaPath = collectionSlug || globalSlug,
|
schemaPath = collectionSlug || globalSlug,
|
||||||
|
select,
|
||||||
skipValidation,
|
skipValidation,
|
||||||
updateLastEdited,
|
updateLastEdited,
|
||||||
} = args
|
} = args
|
||||||
|
|
||||||
|
const selectMode = select ? getSelectMode(select) : undefined
|
||||||
|
|
||||||
let data = incomingData
|
let data = incomingData
|
||||||
|
|
||||||
if (!collectionSlug && !globalSlug) {
|
if (!collectionSlug && !globalSlug) {
|
||||||
@@ -210,6 +213,8 @@ export const buildFormState = async (
|
|||||||
renderFieldFn: renderField,
|
renderFieldFn: renderField,
|
||||||
req,
|
req,
|
||||||
schemaPath,
|
schemaPath,
|
||||||
|
select,
|
||||||
|
selectMode,
|
||||||
skipValidation,
|
skipValidation,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -855,6 +855,7 @@ describe('General', () => {
|
|||||||
test('should not override un-edited values in bulk edit if it has a defaultValue', async () => {
|
test('should not override un-edited values in bulk edit if it has a defaultValue', async () => {
|
||||||
await deleteAllPosts()
|
await deleteAllPosts()
|
||||||
const post1Title = 'Post'
|
const post1Title = 'Post'
|
||||||
|
|
||||||
const postData = {
|
const postData = {
|
||||||
title: 'Post',
|
title: 'Post',
|
||||||
arrayOfFields: [
|
arrayOfFields: [
|
||||||
@@ -879,6 +880,7 @@ describe('General', () => {
|
|||||||
],
|
],
|
||||||
defaultValueField: 'not the default value',
|
defaultValueField: 'not the default value',
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedPostTitle = `${post1Title} (Updated)`
|
const updatedPostTitle = `${post1Title} (Updated)`
|
||||||
await createPost(postData)
|
await createPost(postData)
|
||||||
await page.goto(postsUrl.list)
|
await page.goto(postsUrl.list)
|
||||||
@@ -890,10 +892,8 @@ describe('General', () => {
|
|||||||
hasText: exactText('Title'),
|
hasText: exactText('Title'),
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(titleOption).toBeVisible()
|
|
||||||
await titleOption.click()
|
await titleOption.click()
|
||||||
const titleInput = page.locator('#field-title')
|
const titleInput = page.locator('#field-title')
|
||||||
await expect(titleInput).toBeVisible()
|
|
||||||
await titleInput.fill(updatedPostTitle)
|
await titleInput.fill(updatedPostTitle)
|
||||||
await page.locator('.form-submit button[type="submit"].edit-many__publish').click()
|
await page.locator('.form-submit button[type="submit"].edit-many__publish').click()
|
||||||
|
|
||||||
|
|||||||
@@ -269,10 +269,6 @@ export interface Post {
|
|||||||
* This is a very long description that takes many characters to complete and hopefully will wrap instead of push the sidebar open, lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum voluptates. Quisquam, voluptatum voluptates.
|
* This is a very long description that takes many characters to complete and hopefully will wrap instead of push the sidebar open, lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum voluptates. Quisquam, voluptatum voluptates.
|
||||||
*/
|
*/
|
||||||
sidebarField?: string | null;
|
sidebarField?: string | null;
|
||||||
/**
|
|
||||||
* This field should only validate on submit. Try typing "Not allowed" and submitting the form.
|
|
||||||
*/
|
|
||||||
validateUsingEvent?: string | null;
|
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
_status?: ('draft' | 'published') | null;
|
_status?: ('draft' | 'published') | null;
|
||||||
@@ -719,7 +715,6 @@ export interface PostsSelect<T extends boolean = true> {
|
|||||||
disableListColumnText?: T;
|
disableListColumnText?: T;
|
||||||
disableListFilterText?: T;
|
disableListFilterText?: T;
|
||||||
sidebarField?: T;
|
sidebarField?: T;
|
||||||
validateUsingEvent?: T;
|
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
_status?: T;
|
_status?: T;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { Payload } from 'payload'
|
import type { Payload, User } from 'payload'
|
||||||
|
|
||||||
|
import { buildFormState } from '@payloadcms/ui/utilities/buildFormState'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { createLocalReq } from 'payload'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||||
@@ -12,6 +14,7 @@ import { postsSlug } from './collections/Posts/index.js'
|
|||||||
let payload: Payload
|
let payload: Payload
|
||||||
let token: string
|
let token: string
|
||||||
let restClient: NextRESTClient
|
let restClient: NextRESTClient
|
||||||
|
let user: User
|
||||||
|
|
||||||
const { email, password } = devUser
|
const { email, password } = devUser
|
||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
@@ -22,8 +25,7 @@ describe('Form State', () => {
|
|||||||
// Boilerplate test setup/teardown
|
// Boilerplate test setup/teardown
|
||||||
// --__--__--__--__--__--__--__--__--__
|
// --__--__--__--__--__--__--__--__--__
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const initialized = await initPayloadInt(dirname)
|
;({ payload, restClient } = await initPayloadInt(dirname))
|
||||||
;({ payload, restClient } = initialized)
|
|
||||||
|
|
||||||
const data = await restClient
|
const data = await restClient
|
||||||
.POST('/users/login', {
|
.POST('/users/login', {
|
||||||
@@ -35,6 +37,7 @@ describe('Form State', () => {
|
|||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
|
|
||||||
token = data.token
|
token = data.token
|
||||||
|
user = data.user
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -43,5 +46,97 @@ describe('Form State', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it.todo('should execute form state endpoint')
|
it('should build entire form state', async () => {
|
||||||
|
const req = await createLocalReq({ user }, payload)
|
||||||
|
|
||||||
|
const postData = await payload.create({
|
||||||
|
collection: postsSlug,
|
||||||
|
data: {
|
||||||
|
title: 'Test Post',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { state } = await buildFormState({
|
||||||
|
id: postData.id,
|
||||||
|
collectionSlug: postsSlug,
|
||||||
|
data: postData,
|
||||||
|
docPermissions: {
|
||||||
|
create: true,
|
||||||
|
delete: true,
|
||||||
|
fields: true,
|
||||||
|
read: true,
|
||||||
|
readVersions: true,
|
||||||
|
update: true,
|
||||||
|
},
|
||||||
|
docPreferences: {
|
||||||
|
fields: {},
|
||||||
|
},
|
||||||
|
documentFormState: undefined,
|
||||||
|
operation: 'update',
|
||||||
|
renderAllFields: false,
|
||||||
|
req,
|
||||||
|
schemaPath: postsSlug,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(state).toMatchObject({
|
||||||
|
title: {
|
||||||
|
value: postData.title,
|
||||||
|
initialValue: postData.title,
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
value: postData.updatedAt,
|
||||||
|
initialValue: postData.updatedAt,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
value: postData.createdAt,
|
||||||
|
initialValue: postData.createdAt,
|
||||||
|
},
|
||||||
|
renderTracker: {},
|
||||||
|
validateUsingEvent: {},
|
||||||
|
blocks: {
|
||||||
|
initialValue: 0,
|
||||||
|
requiresRender: false,
|
||||||
|
rows: [],
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should use `select` to build partial form state with only specified fields', async () => {
|
||||||
|
const req = await createLocalReq({ user }, payload)
|
||||||
|
|
||||||
|
const postData = await payload.create({
|
||||||
|
collection: postsSlug,
|
||||||
|
data: {
|
||||||
|
title: 'Test Post',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { state } = await buildFormState({
|
||||||
|
id: postData.id,
|
||||||
|
collectionSlug: postsSlug,
|
||||||
|
data: postData,
|
||||||
|
docPermissions: undefined,
|
||||||
|
docPreferences: {
|
||||||
|
fields: {},
|
||||||
|
},
|
||||||
|
documentFormState: undefined,
|
||||||
|
operation: 'update',
|
||||||
|
renderAllFields: false,
|
||||||
|
req,
|
||||||
|
schemaPath: postsSlug,
|
||||||
|
select: {
|
||||||
|
title: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(state).toStrictEqual({
|
||||||
|
title: {
|
||||||
|
value: postData.title,
|
||||||
|
initialValue: postData.title,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it.todo('should skip validation if specified')
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user