Compare commits
5 Commits
v3.45.0
...
fix/compon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10c5a37411 | ||
|
|
b2bd7f2779 | ||
|
|
8ee6332a86 | ||
|
|
a93bda1040 | ||
|
|
106fb0f2de |
@@ -46,8 +46,16 @@ export type FieldState = {
|
||||
fieldSchema?: Field
|
||||
filterOptions?: FilterOptionsResult
|
||||
initialValue?: unknown
|
||||
/**
|
||||
* The path of the field when its custom components were last rendered.
|
||||
* This is used to denote if a field has been rendered, and if so,
|
||||
* what path it was rendered under last.
|
||||
*
|
||||
* If this path is undefined, or, if it is different
|
||||
* from the current path of a given field, the field's components will be re-rendered.
|
||||
*/
|
||||
lastRenderedPath?: string
|
||||
passesCondition?: boolean
|
||||
requiresRender?: boolean
|
||||
rows?: Row[]
|
||||
valid?: boolean
|
||||
validate?: Validate
|
||||
|
||||
140
packages/payload/src/admin/forms/test.json
Normal file
140
packages/payload/src/admin/forms/test.json
Normal file
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"lockedState": {
|
||||
"isLocked": true,
|
||||
"lastEditedAt": "2025-03-31T21:31:01.419Z",
|
||||
"user": {
|
||||
"createdAt": "2025-03-31T21:30:11.215Z",
|
||||
"updatedAt": "2025-03-31T21:30:11.215Z",
|
||||
"email": "dev@payloadcms.com",
|
||||
"id": "67eb09635d15fc77b1da4823",
|
||||
"loginAttempts": 0
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"title": { "value": "example post", "initialValue": "example post" },
|
||||
"renderTracker": {},
|
||||
"validateUsingEvent": {},
|
||||
"array.0.id": {
|
||||
"value": "67eb09903a4c5f0cce051442",
|
||||
"initialValue": "67eb09903a4c5f0cce051442",
|
||||
"lastRenderedPath": "array.0.id"
|
||||
},
|
||||
"array.0.richText": {},
|
||||
"array.1.id": {
|
||||
"value": "67eb09923a4c5f0cce051444",
|
||||
"initialValue": "67eb09923a4c5f0cce051444"
|
||||
},
|
||||
"array.1.richText": {},
|
||||
"array.2.id": {
|
||||
"value": "67eb0a7f3a4c5f0cce051446",
|
||||
"initialValue": "67eb0a7f3a4c5f0cce051446",
|
||||
"lastRenderedPath": "array.2.id"
|
||||
},
|
||||
"array.2.richText": {
|
||||
"lastRenderedPath": "array.2.richText",
|
||||
"customComponents": {
|
||||
"Field": [
|
||||
"$",
|
||||
"$L2",
|
||||
null,
|
||||
{ "path": "array.2.richText", "children": "$L3" },
|
||||
null,
|
||||
[
|
||||
[
|
||||
"renderField",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx",
|
||||
156,
|
||||
128
|
||||
],
|
||||
[
|
||||
"addFieldStatePromise",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
584,
|
||||
9
|
||||
],
|
||||
[
|
||||
"eval",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
51,
|
||||
101
|
||||
],
|
||||
[
|
||||
"iterateFields",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
15,
|
||||
12
|
||||
],
|
||||
[
|
||||
"arrayValue.reduce.promises",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
125,
|
||||
107
|
||||
],
|
||||
[
|
||||
"addFieldStatePromise",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
112,
|
||||
59
|
||||
],
|
||||
[
|
||||
"eval",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
51,
|
||||
101
|
||||
],
|
||||
[
|
||||
"iterateFields",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
15,
|
||||
12
|
||||
],
|
||||
[
|
||||
"fieldSchemasToFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/index.tsx",
|
||||
36,
|
||||
79
|
||||
],
|
||||
[
|
||||
"async buildFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
130,
|
||||
29
|
||||
],
|
||||
[
|
||||
"async buildFormStateHandler",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
55,
|
||||
21
|
||||
],
|
||||
[
|
||||
"async Server.<anonymous>",
|
||||
"file:///Users/jacobfletcher/dev/payload/payload/test/dev.ts",
|
||||
1,
|
||||
2848
|
||||
]
|
||||
],
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"updatedAt": {
|
||||
"value": "2025-03-31T21:30:11.221Z",
|
||||
"initialValue": "2025-03-31T21:30:11.221Z"
|
||||
},
|
||||
"createdAt": {
|
||||
"value": "2025-03-31T21:30:11.221Z",
|
||||
"initialValue": "2025-03-31T21:30:11.221Z"
|
||||
},
|
||||
"blocks": { "value": 0, "initialValue": 0, "rows": [] },
|
||||
"array": {
|
||||
"rows": [
|
||||
{ "id": "67eb09903a4c5f0cce051442" },
|
||||
{ "id": "67eb09923a4c5f0cce051444" },
|
||||
{ "id": "67eb0a7f3a4c5f0cce051446" }
|
||||
],
|
||||
"value": 3,
|
||||
"initialValue": 3,
|
||||
"disableFormData": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,6 @@ export const uploadValidation = (
|
||||
const result = await fieldSchemasToFormState({
|
||||
id,
|
||||
collectionSlug: node.relationTo,
|
||||
|
||||
data: node?.fields ?? {},
|
||||
documentData: data,
|
||||
fields: collection.fields,
|
||||
|
||||
@@ -53,14 +53,12 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
[`${path}.${rowIndex}.id`]: {
|
||||
initialValue: newRow.id,
|
||||
passesCondition: true,
|
||||
requiresRender: true,
|
||||
valid: true,
|
||||
value: newRow.id,
|
||||
},
|
||||
[path]: {
|
||||
...state[path],
|
||||
disableFormData: true,
|
||||
requiresRender: true,
|
||||
rows: withNewRow,
|
||||
value: siblingRows.length,
|
||||
},
|
||||
@@ -169,7 +167,6 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
[path]: {
|
||||
...state[path],
|
||||
disableFormData: true,
|
||||
requiresRender: true,
|
||||
rows: rowsMetadata,
|
||||
value: rows.length,
|
||||
},
|
||||
@@ -198,7 +195,6 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
...flattenRows(path, topLevelRows),
|
||||
[path]: {
|
||||
...state[path],
|
||||
requiresRender: true,
|
||||
rows: rowsWithinField,
|
||||
},
|
||||
}
|
||||
@@ -250,7 +246,6 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
[path]: {
|
||||
...state[path],
|
||||
disableFormData: rows.length > 0,
|
||||
requiresRender: true,
|
||||
rows: rowsMetadata,
|
||||
value: rows.length,
|
||||
},
|
||||
|
||||
@@ -596,7 +596,7 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
const newRows: unknown[] = getDataByPath(path) || []
|
||||
const rowIndex = rowIndexArg === undefined ? newRows.length : rowIndexArg
|
||||
|
||||
// dispatch ADD_ROW that sets requiresRender: true and adds a blank row to local form state.
|
||||
// dispatch ADD_ROW adds a blank row to local form state.
|
||||
// This performs no form state request, as the debounced onChange effect will do that for us.
|
||||
dispatchFields({
|
||||
type: 'ADD_ROW',
|
||||
|
||||
@@ -34,7 +34,7 @@ export const mergeServerFormState = ({
|
||||
'errorPaths',
|
||||
'rows',
|
||||
'customComponents',
|
||||
'requiresRender',
|
||||
'lastRenderedPath',
|
||||
]
|
||||
|
||||
if (acceptValues) {
|
||||
|
||||
@@ -148,7 +148,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
)
|
||||
}
|
||||
|
||||
const requiresRender = renderAllFields || previousFormState?.[path]?.requiresRender
|
||||
const lastRenderedPath = previousFormState?.[path]?.lastRenderedPath
|
||||
const requiresRender = renderAllFields || !lastRenderedPath || lastRenderedPath !== path
|
||||
|
||||
let fieldPermissions: SanitizedFieldPermissions = true
|
||||
|
||||
@@ -299,7 +300,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
fieldPermissions === true ? fieldPermissions : fieldPermissions?.fields || {},
|
||||
preferences,
|
||||
previousFormState,
|
||||
renderAllFields: requiresRender,
|
||||
renderAllFields,
|
||||
renderFieldFn,
|
||||
req,
|
||||
select: typeof arraySelect === 'object' ? arraySelect : undefined,
|
||||
@@ -356,10 +357,6 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
fieldState.rows = rows
|
||||
}
|
||||
|
||||
// Unset requiresRender
|
||||
// so it will be removed from form state
|
||||
fieldState.requiresRender = false
|
||||
|
||||
// Add values to field state
|
||||
if (data[field.name] !== null) {
|
||||
fieldState.value = forceFullValue ? arrayValue : arrayValue.length
|
||||
@@ -483,7 +480,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
: parentPermissions?.[field.name]?.blocks?.[block.slug]?.fields || {},
|
||||
preferences,
|
||||
previousFormState,
|
||||
renderAllFields: requiresRender,
|
||||
renderAllFields,
|
||||
renderFieldFn,
|
||||
req,
|
||||
select: typeof blockSelect === 'object' ? blockSelect : undefined,
|
||||
@@ -536,10 +533,6 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
|
||||
fieldState.rows = rowMetadata
|
||||
|
||||
// Unset requiresRender
|
||||
// so it will be removed from form state
|
||||
fieldState.requiresRender = false
|
||||
|
||||
// Add field to state
|
||||
if (!omitParents && (!filter || filter(args))) {
|
||||
state[path] = fieldState
|
||||
|
||||
@@ -53,6 +53,11 @@ export const renderField: RenderFieldMethod = ({
|
||||
importMap: req.payload.importMap,
|
||||
})
|
||||
|
||||
/**
|
||||
* Set the lastRenderedPath equal to the new path of the field
|
||||
*/
|
||||
fieldState.lastRenderedPath = path
|
||||
|
||||
if (fieldIsHiddenOrDisabled(clientField)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,61 +6,6 @@
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported timezones in IANA format.
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "supportedTimezones".
|
||||
*/
|
||||
export type SupportedTimezones =
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Brisbane'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji'
|
||||
| 'America/Monterrey';
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "BlockColumns".
|
||||
@@ -82,11 +27,6 @@ export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
blocks: {
|
||||
ConfigBlockTest: ConfigBlockTest;
|
||||
localizedTextReference: LocalizedTextReference;
|
||||
localizedTextReference2: LocalizedTextReference2;
|
||||
};
|
||||
collections: {
|
||||
'lexical-fields': LexicalField;
|
||||
'lexical-migrate-fields': LexicalMigrateField;
|
||||
@@ -216,36 +156,6 @@ export interface UserAuthOperations {
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "ConfigBlockTest".
|
||||
*/
|
||||
export interface ConfigBlockTest {
|
||||
deduplicatedText?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'ConfigBlockTest';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localizedTextReference".
|
||||
*/
|
||||
export interface LocalizedTextReference {
|
||||
text?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'localizedTextReference';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localizedTextReference2".
|
||||
*/
|
||||
export interface LocalizedTextReference2 {
|
||||
text?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'localizedTextReference2';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "lexical-fields".
|
||||
@@ -844,10 +754,10 @@ export interface BlockField {
|
||||
blockType: 'text';
|
||||
}[]
|
||||
| null;
|
||||
deduplicatedBlocks?: ConfigBlockTest[] | null;
|
||||
deduplicatedBlocks2?: ConfigBlockTest[] | null;
|
||||
localizedReferencesLocalizedBlock?: LocalizedTextReference[] | null;
|
||||
localizedReferences?: LocalizedTextReference2[] | null;
|
||||
deduplicatedBlocks?: unknown[] | null;
|
||||
deduplicatedBlocks2?: unknown[] | null;
|
||||
localizedReferencesLocalizedBlock?: unknown[] | null;
|
||||
localizedReferences?: unknown[] | null;
|
||||
/**
|
||||
* The purpose of this field is to test Block groups.
|
||||
*/
|
||||
@@ -1249,16 +1159,166 @@ export interface DateField {
|
||||
dayAndTime?: string | null;
|
||||
monthOnly?: string | null;
|
||||
defaultWithTimezone?: string | null;
|
||||
defaultWithTimezone_tz?: SupportedTimezones;
|
||||
defaultWithTimezone_tz?:
|
||||
| (
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Brisbane'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji'
|
||||
| 'America/Monterrey'
|
||||
)
|
||||
| null;
|
||||
/**
|
||||
* This date here should be required.
|
||||
*/
|
||||
dayAndTimeWithTimezone: string;
|
||||
dayAndTimeWithTimezone_tz: SupportedTimezones;
|
||||
dayAndTimeWithTimezone_tz:
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Brisbane'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji'
|
||||
| 'America/Monterrey';
|
||||
timezoneBlocks?:
|
||||
| {
|
||||
dayAndTime?: string | null;
|
||||
dayAndTime_tz?: SupportedTimezones;
|
||||
dayAndTime_tz?:
|
||||
| (
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Brisbane'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji'
|
||||
| 'America/Monterrey'
|
||||
)
|
||||
| null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'dateBlock';
|
||||
@@ -1267,7 +1327,58 @@ export interface DateField {
|
||||
timezoneArray?:
|
||||
| {
|
||||
dayAndTime?: string | null;
|
||||
dayAndTime_tz?: SupportedTimezones;
|
||||
dayAndTime_tz?:
|
||||
| (
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Brisbane'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji'
|
||||
| 'America/Monterrey'
|
||||
)
|
||||
| null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { BrowserContext, Page } from '@playwright/test'
|
||||
import type { PayloadTestSDK } from 'helpers/sdk/index.js'
|
||||
import type { FormState } from 'payload'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { addBlock } from 'helpers/e2e/addBlock.js'
|
||||
import { assertNetworkRequests } from 'helpers/e2e/assertNetworkRequests.js'
|
||||
import { assertResponseBody } from 'helpers/e2e/assertResponseBody.js'
|
||||
import * as path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
@@ -180,7 +182,7 @@ test.describe('Form State', () => {
|
||||
await cdpSession.detach()
|
||||
})
|
||||
|
||||
test('sequentially queued tasks not cause nested custom components to disappear', async () => {
|
||||
test('should not cause nested custom fields to disappear when adding a row and then editing a field', async () => {
|
||||
await page.goto(postsUrl.create)
|
||||
const field = page.locator('#field-title')
|
||||
await field.fill('Test')
|
||||
@@ -196,9 +198,9 @@ test.describe('Form State', () => {
|
||||
postsUrl.create,
|
||||
async () => {
|
||||
await page.locator('#field-array .array-field__add-row').click()
|
||||
|
||||
await page.locator('#field-title').fill('Title 2')
|
||||
|
||||
// use `waitForSelector` to ensure the element doesn't appear and then disappear
|
||||
// eslint-disable-next-line playwright/no-wait-for-selector
|
||||
await page.waitForSelector('#field-array #array-row-0 .field-type.rich-text-lexical', {
|
||||
timeout: TEST_TIMEOUT,
|
||||
@@ -223,6 +225,80 @@ test.describe('Form State', () => {
|
||||
|
||||
await cdpSession.detach()
|
||||
})
|
||||
|
||||
test('should not cause nested custom fields to disappear when adding rows back-to-back', async () => {
|
||||
await page.goto(postsUrl.create)
|
||||
const field = page.locator('#field-title')
|
||||
await field.fill('Test')
|
||||
|
||||
const cdpSession = await throttleTest({
|
||||
page,
|
||||
context,
|
||||
delay: 'Slow 3G',
|
||||
})
|
||||
|
||||
// Add two rows quickly
|
||||
// Test that the rich text fields within the rows do not disappear
|
||||
await assertNetworkRequests(
|
||||
page,
|
||||
postsUrl.create,
|
||||
async () => {
|
||||
// Ensure `requiresRender` is `true` is set for the first request
|
||||
await assertResponseBody<{ state: FormState }>(page, {
|
||||
action: page.locator('#field-array .array-field__add-row').click(),
|
||||
url: '/admin/collections/posts/create',
|
||||
// expect: (body) => body[0]?.args?.formState?.array?.requiresRender === true,
|
||||
})
|
||||
|
||||
// Ensure `requiresRender` is `true` is set for the second request
|
||||
await assertResponseBody<{ state: FormState }>(page, {
|
||||
action: page.locator('#field-array .array-field__add-row').click(),
|
||||
url: '/admin/collections/posts/create',
|
||||
// expect: (body) => body[0]?.args?.formState?.array?.requiresRender === true,
|
||||
})
|
||||
|
||||
// use `waitForSelector` to ensure the element doesn't appear and then disappear
|
||||
// eslint-disable-next-line playwright/no-wait-for-selector
|
||||
await page.waitForSelector('#field-array #array-row-0 .field-type.rich-text-lexical', {
|
||||
timeout: TEST_TIMEOUT,
|
||||
})
|
||||
|
||||
// use `waitForSelector` to ensure the element doesn't appear and then disappear
|
||||
// eslint-disable-next-line playwright/no-wait-for-selector
|
||||
await page.waitForSelector('#field-array #array-row-1 .field-type.rich-text-lexical', {
|
||||
timeout: TEST_TIMEOUT,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.locator('#field-array #array-row-0 .field-type.rich-text-lexical'),
|
||||
).toBeVisible()
|
||||
|
||||
await expect(
|
||||
page.locator('#field-array #array-row-1 .field-type.rich-text-lexical'),
|
||||
).toBeVisible()
|
||||
},
|
||||
{
|
||||
allowedNumberOfRequests: 2,
|
||||
timeout: 10000,
|
||||
},
|
||||
)
|
||||
|
||||
// Ensure `requiresRender` is `false` for the third request
|
||||
await assertResponseBody<{ state: FormState }>(page, {
|
||||
action: page.locator('#field-title').fill('Title 2'),
|
||||
url: '/admin/collections/posts/create',
|
||||
// expect: (body) => body[0]?.args?.formState?.array?.requiresRender === false,
|
||||
})
|
||||
|
||||
await cdpSession.send('Network.emulateNetworkConditions', {
|
||||
offline: false,
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
await cdpSession.detach()
|
||||
})
|
||||
})
|
||||
|
||||
async function createPost(overrides?: Partial<Post>): Promise<Post> {
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('Form State', () => {
|
||||
// Boilerplate test setup/teardown
|
||||
// --__--__--__--__--__--__--__--__--__
|
||||
beforeAll(async () => {
|
||||
;({ payload, restClient } = await initPayloadInt(dirname))
|
||||
;({ payload, restClient } = await initPayloadInt(dirname, undefined, true, false))
|
||||
|
||||
const data = await restClient
|
||||
.POST('/users/login', {
|
||||
@@ -138,5 +138,163 @@ describe('Form State', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.todo('should skip validation if specified')
|
||||
it('should not unnecessarily re-render custom components when adding a row and then editing a field', async () => {
|
||||
const req = await createLocalReq({ user }, payload)
|
||||
|
||||
const { state: stateWithRow } = await buildFormState({
|
||||
collectionSlug: postsSlug,
|
||||
formState: {
|
||||
array: {
|
||||
rows: [
|
||||
{
|
||||
id: '123',
|
||||
},
|
||||
],
|
||||
},
|
||||
'array.0.id': {
|
||||
value: '123',
|
||||
initialValue: '123',
|
||||
},
|
||||
},
|
||||
docPermissions: undefined,
|
||||
docPreferences: {
|
||||
fields: {},
|
||||
},
|
||||
documentFormState: undefined,
|
||||
operation: 'update',
|
||||
renderAllFields: false,
|
||||
req,
|
||||
schemaPath: postsSlug,
|
||||
})
|
||||
|
||||
// Ensure that row 1 returns with rendered components
|
||||
expect(stateWithRow['array']?.lastRenderedPath).toStrictEqual('array')
|
||||
expect(stateWithRow['array.0.richText']?.lastRenderedPath).toStrictEqual('array.0.richText')
|
||||
expect(stateWithRow['array.0.richText']?.customComponents?.Field).toBeDefined()
|
||||
|
||||
const { state: stateWithTitle } = await buildFormState({
|
||||
collectionSlug: postsSlug,
|
||||
formState: {
|
||||
title: {
|
||||
value: 'Test Post',
|
||||
initialValue: 'Test Post',
|
||||
},
|
||||
array: {
|
||||
rows: [
|
||||
{
|
||||
id: '123',
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
},
|
||||
],
|
||||
},
|
||||
'array.0.id': {
|
||||
value: '123',
|
||||
initialValue: '123',
|
||||
},
|
||||
'array.0.richText': {
|
||||
lastRenderedPath: 'array.0.richText',
|
||||
},
|
||||
'array.1.id': {
|
||||
value: '456',
|
||||
initialValue: '456',
|
||||
},
|
||||
},
|
||||
docPermissions: undefined,
|
||||
docPreferences: {
|
||||
fields: {},
|
||||
},
|
||||
documentFormState: undefined,
|
||||
operation: 'update',
|
||||
renderAllFields: false,
|
||||
req,
|
||||
schemaPath: postsSlug,
|
||||
})
|
||||
|
||||
// Ensure that row 1 DOES NOT return with rendered components
|
||||
expect(stateWithTitle['array']?.lastRenderedPath).toStrictEqual('array')
|
||||
expect(stateWithTitle['array.0.richText']).not.toHaveProperty('lastRenderedPath')
|
||||
expect(stateWithTitle['array.0.richText']).not.toHaveProperty('customComponents')
|
||||
})
|
||||
|
||||
it('should not unnecessarily re-render custom components when adding rows back-to-back', async () => {
|
||||
const req = await createLocalReq({ user }, payload)
|
||||
|
||||
const { state: stateWith1Row } = await buildFormState({
|
||||
collectionSlug: postsSlug,
|
||||
formState: {
|
||||
array: {
|
||||
rows: [
|
||||
{
|
||||
id: '123',
|
||||
},
|
||||
],
|
||||
},
|
||||
'array.0.id': {
|
||||
value: '123',
|
||||
initialValue: '123',
|
||||
},
|
||||
},
|
||||
docPermissions: undefined,
|
||||
docPreferences: {
|
||||
fields: {},
|
||||
},
|
||||
documentFormState: undefined,
|
||||
operation: 'update',
|
||||
renderAllFields: false,
|
||||
req,
|
||||
schemaPath: postsSlug,
|
||||
})
|
||||
|
||||
// Ensure that row 1 returns rendered components
|
||||
expect(stateWith1Row['array']?.lastRenderedPath).toStrictEqual('array')
|
||||
expect(stateWith1Row['array.0.richText']?.lastRenderedPath).toStrictEqual('array.0.richText')
|
||||
expect(stateWith1Row['array.0.richText']?.customComponents?.Field).toBeDefined()
|
||||
|
||||
const { state: stateWith2Rows } = await buildFormState({
|
||||
collectionSlug: postsSlug,
|
||||
formState: {
|
||||
array: {
|
||||
lastRenderedPath: 'array',
|
||||
rows: [
|
||||
{
|
||||
id: '123',
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
},
|
||||
],
|
||||
},
|
||||
'array.0.id': {
|
||||
value: '123',
|
||||
initialValue: '123',
|
||||
},
|
||||
'array.0.richText': {
|
||||
lastRenderedPath: 'array.0.richText',
|
||||
},
|
||||
'array.1.id': {
|
||||
value: '456',
|
||||
initialValue: '456',
|
||||
},
|
||||
},
|
||||
docPermissions: undefined,
|
||||
docPreferences: {
|
||||
fields: {},
|
||||
},
|
||||
documentFormState: undefined,
|
||||
operation: 'update',
|
||||
renderAllFields: false,
|
||||
req,
|
||||
schemaPath: postsSlug,
|
||||
})
|
||||
|
||||
// Ensure that row 1 DOES NOT return rendered components
|
||||
// But row 2 DOES return rendered components
|
||||
expect(stateWith2Rows['array']?.lastRenderedPath).toStrictEqual('array')
|
||||
expect(stateWith2Rows['array.0.richText']).not.toHaveProperty('lastRenderedPath')
|
||||
expect(stateWith2Rows['array.0.richText']).not.toHaveProperty('customComponents')
|
||||
expect(stateWith2Rows['array.1.richText']?.lastRenderedPath).toStrictEqual('array.1.richText')
|
||||
expect(stateWith2Rows['array.1.richText']?.customComponents?.Field).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
466
test/form-state/test2.json
Normal file
466
test/form-state/test2.json
Normal file
@@ -0,0 +1,466 @@
|
||||
{
|
||||
"lockedState": {
|
||||
"isLocked": true,
|
||||
"lastEditedAt": "2025-03-31T21:44:00.115Z",
|
||||
"user": {
|
||||
"createdAt": "2025-03-31T21:30:11.215Z",
|
||||
"updatedAt": "2025-03-31T21:30:11.215Z",
|
||||
"email": "dev@payloadcms.com",
|
||||
"id": "67eb09635d15fc77b1da4823",
|
||||
"loginAttempts": 0,
|
||||
"collection": "users",
|
||||
"_strategy": "local-jwt"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"title": {
|
||||
"value": "example post",
|
||||
"initialValue": "example post",
|
||||
"lastRenderedPath": "title"
|
||||
},
|
||||
"renderTracker": {
|
||||
"lastRenderedPath": "renderTracker",
|
||||
"customComponents": {
|
||||
"Field": [
|
||||
"$",
|
||||
"$L2",
|
||||
null,
|
||||
{
|
||||
"path": "renderTracker",
|
||||
"children": [
|
||||
"$",
|
||||
"$L3",
|
||||
"field.admin.components.Field",
|
||||
{
|
||||
"field": {
|
||||
"name": "renderTracker",
|
||||
"type": "text",
|
||||
"admin": {},
|
||||
"label": "Render Tracker"
|
||||
},
|
||||
"path": "renderTracker",
|
||||
"permissions": true,
|
||||
"readOnly": false,
|
||||
"schemaPath": "posts.renderTracker"
|
||||
},
|
||||
null,
|
||||
[
|
||||
[
|
||||
"RenderServerComponent",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/elements/RenderServerComponent/index.tsx",
|
||||
59,
|
||||
95
|
||||
],
|
||||
[
|
||||
"renderField",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx",
|
||||
259,
|
||||
126
|
||||
],
|
||||
[
|
||||
"addFieldStatePromise",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
584,
|
||||
9
|
||||
],
|
||||
[
|
||||
"eval",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
51,
|
||||
101
|
||||
],
|
||||
[
|
||||
"iterateFields",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
15,
|
||||
12
|
||||
],
|
||||
[
|
||||
"fieldSchemasToFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/index.tsx",
|
||||
36,
|
||||
79
|
||||
],
|
||||
[
|
||||
"async buildFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
130,
|
||||
29
|
||||
],
|
||||
[
|
||||
"async buildFormStateHandler",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
55,
|
||||
21
|
||||
],
|
||||
[
|
||||
"async Server.<anonymous>",
|
||||
"file:///Users/jacobfletcher/dev/payload/payload/test/dev.ts",
|
||||
1,
|
||||
2848
|
||||
]
|
||||
],
|
||||
1
|
||||
]
|
||||
},
|
||||
null,
|
||||
[
|
||||
[
|
||||
"renderField",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx",
|
||||
257,
|
||||
128
|
||||
],
|
||||
[
|
||||
"addFieldStatePromise",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
584,
|
||||
9
|
||||
],
|
||||
[
|
||||
"eval",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
51,
|
||||
101
|
||||
],
|
||||
[
|
||||
"iterateFields",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
15,
|
||||
12
|
||||
],
|
||||
[
|
||||
"fieldSchemasToFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/index.tsx",
|
||||
36,
|
||||
79
|
||||
],
|
||||
[
|
||||
"async buildFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
130,
|
||||
29
|
||||
],
|
||||
[
|
||||
"async buildFormStateHandler",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
55,
|
||||
21
|
||||
],
|
||||
[
|
||||
"async Server.<anonymous>",
|
||||
"file:///Users/jacobfletcher/dev/payload/payload/test/dev.ts",
|
||||
1,
|
||||
2848
|
||||
]
|
||||
],
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"validateUsingEvent": { "lastRenderedPath": "validateUsingEvent" },
|
||||
"array.0.id": {
|
||||
"value": "67eb09903a4c5f0cce051442",
|
||||
"initialValue": "67eb09903a4c5f0cce051442"
|
||||
},
|
||||
"array.0.richText": {
|
||||
"lastRenderedPath": "array.0.richText",
|
||||
"customComponents": {
|
||||
"Field": [
|
||||
"$",
|
||||
"$L2",
|
||||
null,
|
||||
{ "path": "array.0.richText", "children": "$L4" },
|
||||
null,
|
||||
[
|
||||
[
|
||||
"renderField",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx",
|
||||
156,
|
||||
128
|
||||
],
|
||||
[
|
||||
"addFieldStatePromise",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
584,
|
||||
9
|
||||
],
|
||||
[
|
||||
"eval",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
51,
|
||||
101
|
||||
],
|
||||
[
|
||||
"iterateFields",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
15,
|
||||
12
|
||||
],
|
||||
[
|
||||
"arrayValue.reduce.promises",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
125,
|
||||
107
|
||||
],
|
||||
[
|
||||
"addFieldStatePromise",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
112,
|
||||
59
|
||||
],
|
||||
[
|
||||
"eval",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
51,
|
||||
101
|
||||
],
|
||||
[
|
||||
"iterateFields",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
15,
|
||||
12
|
||||
],
|
||||
[
|
||||
"fieldSchemasToFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/index.tsx",
|
||||
36,
|
||||
79
|
||||
],
|
||||
[
|
||||
"async buildFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
130,
|
||||
29
|
||||
],
|
||||
[
|
||||
"async buildFormStateHandler",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
55,
|
||||
21
|
||||
],
|
||||
[
|
||||
"async Server.<anonymous>",
|
||||
"file:///Users/jacobfletcher/dev/payload/payload/test/dev.ts",
|
||||
1,
|
||||
2848
|
||||
]
|
||||
],
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"array.1.id": {
|
||||
"value": "67eb09923a4c5f0cce051444",
|
||||
"initialValue": "67eb09923a4c5f0cce051444",
|
||||
"lastRenderedPath": "array.1.id"
|
||||
},
|
||||
"array.1.richText": {
|
||||
"lastRenderedPath": "array.1.richText",
|
||||
"customComponents": {
|
||||
"Field": [
|
||||
"$",
|
||||
"$L2",
|
||||
null,
|
||||
{ "path": "array.1.richText", "children": "$L6" },
|
||||
null,
|
||||
[
|
||||
[
|
||||
"renderField",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx",
|
||||
156,
|
||||
128
|
||||
],
|
||||
[
|
||||
"addFieldStatePromise",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
584,
|
||||
9
|
||||
],
|
||||
[
|
||||
"eval",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
51,
|
||||
101
|
||||
],
|
||||
[
|
||||
"iterateFields",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
15,
|
||||
12
|
||||
],
|
||||
[
|
||||
"arrayValue.reduce.promises",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
125,
|
||||
107
|
||||
],
|
||||
[
|
||||
"addFieldStatePromise",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
112,
|
||||
59
|
||||
],
|
||||
[
|
||||
"eval",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
51,
|
||||
101
|
||||
],
|
||||
[
|
||||
"iterateFields",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
15,
|
||||
12
|
||||
],
|
||||
[
|
||||
"fieldSchemasToFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/index.tsx",
|
||||
36,
|
||||
79
|
||||
],
|
||||
[
|
||||
"async buildFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
130,
|
||||
29
|
||||
],
|
||||
[
|
||||
"async buildFormStateHandler",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
55,
|
||||
21
|
||||
],
|
||||
[
|
||||
"async Server.<anonymous>",
|
||||
"file:///Users/jacobfletcher/dev/payload/payload/test/dev.ts",
|
||||
1,
|
||||
2848
|
||||
]
|
||||
],
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"array.2.id": {
|
||||
"value": "67eb0a7f3a4c5f0cce051446",
|
||||
"initialValue": "67eb0a7f3a4c5f0cce051446"
|
||||
},
|
||||
"array.2.richText": {},
|
||||
"array.3.id": {
|
||||
"value": "67eb0c9f3a4c5f0cce051448",
|
||||
"initialValue": "67eb0c9f3a4c5f0cce051448",
|
||||
"lastRenderedPath": "array.3.id"
|
||||
},
|
||||
"array.3.richText": {
|
||||
"lastRenderedPath": "array.3.richText",
|
||||
"customComponents": {
|
||||
"Field": [
|
||||
"$",
|
||||
"$L2",
|
||||
null,
|
||||
{ "path": "array.3.richText", "children": "$L8" },
|
||||
null,
|
||||
[
|
||||
[
|
||||
"renderField",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx",
|
||||
156,
|
||||
128
|
||||
],
|
||||
[
|
||||
"addFieldStatePromise",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
584,
|
||||
9
|
||||
],
|
||||
[
|
||||
"eval",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
51,
|
||||
101
|
||||
],
|
||||
[
|
||||
"iterateFields",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
15,
|
||||
12
|
||||
],
|
||||
[
|
||||
"arrayValue.reduce.promises",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
125,
|
||||
107
|
||||
],
|
||||
[
|
||||
"addFieldStatePromise",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts",
|
||||
112,
|
||||
59
|
||||
],
|
||||
[
|
||||
"eval",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
51,
|
||||
101
|
||||
],
|
||||
[
|
||||
"iterateFields",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/iterateFields.ts",
|
||||
15,
|
||||
12
|
||||
],
|
||||
[
|
||||
"fieldSchemasToFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/forms/fieldSchemasToFormState/index.tsx",
|
||||
36,
|
||||
79
|
||||
],
|
||||
[
|
||||
"async buildFormState",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
130,
|
||||
29
|
||||
],
|
||||
[
|
||||
"async buildFormStateHandler",
|
||||
"webpack-internal:///(rsc)/./packages/ui/src/utilities/buildFormState.ts",
|
||||
55,
|
||||
21
|
||||
],
|
||||
[
|
||||
"async Server.<anonymous>",
|
||||
"file:///Users/jacobfletcher/dev/payload/payload/test/dev.ts",
|
||||
1,
|
||||
2848
|
||||
]
|
||||
],
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"updatedAt": {
|
||||
"value": "2025-03-31T21:30:11.221Z",
|
||||
"initialValue": "2025-03-31T21:30:11.221Z",
|
||||
"lastRenderedPath": "updatedAt"
|
||||
},
|
||||
"createdAt": {
|
||||
"value": "2025-03-31T21:30:11.221Z",
|
||||
"initialValue": "2025-03-31T21:30:11.221Z",
|
||||
"lastRenderedPath": "createdAt"
|
||||
},
|
||||
"blocks": { "value": 0, "initialValue": 0, "rows": [], "lastRenderedPath": "blocks" },
|
||||
"array": {
|
||||
"rows": [
|
||||
{ "id": "67eb09903a4c5f0cce051442" },
|
||||
{ "id": "67eb09923a4c5f0cce051444" },
|
||||
{ "id": "67eb0a7f3a4c5f0cce051446" },
|
||||
{ "id": "67eb0c9f3a4c5f0cce051448" }
|
||||
],
|
||||
"value": 4,
|
||||
"initialValue": 4,
|
||||
"disableFormData": true,
|
||||
"lastRenderedPath": "array"
|
||||
}
|
||||
}
|
||||
}
|
||||
44
test/helpers/e2e/assertRequestBody.ts
Normal file
44
test/helpers/e2e/assertRequestBody.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* A helper function to assert the body of a network request.
|
||||
* This is useful for reading the body of a request and testing whether it is correct.
|
||||
* For example, if you have a form that submits data to an API, you can use this function to
|
||||
* assert that the data being sent is correct.
|
||||
* @param page The Playwright page
|
||||
* @param options Options
|
||||
* @param options.action The action to perform that will trigger the request
|
||||
* @param options.expect A function to run after the request is made to assert the request body
|
||||
* @returns The request body
|
||||
* @example
|
||||
* const requestBody = await assertRequestBody(page, {
|
||||
* action: page.click('button'),
|
||||
* expect: (requestBody) => expect(requestBody.foo).toBe('bar')
|
||||
* })
|
||||
*/
|
||||
export const assertRequestBody = async <T>(
|
||||
page: Page,
|
||||
options: {
|
||||
action: Promise<void> | void
|
||||
expect?: (requestBody: T) => boolean | Promise<boolean>
|
||||
},
|
||||
): Promise<T | undefined> => {
|
||||
const [request] = await Promise.all([
|
||||
page.waitForRequest((request) => Boolean(request.method())), // Adjust condition as needed
|
||||
await options.action,
|
||||
])
|
||||
|
||||
const requestBody = request.postData()
|
||||
|
||||
if (typeof requestBody === 'string') {
|
||||
const parsedBody = JSON.parse(requestBody) as T
|
||||
|
||||
if (typeof options.expect === 'function') {
|
||||
expect(await options.expect(parsedBody)).toBeTruthy()
|
||||
}
|
||||
|
||||
return parsedBody
|
||||
}
|
||||
}
|
||||
76
test/helpers/e2e/assertResponseBody.ts
Normal file
76
test/helpers/e2e/assertResponseBody.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
function parseRSC(rscText: string) {
|
||||
// Next.js streams use special delimiters like "\n"
|
||||
const chunks = rscText.split('\n').filter((line) => line.trim() !== '')
|
||||
|
||||
// find the chunk starting with '1:', remove the '1:' prefix and parse the rest
|
||||
const match = chunks.find((chunk) => chunk.startsWith('1:'))
|
||||
|
||||
if (match) {
|
||||
const jsonString = match.slice(2).trim()
|
||||
if (jsonString) {
|
||||
try {
|
||||
return JSON.parse(jsonString)
|
||||
} catch (err) {
|
||||
console.error('Failed to parse JSON:', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to assert the response of a network request.
|
||||
* This is useful for reading the response of a request and testing whether it is correct.
|
||||
* For example, if you have a form that submits data to an API, you can use this function to
|
||||
* assert that the data sent back is correct.
|
||||
* @param page The Playwright page
|
||||
* @param options Options
|
||||
* @param options.action The action to perform that will trigger the request
|
||||
* @param options.expect A function to run after the request is made to assert the response body
|
||||
* @param options.url The URL to match in the network requests
|
||||
* @returns The request body
|
||||
* @example
|
||||
* const responseBody = await assertResponseBody(page, {
|
||||
* action: page.click('button'),
|
||||
* expect: (responseBody) => expect(responseBody.foo).toBe('bar')
|
||||
* })
|
||||
*/
|
||||
export const assertResponseBody = async <T>(
|
||||
page: Page,
|
||||
options: {
|
||||
action: Promise<void> | void
|
||||
expect?: (requestBody: T) => boolean | Promise<boolean>
|
||||
url?: string
|
||||
},
|
||||
): Promise<T | undefined> => {
|
||||
const [response] = await Promise.all([
|
||||
page.waitForResponse((response) => response.url().includes(options.url || '')),
|
||||
await options.action,
|
||||
])
|
||||
|
||||
if (!response) {
|
||||
throw new Error('No response received')
|
||||
}
|
||||
|
||||
const responseBody = await response.text()
|
||||
const responseType = response.headers()['content-type']?.split(';')[0]
|
||||
|
||||
let parsedBody: T = undefined as T
|
||||
|
||||
if (responseType === 'text/x-component') {
|
||||
parsedBody = parseRSC(responseBody)
|
||||
} else if (typeof responseBody === 'string') {
|
||||
parsedBody = JSON.parse(responseBody) as T
|
||||
}
|
||||
|
||||
if (typeof options.expect === 'function') {
|
||||
expect(await options.expect(parsedBody)).toBeTruthy()
|
||||
}
|
||||
|
||||
return parsedBody
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": ["./test/query-presets/config.ts"],
|
||||
"@payload-config": ["./test/form-state/config.ts"],
|
||||
"@payloadcms/admin-bar": ["./packages/admin-bar/src"],
|
||||
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user