Skip to content
Draft

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { JsonObject, JsonValue, PayloadRequest } from '../../../types/index.js'
import type { FieldAffectingData } from '../../config/types.js'

import { deepCopyObjectSimple } from '../../../utilities/deepCopyObject.js'
import { getDefaultValue } from '../../getDefaultValue.js'
import { cloneDataFromOriginalDoc } from '../beforeChange/cloneDataFromOriginalDoc.js'

export async function getFallbackValue({
field,
Expand All @@ -18,7 +18,8 @@ export async function getFallbackValue({
let fallbackValue: JsonValue = undefined
if ('name' in field && field.name) {
if (isDocumentValueAllowed && typeof siblingDoc[field.name] !== 'undefined') {
fallbackValue = cloneDataFromOriginalDoc(siblingDoc[field.name])
// Keep nested mutations from hooks and access control from changing the original document.
fallbackValue = deepCopyObjectSimple(siblingDoc[field.name])
} else if ('defaultValue' in field && typeof field.defaultValue !== 'undefined') {
fallbackValue = await getDefaultValue({
defaultValue: field.defaultValue,
Expand Down
23 changes: 23 additions & 0 deletions test/access-control/collections/BlocksFieldAccess/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@ export const BlocksFieldAccess: CollectionConfig = {
update: () => true,
},
fields: [
{
name: 'rows',
type: 'array',
fields: [
{
name: 'children',
type: 'array',
fields: [
{
name: 'allowCodeChanges',
type: 'checkbox',
defaultValue: false,
},
{
name: 'code',
type: 'text',
access: { update: ({ siblingData }) => siblingData?.allowCodeChanges === true },
defaultValue: 'default',
},
],
},
],
},
{
name: 'title',
type: 'text',
Expand Down
120 changes: 120 additions & 0 deletions test/access-control/int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,126 @@ test.suite('Access Control', { config: './config.ts', resetBetweenTests: false }
})

test.describe('Fields', () => {
test.describe('fallback values', () => {
const createdIDs: (number | string)[] = []

test.afterEach(async ({ payload }) => {
for (const id of createdIDs) {
await payload.delete({ id, collection: blocksFieldAccessSlug, overrideAccess: true })
}
createdIDs.length = 0
})

test('should allow nested field updates when sibling access permits them', async ({
payload,
}) => {
const doc = await payload.create({
collection: blocksFieldAccessSlug,
data: {
rows: [{ children: [{ allowCodeChanges: true, code: 'stored' }] }],
title: 'original',
},
})

const row = doc.rows![0]!

createdIDs.push(doc.id)

await payload.update({
id: doc.id,
collection: blocksFieldAccessSlug,
data: {
rows: [
{
id: row.id,
children: [{ id: row.children![0]!.id, allowCodeChanges: true, code: 'changed' }],
},
],
},
overrideAccess: false,
})

const stored = await payload.findByID({
id: doc.id,
collection: blocksFieldAccessSlug,
})

expect(stored.rows?.[0]?.children?.[0]?.code).toBe('changed')
})

for (const draft of [false, true]) {
const operation = draft ? 'draft save' : 'published update'

test(`should preserve omitted protected nested fields after a ${operation}`, async ({
payload,
}) => {
const doc = await payload.create({
collection: blocksFieldAccessSlug,
data: {
_status: draft ? 'draft' : 'published',
rows: [{ children: [{ code: 'stored' }] }],
title: 'original',
},
draft,
})

createdIDs.push(doc.id)

await payload.update({
id: doc.id,
collection: blocksFieldAccessSlug,
data: { title: 'updated' },
draft,
overrideAccess: false,
})

const stored = await payload.findByID({
id: doc.id,
collection: blocksFieldAccessSlug,
draft,
})

expect(stored.rows?.[0]?.children?.[0]?.code).toBe('stored')
})

test(`should reject changes to protected nested fields during a ${operation}`, async ({
payload,
}) => {
const doc = await payload.create({
collection: blocksFieldAccessSlug,
data: {
_status: draft ? 'draft' : 'published',
rows: [{ children: [{ code: 'stored' }] }],
title: 'original',
},
draft,
})

const row = doc.rows![0]!

createdIDs.push(doc.id)

await payload.update({
id: doc.id,
collection: blocksFieldAccessSlug,
data: {
rows: [{ id: row.id, children: [{ id: row.children![0]!.id, code: 'changed' }] }],
},
draft,
overrideAccess: false,
})

const stored = await payload.findByID({
id: doc.id,
collection: blocksFieldAccessSlug,
draft,
})

expect(stored.rows?.[0]?.children?.[0]?.code).toBe('stored')
})
}
})

test('should not affect hidden fields when patching data', async ({ payload }) => {
const doc = await payload.create({
collection: hiddenFieldsSlug,
Expand Down
26 changes: 26 additions & 0 deletions test/access-control/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,18 @@ export interface InheritedReadVersionsVirtual {
*/
export interface BlocksFieldAccess {
id: string;
rows?:
| {
children?:
| {
allowCodeChanges?: boolean | null;
code?: string | null;
id?: string | null;
}[]
| null;
id?: string | null;
}[]
| null;
title: string;
editableBlocks?: TestBlock[] | null;
readOnlyBlocks?: TestBlock2[] | null;
Expand Down Expand Up @@ -1477,6 +1489,7 @@ export interface BlocksFieldAccess {
} | null);
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
Expand Down Expand Up @@ -2811,6 +2824,18 @@ export interface InheritedReadVersionsVirtualSelect<T extends boolean = true> {
* via the `definition` "blocks-field-access_select".
*/
export interface BlocksFieldAccessSelect<T extends boolean = true> {
rows?:
| T
| {
children?:
| T
| {
allowCodeChanges?: T;
code?: T;
id?: T;
};
id?: T;
};
title?: T;
editableBlocks?:
| T
Expand Down Expand Up @@ -2859,6 +2884,7 @@ export interface BlocksFieldAccessSelect<T extends boolean = true> {
updatedBy?: T;
updatedAt?: T;
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
Expand Down
1 change: 1 addition & 0 deletions test/fields/collections/SelectVersions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const SelectVersionsFields: CollectionConfig = {
slug: selectVersionsFieldsSlug,
versions: { drafts: { autosave: true } },
fields: [
{ name: 'customJSON', type: 'json', label: 'Custom JSON' },
{
type: 'select',
hasMany: true,
Expand Down
56 changes: 56 additions & 0 deletions test/fields/int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ import {
dateFieldsSlug,
duplicateFieldsSlug,
groupFieldsSlug,
jsonFieldsSlug,
numberFieldsSlug,
relationshipFieldsSlug,
selectVersionsFieldsSlug,
tabsFieldsSlug,
textFieldsSlug,
} from './slugs.js'
Expand Down Expand Up @@ -5468,6 +5470,60 @@ test.suite('Fields', { config: './config.ts', resetBetweenTests: false }, () =>
})

test.describe('json', () => {
test.describe('fallback values', () => {
const createdDocs: {
collection: typeof jsonFieldsSlug | typeof selectVersionsFieldsSlug
id: number | string
}[] = []

test.afterEach(async ({ payload }) => {
for (const doc of createdDocs) {
await payload.delete({ ...doc, overrideAccess: true })
}
createdDocs.length = 0
})

for (const draft of [false, true]) {
const operation = draft ? 'draft save' : 'partial update'
const collection = draft ? selectVersionsFieldsSlug : jsonFieldsSlug

test(`should preserve omitted JSON tuples after a ${operation}`, async ({ payload }) => {
const doc = await payload.create({
collection,
data: {
customJSON: [
[100, 200],
[300, 400],
],
},
draft,
overrideAccess: true,
})
createdDocs.push({ collection, id: doc.id })

await payload.update({
id: doc.id,
collection,
data: draft ? { hasMany: ['a'] } : { json: { foo: 'bar' } },
draft,
overrideAccess: true,
})

const stored = await payload.findByID({
id: doc.id,
collection,
draft,
overrideAccess: true,
})

expect(stored.customJSON).toStrictEqual([
[100, 200],
[300, 400],
])
})
}
})

test('should save json data', async ({ payload }) => {
const json = { foo: 'bar' }
const doc = await payload.create({
Expand Down
10 changes: 10 additions & 0 deletions test/fields/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,15 @@ export interface User {
*/
export interface SelectVersionsField {
id: string;
customJSON?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
hasMany?: ('a' | 'b' | 'c' | 'd')[] | null;
array?:
| {
Expand Down Expand Up @@ -2713,6 +2722,7 @@ export interface UsersSelect<T extends boolean = true> {
* via the `definition` "select-versions-fields_select".
*/
export interface SelectVersionsFieldsSelect<T extends boolean = true> {
customJSON?: T;
hasMany?: T;
array?:
| T
Expand Down
Loading