fix: ensure blocks filterOptions are awaited (#13960)

Fixes #13956
This commit is contained in:
Alessio Gravili
2025-09-29 10:48:30 -07:00
committed by GitHub
parent 6d995ffb91
commit 41aa201f7b
4 changed files with 49 additions and 42 deletions

View File

@@ -205,7 +205,7 @@ export const promise = async ({
if (field.type === 'blocks' && field.filterOptions) { if (field.type === 'blocks' && field.filterOptions) {
// Re-run filteroptions. If the validation error is due to filteroptions, we need to add error paths to all the blocks // Re-run filteroptions. If the validation error is due to filteroptions, we need to add error paths to all the blocks
// that are no longer valid // that are no longer valid
const validationResult = validateBlocksFilterOptions({ const validationResult = await validateBlocksFilterOptions({
id, id,
data, data,
filterOptions: field.filterOptions, filterOptions: field.filterOptions,

View File

@@ -452,7 +452,7 @@ describe('Field Validations', () => {
const blocksOptions: Parameters<BlocksFieldValidation>[1] = { const blocksOptions: Parameters<BlocksFieldValidation>[1] = {
...options, ...options,
} }
it('basic blocks should pass validation', () => { it('basic blocks should pass validation', async () => {
const val: any[] = [ const val: any[] = [
{ {
blockType: 'block1', blockType: 'block1',
@@ -463,12 +463,12 @@ describe('Field Validations', () => {
someField: 'some data', someField: 'some data',
}, },
] ]
const result = blocks(val, blocksOptions) const result = await blocks(val, blocksOptions)
expect(result).toStrictEqual(true) expect(result).toStrictEqual(true)
}) })
it('should respect required validation', () => { it('should respect required validation', async () => {
const result1 = blocks( const result1 = await blocks(
[ [
{ {
blockType: 'block1', blockType: 'block1',
@@ -479,17 +479,17 @@ describe('Field Validations', () => {
) )
expect(result1).toStrictEqual(true) expect(result1).toStrictEqual(true)
const result2 = blocks([], { ...blocksOptions, required: true }) const result2 = await blocks([], { ...blocksOptions, required: true })
expect(result2).not.toStrictEqual(true) expect(result2).not.toStrictEqual(true)
const result3 = blocks(undefined, { ...blocksOptions, required: true }) const result3 = await blocks(undefined, { ...blocksOptions, required: true })
expect(result3).not.toStrictEqual(true) expect(result3).not.toStrictEqual(true)
const result4 = blocks(null, { ...blocksOptions, required: true }) const result4 = await blocks(null, { ...blocksOptions, required: true })
expect(result4).not.toStrictEqual(true) expect(result4).not.toStrictEqual(true)
}) })
it('should respect minRows validation', () => { it('should respect minRows validation', async () => {
const val: any[] = [ const val: any[] = [
{ {
blockType: 'block1', blockType: 'block1',
@@ -500,16 +500,16 @@ describe('Field Validations', () => {
someField: 'some data', someField: 'some data',
}, },
] ]
const result1 = blocks(val, { ...blocksOptions, minRows: 0 }) const result1 = await blocks(val, { ...blocksOptions, minRows: 0 })
expect(result1).toStrictEqual(true) expect(result1).toStrictEqual(true)
const result2 = blocks(val, { ...blocksOptions, minRows: 2 }) const result2 = await blocks(val, { ...blocksOptions, minRows: 2 })
expect(result2).toStrictEqual(true) expect(result2).toStrictEqual(true)
const result3 = blocks(val, { ...blocksOptions, minRows: 3 }) const result3 = await blocks(val, { ...blocksOptions, minRows: 3 })
expect(result3).not.toStrictEqual(true) expect(result3).not.toStrictEqual(true)
}) })
it('should respect maxRows validation', () => { it('should respect maxRows validation', async () => {
const val: any[] = [ const val: any[] = [
{ {
blockType: 'block1', blockType: 'block1',
@@ -521,16 +521,16 @@ describe('Field Validations', () => {
}, },
] ]
const result1 = blocks(val, { ...blocksOptions, maxRows: 2 }) const result1 = await blocks(val, { ...blocksOptions, maxRows: 2 })
expect(result1).toStrictEqual(true) expect(result1).toStrictEqual(true)
const result2 = blocks(val, { ...blocksOptions, maxRows: 3 }) const result2 = await blocks(val, { ...blocksOptions, maxRows: 3 })
expect(result2).toStrictEqual(true) expect(result2).toStrictEqual(true)
const result3 = blocks(val, { ...blocksOptions, maxRows: 1 }) const result3 = await blocks(val, { ...blocksOptions, maxRows: 1 })
expect(result3).not.toStrictEqual(true) expect(result3).not.toStrictEqual(true)
}) })
it('should respect both minRows and maxRows validation', () => { it('should respect both minRows and maxRows validation', async () => {
const val: any[] = [ const val: any[] = [
{ {
blockType: 'block1', blockType: 'block1',
@@ -541,20 +541,20 @@ describe('Field Validations', () => {
someField: 'some data', someField: 'some data',
}, },
] ]
const result1 = blocks(val, { ...blocksOptions, maxRows: 2, minRows: 2 }) const result1 = await blocks(val, { ...blocksOptions, maxRows: 2, minRows: 2 })
expect(result1).toStrictEqual(true) expect(result1).toStrictEqual(true)
const result2 = blocks(val, { ...blocksOptions, maxRows: 1, minRows: 4 }) const result2 = await blocks(val, { ...blocksOptions, maxRows: 1, minRows: 4 })
expect(result2).not.toStrictEqual(true) expect(result2).not.toStrictEqual(true)
const result3 = blocks(val, { ...blocksOptions, maxRows: 1, minRows: 0 }) const result3 = await blocks(val, { ...blocksOptions, maxRows: 1, minRows: 0 })
expect(result3).not.toStrictEqual(true) expect(result3).not.toStrictEqual(true)
const result4 = blocks(val, { ...blocksOptions, maxRows: 5, minRows: 3 }) const result4 = await blocks(val, { ...blocksOptions, maxRows: 5, minRows: 3 })
expect(result4).not.toStrictEqual(true) expect(result4).not.toStrictEqual(true)
}) })
it('should validate static filterOptions', () => { it('should validate static filterOptions', async () => {
const val: any[] = [ const val: any[] = [
{ {
blockType: 'block1', blockType: 'block1',
@@ -565,23 +565,23 @@ describe('Field Validations', () => {
someField: 'some data', someField: 'some data',
}, },
] ]
const result1 = blocks(val, { ...blocksOptions, filterOptions: ['block1', 'block2'] }) const result1 = await blocks(val, { ...blocksOptions, filterOptions: ['block1', 'block2'] })
expect(result1).toStrictEqual(true) expect(result1).toStrictEqual(true)
const result2 = blocks(val, { const result2 = await blocks(val, {
...blocksOptions, ...blocksOptions,
filterOptions: ['block1', 'block2', 'block3'], filterOptions: ['block1', 'block2', 'block3'],
}) })
expect(result2).toStrictEqual(true) expect(result2).toStrictEqual(true)
const result3 = blocks(val, { ...blocksOptions, filterOptions: ['block1', 'block3'] }) const result3 = await blocks(val, { ...blocksOptions, filterOptions: ['block1', 'block3'] })
expect(result3).not.toStrictEqual(true) expect(result3).not.toStrictEqual(true)
const result4 = blocks(val, { ...blocksOptions, filterOptions: [] }) const result4 = await blocks(val, { ...blocksOptions, filterOptions: [] })
expect(result4).not.toStrictEqual(true) expect(result4).not.toStrictEqual(true)
}) })
it('should validate dynamic filterOptions 1', () => { it('should validate dynamic filterOptions 1', async () => {
const val: any[] = [ const val: any[] = [
{ {
blockType: 'block1', blockType: 'block1',
@@ -592,25 +592,31 @@ describe('Field Validations', () => {
someField: 'some data', someField: 'some data',
}, },
] ]
const result1 = blocks(val, { ...blocksOptions, filterOptions: () => true }) const result1 = await blocks(val, { ...blocksOptions, filterOptions: () => true })
expect(result1).toStrictEqual(true) expect(result1).toStrictEqual(true)
const result2 = blocks(val, { ...blocksOptions, filterOptions: () => ['block1', 'block2'] }) const result2 = await blocks(val, {
...blocksOptions,
filterOptions: () => ['block1', 'block2'],
})
expect(result2).toStrictEqual(true) expect(result2).toStrictEqual(true)
const result3 = blocks(val, { const result3 = await blocks(val, {
...blocksOptions, ...blocksOptions,
filterOptions: () => ['block1', 'block2', 'block3'], filterOptions: () => ['block1', 'block2', 'block3'],
}) })
expect(result3).toStrictEqual(true) expect(result3).toStrictEqual(true)
const result4 = blocks(val, { ...blocksOptions, filterOptions: () => [] }) const result4 = await blocks(val, { ...blocksOptions, filterOptions: () => [] })
expect(result4).not.toStrictEqual(true) expect(result4).not.toStrictEqual(true)
const result5 = blocks(val, { ...blocksOptions, filterOptions: () => ['block1'] }) const result5 = await blocks(val, { ...blocksOptions, filterOptions: () => ['block1'] })
expect(result5).not.toStrictEqual(true) expect(result5).not.toStrictEqual(true)
const result6 = blocks(val, { ...blocksOptions, filterOptions: () => ['block1', 'block3'] }) const result6 = await blocks(val, {
...blocksOptions,
filterOptions: () => ['block1', 'block3'],
})
expect(result6).not.toStrictEqual(true) expect(result6).not.toStrictEqual(true)
}) })
}) })

View File

@@ -536,7 +536,7 @@ export type BlocksFieldValidation = Validate<unknown, unknown, unknown, BlocksFi
* *
* @internal - this may break or be removed at any time * @internal - this may break or be removed at any time
*/ */
export function validateBlocksFilterOptions({ export async function validateBlocksFilterOptions({
id, id,
data, data,
filterOptions, filterOptions,
@@ -546,7 +546,7 @@ export function validateBlocksFilterOptions({
}: { value: Parameters<BlocksFieldValidation>[0] } & Pick< }: { value: Parameters<BlocksFieldValidation>[0] } & Pick<
Parameters<BlocksFieldValidation>[1], Parameters<BlocksFieldValidation>[1],
'data' | 'filterOptions' | 'id' | 'req' | 'siblingData' 'data' | 'filterOptions' | 'id' | 'req' | 'siblingData'
>): { >): Promise<{
/** /**
* All block slugs found in the value of the blocks field * All block slugs found in the value of the blocks field
*/ */
@@ -559,7 +559,7 @@ export function validateBlocksFilterOptions({
* A list of block slugs that are used despite being disallowed. If undefined, field passed validation. * A list of block slugs that are used despite being disallowed. If undefined, field passed validation.
*/ */
invalidBlockSlugs: string[] | undefined invalidBlockSlugs: string[] | undefined
} { }> {
const allBlockSlugs = Array.isArray(value) const allBlockSlugs = Array.isArray(value)
? (value as Array<{ blockType?: string }>) ? (value as Array<{ blockType?: string }>)
.map((b) => b.blockType) .map((b) => b.blockType)
@@ -570,7 +570,7 @@ export function validateBlocksFilterOptions({
let allowedBlockSlugs: string[] | undefined = undefined let allowedBlockSlugs: string[] | undefined = undefined
if (typeof filterOptions === 'function') { if (typeof filterOptions === 'function') {
const result = filterOptions({ const result = await filterOptions({
id: id!, // original code asserted presence id: id!, // original code asserted presence
data, data,
req, req,
@@ -599,7 +599,7 @@ export function validateBlocksFilterOptions({
invalidBlockSlugs, invalidBlockSlugs,
} }
} }
export const blocks: BlocksFieldValidation = ( export const blocks: BlocksFieldValidation = async (
value, value,
{ id, data, filterOptions, maxRows, minRows, req: { t }, req, required, siblingData }, { id, data, filterOptions, maxRows, minRows, req: { t }, req, required, siblingData },
) => { ) => {
@@ -609,7 +609,7 @@ export const blocks: BlocksFieldValidation = (
} }
if (filterOptions) { if (filterOptions) {
const { invalidBlockSlugs } = validateBlocksFilterOptions({ const { invalidBlockSlugs } = await validateBlocksFilterOptions({
id, id,
data, data,
filterOptions, filterOptions,

View File

@@ -413,10 +413,11 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
const blocksValue = Array.isArray(data[field.name]) ? data[field.name] : [] const blocksValue = Array.isArray(data[field.name]) ? data[field.name] : []
// Handle blocks filterOptions // Handle blocks filterOptions
let filterOptionsValidationResult: null | ReturnType<typeof validateBlocksFilterOptions> = let filterOptionsValidationResult: Awaited<
null ReturnType<typeof validateBlocksFilterOptions>
> | null = null
if (field.filterOptions) { if (field.filterOptions) {
filterOptionsValidationResult = validateBlocksFilterOptions({ filterOptionsValidationResult = await validateBlocksFilterOptions({
id, id,
data: fullData, data: fullData,
filterOptions: field.filterOptions, filterOptions: field.filterOptions,