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:
Jarrod Flesch
2024-07-29 16:28:28 -04:00
committed by GitHub
parent 354588898f
commit 3a941c7c8a
14 changed files with 177 additions and 24 deletions

View File

@@ -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'>

View File

@@ -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({

View File

@@ -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

View File

@@ -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> {

View 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',
},
],
}

View File

@@ -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',
},
],
}

View File

@@ -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,
}, },

View File

@@ -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',

View File

@@ -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,

View File

@@ -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) => {

View File

@@ -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')
})
})
}) })

View File

@@ -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',

View File

@@ -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".

View File

@@ -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',
} }