chore: duplicates prev value PRs from v2 (#7414)
Updates V3 with V2 PR's - previousVersion type https://github.com/payloadcms/payload/pull/6805 - tests from https://github.com/payloadcms/payload/pull/6805
This commit is contained in:
@@ -175,20 +175,22 @@ export type Labels = {
|
|||||||
singular: LabelFunction | LabelStatic
|
singular: LabelFunction | LabelStatic
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BaseValidateOptions<TData, TSiblingData> = {
|
export type BaseValidateOptions<TData, TSiblingData, TValue> = {
|
||||||
data: Partial<TData>
|
data: Partial<TData>
|
||||||
id?: number | string
|
id?: number | string
|
||||||
operation?: Operation
|
operation?: Operation
|
||||||
preferences: DocumentPreferences
|
preferences: DocumentPreferences
|
||||||
|
previousValue?: TValue
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
siblingData: Partial<TSiblingData>
|
siblingData: Partial<TSiblingData>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ValidateOptions<TData, TSiblingData, TFieldConfig extends object> = BaseValidateOptions<
|
export type ValidateOptions<
|
||||||
TData,
|
TData,
|
||||||
TSiblingData
|
TSiblingData,
|
||||||
> &
|
TFieldConfig extends object,
|
||||||
TFieldConfig
|
TValue,
|
||||||
|
> = BaseValidateOptions<TData, TSiblingData, TValue> & TFieldConfig
|
||||||
|
|
||||||
export type Validate<
|
export type Validate<
|
||||||
TValue = any,
|
TValue = any,
|
||||||
@@ -197,7 +199,7 @@ export type Validate<
|
|||||||
TFieldConfig extends object = object,
|
TFieldConfig extends object = object,
|
||||||
> = (
|
> = (
|
||||||
value: TValue,
|
value: TValue,
|
||||||
options: ValidateOptions<TData, TSiblingData, TFieldConfig>,
|
options: ValidateOptions<TData, TSiblingData, TFieldConfig, TValue>,
|
||||||
) => Promise<string | true> | string | true
|
) => Promise<string | true> | string | true
|
||||||
|
|
||||||
export type ClientValidate = Omit<Validate, 'req'>
|
export type ClientValidate = Omit<Validate, 'req'>
|
||||||
|
|||||||
@@ -140,9 +140,10 @@ export const promise = async ({
|
|||||||
jsonError,
|
jsonError,
|
||||||
operation,
|
operation,
|
||||||
preferences: { fields: {} },
|
preferences: { fields: {} },
|
||||||
|
previousValue: siblingDoc[field.name],
|
||||||
req,
|
req,
|
||||||
siblingData: deepMergeWithSourceArrays(siblingDoc, siblingData),
|
siblingData: deepMergeWithSourceArrays(siblingDoc, siblingData),
|
||||||
} as ValidateOptions<any, any, { jsonError: object }>)
|
} as ValidateOptions<any, any, { jsonError: object }, any>)
|
||||||
|
|
||||||
if (typeof validationResult === 'string') {
|
if (typeof validationResult === 'string') {
|
||||||
errors.push({
|
errors.push({
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export type NodeValidation<T extends SerializedLexicalNode = SerializedLexicalNo
|
|||||||
node: T
|
node: T
|
||||||
nodeValidations: Map<string, Array<NodeValidation>>
|
nodeValidations: Map<string, Array<NodeValidation>>
|
||||||
validation: {
|
validation: {
|
||||||
options: ValidateOptions<unknown, unknown, RichTextField>
|
options: ValidateOptions<unknown, unknown, RichTextField, SerializedEditorState>
|
||||||
value: SerializedEditorState
|
value: SerializedEditorState
|
||||||
}
|
}
|
||||||
}) => Promise<string | true> | string | true
|
}) => Promise<string | true> | string | true
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export async function validateNodes({
|
|||||||
nodeValidations: Map<string, Array<NodeValidation>>
|
nodeValidations: Map<string, Array<NodeValidation>>
|
||||||
nodes: SerializedLexicalNode[]
|
nodes: SerializedLexicalNode[]
|
||||||
validation: {
|
validation: {
|
||||||
options: ValidateOptions<unknown, unknown, RichTextField>
|
options: ValidateOptions<unknown, unknown, RichTextField, SerializedEditorState>
|
||||||
value: SerializedEditorState
|
value: SerializedEditorState
|
||||||
}
|
}
|
||||||
}): Promise<string | true> {
|
}): Promise<string | true> {
|
||||||
|
|||||||
59
test/field-error-states/collections/PrevValue/index.ts
Normal file
59
test/field-error-states/collections/PrevValue/index.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
|
import * as QueryString from 'qs-esm'
|
||||||
|
|
||||||
|
import { collectionSlugs } from '../../shared.js'
|
||||||
|
|
||||||
|
export const PrevValue: CollectionConfig = {
|
||||||
|
slug: collectionSlugs.prevValue,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
validate: async (value, options) => {
|
||||||
|
if (options.operation === 'create') return true
|
||||||
|
|
||||||
|
const query = QueryString.stringify(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
previousValueRelation: {
|
||||||
|
in: [options.id],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
addQueryPrefix: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const relatedDocs = await fetch(
|
||||||
|
`http://localhost:3000/api/${collectionSlugs.prevValueRelation}${query}`,
|
||||||
|
{
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).then((res) => res.json())
|
||||||
|
if (relatedDocs.docs.length > 0 && value !== options.previousValue) {
|
||||||
|
console.log({
|
||||||
|
value,
|
||||||
|
prev: options.previousValue,
|
||||||
|
})
|
||||||
|
return 'Doc is being referenced, cannot change title'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
|
import { collectionSlugs } from '../../shared.js'
|
||||||
|
|
||||||
|
export const PrevValueRelation: CollectionConfig = {
|
||||||
|
slug: collectionSlugs.prevValueRelation,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
relationTo: collectionSlugs.prevValue,
|
||||||
|
name: 'previousValueRelation',
|
||||||
|
type: 'relationship',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { CollectionConfig } from 'payload'
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
import { slugs } from '../../shared.js'
|
import { collectionSlugs } from '../../shared.js'
|
||||||
import { ValidateDraftsOn } from '../ValidateDraftsOn/index.js'
|
import { ValidateDraftsOn } from '../ValidateDraftsOn/index.js'
|
||||||
|
|
||||||
export const ValidateDraftsOff: CollectionConfig = {
|
export const ValidateDraftsOff: CollectionConfig = {
|
||||||
...ValidateDraftsOn,
|
...ValidateDraftsOn,
|
||||||
slug: slugs.validateDraftsOff,
|
slug: collectionSlugs.validateDraftsOff,
|
||||||
versions: {
|
versions: {
|
||||||
drafts: true,
|
drafts: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { CollectionConfig } from 'payload'
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
import { slugs } from '../../shared.js'
|
import { collectionSlugs } from '../../shared.js'
|
||||||
|
|
||||||
export const ValidateDraftsOn: CollectionConfig = {
|
export const ValidateDraftsOn: CollectionConfig = {
|
||||||
slug: slugs.validateDraftsOn,
|
slug: collectionSlugs.validateDraftsOn,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'title',
|
name: 'title',
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { CollectionConfig } from 'payload'
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
import { slugs } from '../../shared.js'
|
import { collectionSlugs } from '../../shared.js'
|
||||||
import { ValidateDraftsOn } from '../ValidateDraftsOn/index.js'
|
import { ValidateDraftsOn } from '../ValidateDraftsOn/index.js'
|
||||||
|
|
||||||
export const ValidateDraftsOnAndAutosave: CollectionConfig = {
|
export const ValidateDraftsOnAndAutosave: CollectionConfig = {
|
||||||
...ValidateDraftsOn,
|
...ValidateDraftsOn,
|
||||||
slug: slugs.validateDraftsOnAutosave,
|
slug: collectionSlugs.validateDraftsOnAutosave,
|
||||||
versions: {
|
versions: {
|
||||||
drafts: {
|
drafts: {
|
||||||
autosave: true,
|
autosave: true,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ const dirname = path.dirname(filename)
|
|||||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||||
import { devUser } from '../credentials.js'
|
import { devUser } from '../credentials.js'
|
||||||
import { ErrorFieldsCollection } from './collections/ErrorFields/index.js'
|
import { ErrorFieldsCollection } from './collections/ErrorFields/index.js'
|
||||||
|
import { PrevValue } from './collections/PrevValue/index.js'
|
||||||
|
import { PrevValueRelation } from './collections/PrevValueRelation/index.js'
|
||||||
import Uploads from './collections/Upload/index.js'
|
import Uploads from './collections/Upload/index.js'
|
||||||
import { ValidateDraftsOff } from './collections/ValidateDraftsOff/index.js'
|
import { ValidateDraftsOff } from './collections/ValidateDraftsOff/index.js'
|
||||||
import { ValidateDraftsOn } from './collections/ValidateDraftsOn/index.js'
|
import { ValidateDraftsOn } from './collections/ValidateDraftsOn/index.js'
|
||||||
@@ -18,6 +20,8 @@ export default buildConfigWithDefaults({
|
|||||||
ValidateDraftsOn,
|
ValidateDraftsOn,
|
||||||
ValidateDraftsOff,
|
ValidateDraftsOff,
|
||||||
ValidateDraftsOnAndAutosave,
|
ValidateDraftsOnAndAutosave,
|
||||||
|
PrevValue,
|
||||||
|
PrevValueRelation,
|
||||||
],
|
],
|
||||||
globals: [GlobalValidateDraftsOn],
|
globals: [GlobalValidateDraftsOn],
|
||||||
onInit: async (payload) => {
|
onInit: async (payload) => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { fileURLToPath } from 'url'
|
|||||||
import { ensureCompilationIsDone, initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers.js'
|
import { ensureCompilationIsDone, initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers.js'
|
||||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||||
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||||
import { slugs } from './shared.js'
|
import { collectionSlugs } from './shared.js'
|
||||||
|
|
||||||
const { beforeAll, describe } = test
|
const { beforeAll, describe } = test
|
||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
@@ -20,13 +20,17 @@ describe('field error states', () => {
|
|||||||
let validateDraftsOff: AdminUrlUtil
|
let validateDraftsOff: AdminUrlUtil
|
||||||
let validateDraftsOn: AdminUrlUtil
|
let validateDraftsOn: AdminUrlUtil
|
||||||
let validateDraftsOnAutosave: AdminUrlUtil
|
let validateDraftsOnAutosave: AdminUrlUtil
|
||||||
|
let prevValue: AdminUrlUtil
|
||||||
|
let prevValueRelation: AdminUrlUtil
|
||||||
|
|
||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
;({ serverURL } = await initPayloadE2ENoConfig({ dirname }))
|
;({ serverURL } = await initPayloadE2ENoConfig({ dirname }))
|
||||||
validateDraftsOff = new AdminUrlUtil(serverURL, slugs.validateDraftsOff)
|
validateDraftsOff = new AdminUrlUtil(serverURL, collectionSlugs.validateDraftsOff)
|
||||||
validateDraftsOn = new AdminUrlUtil(serverURL, slugs.validateDraftsOn)
|
validateDraftsOn = new AdminUrlUtil(serverURL, collectionSlugs.validateDraftsOn)
|
||||||
validateDraftsOnAutosave = new AdminUrlUtil(serverURL, slugs.validateDraftsOnAutosave)
|
validateDraftsOnAutosave = new AdminUrlUtil(serverURL, collectionSlugs.validateDraftsOnAutosave)
|
||||||
|
prevValue = new AdminUrlUtil(serverURL, collectionSlugs.prevValue)
|
||||||
|
prevValueRelation = new AdminUrlUtil(serverURL, collectionSlugs.prevValueRelation)
|
||||||
const context = await browser.newContext()
|
const context = await browser.newContext()
|
||||||
page = await context.newPage()
|
page = await context.newPage()
|
||||||
initPageConsoleErrorCatch(page)
|
initPageConsoleErrorCatch(page)
|
||||||
@@ -87,4 +91,33 @@ describe('field error states', () => {
|
|||||||
await saveDocAndAssert(page, '#action-save', 'error')
|
await saveDocAndAssert(page, '#action-save', 'error')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('previous values', () => {
|
||||||
|
test('should pass previous value into validate function', async () => {
|
||||||
|
// save original
|
||||||
|
await page.goto(prevValue.create)
|
||||||
|
await page.locator('#field-title').fill('original value')
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
await page.locator('#field-title').fill('original value 2')
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
|
||||||
|
// create relation to doc
|
||||||
|
await page.goto(prevValueRelation.create)
|
||||||
|
await page.locator('#field-previousValueRelation .react-select').click()
|
||||||
|
await page.locator('#field-previousValueRelation .rs__option').first().click()
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
|
||||||
|
// go back to doc
|
||||||
|
await page.goto(prevValue.list)
|
||||||
|
await page.locator('.row-1 a').click()
|
||||||
|
await page.locator('#field-description').fill('some description')
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
await page.locator('#field-title').fill('changed')
|
||||||
|
await saveDocAndAssert(page, '#action-save', 'error')
|
||||||
|
|
||||||
|
// ensure value is the value before relationship association
|
||||||
|
await page.reload()
|
||||||
|
await expect(page.locator('#field-title')).toHaveValue('original value 2')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { GlobalConfig } from 'payload'
|
import type { GlobalConfig } from 'payload'
|
||||||
|
|
||||||
import { slugs } from '../../shared.js'
|
import { globalSlugs } from '../../shared.js'
|
||||||
|
|
||||||
export const GlobalValidateDraftsOn: GlobalConfig = {
|
export const GlobalValidateDraftsOn: GlobalConfig = {
|
||||||
slug: slugs.globalValidateDraftsOn,
|
slug: globalSlugs.globalValidateDraftsOn,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'group',
|
name: 'group',
|
||||||
|
|||||||
@@ -16,10 +16,15 @@ export interface Config {
|
|||||||
'validate-drafts-on': ValidateDraftsOn;
|
'validate-drafts-on': ValidateDraftsOn;
|
||||||
'validate-drafts-off': ValidateDraftsOff;
|
'validate-drafts-off': ValidateDraftsOff;
|
||||||
'validate-drafts-on-autosave': ValidateDraftsOnAutosave;
|
'validate-drafts-on-autosave': ValidateDraftsOnAutosave;
|
||||||
|
'prev-value': PrevValue;
|
||||||
|
'prev-value-relation': PrevValueRelation;
|
||||||
users: User;
|
users: User;
|
||||||
'payload-preferences': PayloadPreference;
|
'payload-preferences': PayloadPreference;
|
||||||
'payload-migrations': PayloadMigration;
|
'payload-migrations': PayloadMigration;
|
||||||
};
|
};
|
||||||
|
db: {
|
||||||
|
defaultIDType: string;
|
||||||
|
};
|
||||||
globals: {
|
globals: {
|
||||||
'global-validate-drafts-on': GlobalValidateDraftsOn;
|
'global-validate-drafts-on': GlobalValidateDraftsOn;
|
||||||
};
|
};
|
||||||
@@ -33,13 +38,16 @@ export interface UserAuthOperations {
|
|||||||
email: string;
|
email: string;
|
||||||
};
|
};
|
||||||
login: {
|
login: {
|
||||||
password: string;
|
|
||||||
email: string;
|
email: string;
|
||||||
|
password: string;
|
||||||
};
|
};
|
||||||
registerFirstUser: {
|
registerFirstUser: {
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
unlock: {
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
@@ -312,6 +320,27 @@ export interface ValidateDraftsOnAutosave {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
_status?: ('draft' | 'published') | null;
|
_status?: ('draft' | 'published') | null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "prev-value".
|
||||||
|
*/
|
||||||
|
export interface PrevValue {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string | null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "prev-value-relation".
|
||||||
|
*/
|
||||||
|
export interface PrevValueRelation {
|
||||||
|
id: string;
|
||||||
|
previousValueRelation?: (string | null) | PrevValue;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "payload-preferences".
|
* via the `definition` "payload-preferences".
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
export const slugs = {
|
import type { CollectionSlug, GlobalSlug } from 'payload'
|
||||||
globalValidateDraftsOn: 'global-validate-drafts-on',
|
|
||||||
|
export const collectionSlugs: {
|
||||||
|
[key: string]: CollectionSlug
|
||||||
|
} = {
|
||||||
validateDraftsOff: 'validate-drafts-off',
|
validateDraftsOff: 'validate-drafts-off',
|
||||||
validateDraftsOn: 'validate-drafts-on',
|
validateDraftsOn: 'validate-drafts-on',
|
||||||
validateDraftsOnAutosave: 'validate-drafts-on-autosave',
|
validateDraftsOnAutosave: 'validate-drafts-on-autosave',
|
||||||
|
prevValue: 'prev-value',
|
||||||
|
prevValueRelation: 'prev-value-relation',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const globalSlugs: {
|
||||||
|
[key: string]: GlobalSlug
|
||||||
|
} = {
|
||||||
|
globalValidateDraftsOn: 'global-validate-drafts-on',
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user