diff --git a/packages/dataviews/src/field-types/index.tsx b/packages/dataviews/src/field-types/index.tsx index 1ca5a12bd23de0..1075858c6e0669 100644 --- a/packages/dataviews/src/field-types/index.tsx +++ b/packages/dataviews/src/field-types/index.tsx @@ -2,7 +2,7 @@ * Internal dependencies */ import { default as integer } from './integer'; -import type { FieldType } from '../types'; +import type { FieldType, ValidationContext } from '../types'; /** * @@ -15,8 +15,17 @@ export default function getFieldTypeDefinition( type?: FieldType ) { return integer; } - // If no type found, the sort function doesn't do anything. return { sort: () => 0, + isValid: ( value: any, context?: ValidationContext ) => { + if ( context?.elements ) { + const validValues = context?.elements?.map( ( f ) => f.value ); + if ( ! validValues.includes( value ) ) { + return false; + } + } + + return true; + }, }; } diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx index 36d0b445d6275f..eaa90e8f1e4e26 100644 --- a/packages/dataviews/src/field-types/integer.tsx +++ b/packages/dataviews/src/field-types/integer.tsx @@ -1,12 +1,33 @@ /** * Internal dependencies */ -import type { SortDirection } from '../types'; +import type { SortDirection, ValidationContext } from '../types'; function sort( a: any, b: any, direction: SortDirection ) { return direction === 'asc' ? a - b : b - a; } +function isValid( value: any, context?: ValidationContext ) { + // TODO: this implicitely means the value is required. + if ( value === '' ) { + return false; + } + + if ( ! Number.isInteger( Number( value ) ) ) { + return false; + } + + if ( context?.elements ) { + const validValues = context?.elements.map( ( f ) => f.value ); + if ( ! validValues.includes( Number( value ) ) ) { + return false; + } + } + + return true; +} + export default { sort, + isValid, }; diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 6cb36de1029005..8d08b08715c9a3 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -30,12 +30,22 @@ export function normalizeFields< Item >( ); }; + const isValid = + field.isValid ?? + function isValid( item, context ) { + return fieldTypeDefinition.isValid( + getValue( { item } ), + context + ); + }; + return { ...field, label: field.label || field.id, getValue, render: field.render || getValue, sort, + isValid, }; } ); } diff --git a/packages/dataviews/src/test/validation.ts b/packages/dataviews/src/test/validation.ts index ded002e5bc042e..4f3fb2c922433e 100644 --- a/packages/dataviews/src/test/validation.ts +++ b/packages/dataviews/src/test/validation.ts @@ -61,7 +61,7 @@ describe( 'validation', () => { expect( result ).toBe( false ); } ); - it( 'field is invalid if value is not one of the elements', () => { + it( 'integer field is invalid if value is not one of the elements', () => { const item = { id: 1, author: 3 }; const fields: Field< {} >[] = [ { @@ -77,4 +77,38 @@ describe( 'validation', () => { const result = isItemValid( item, fields, form ); expect( result ).toBe( false ); } ); + + it( 'untyped field is invalid if value is not one of the elements', () => { + const item = { id: 1, author: 'not-in-elements' }; + const fields: Field< {} >[] = [ + { + id: 'author', + elements: [ + { value: 'jane', label: 'Jane' }, + { value: 'john', label: 'John' }, + ], + }, + ]; + const form = { visibleFields: [ 'author' ] }; + const result = isItemValid( item, fields, form ); + expect( result ).toBe( false ); + } ); + + it( 'fields can provide its own isValid function', () => { + const item = { id: 1, order: 'd' }; + const fields: Field< {} >[] = [ + { + id: 'order', + type: 'integer', + elements: [ + { value: 'a', label: 'A' }, + { value: 'b', label: 'B' }, + ], + isValid: () => true, // Overrides the validation provided for integer types. + }, + ]; + const form = { visibleFields: [ 'order' ] }; + const result = isItemValid( item, fields, form ); + expect( result ).toBe( true ); + } ); } ); diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index f410c18aa43699..7029db7d17b1af 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -46,6 +46,10 @@ export type ItemRecord = Record< string, unknown >; export type FieldType = 'text' | 'integer'; +export type ValidationContext = { + elements?: Option[]; +}; + /** * A dataview field for a specific property of a data type. */ @@ -85,6 +89,11 @@ export type Field< Item > = { */ sort?: ( a: Item, b: Item, direction: SortDirection ) => number; + /** + * Callback used to validate the field. + */ + isValid?: ( item: Item, context?: ValidationContext ) => boolean; + /** * Whether the field is sortable. */ @@ -130,6 +139,7 @@ export type NormalizedField< Item > = Field< Item > & { getValue: ( args: { item: Item } ) => any; render: ComponentType< { item: Item } >; sort: ( a: Item, b: Item, direction: SortDirection ) => number; + isValid: ( item: Item, context?: ValidationContext ) => boolean; }; /** diff --git a/packages/dataviews/src/validation.ts b/packages/dataviews/src/validation.ts index a6d3515fe6e57e..e350be68d54219 100644 --- a/packages/dataviews/src/validation.ts +++ b/packages/dataviews/src/validation.ts @@ -13,28 +13,6 @@ export function isItemValid< Item >( fields.filter( ( { id } ) => !! form.visibleFields?.includes( id ) ) ); return _fields.every( ( field ) => { - const value = field.getValue( { item } ); - - // TODO: this implicitely means the value is required. - if ( field.type === 'integer' && value === '' ) { - return false; - } - - if ( - field.type === 'integer' && - ! Number.isInteger( Number( value ) ) - ) { - return false; - } - - if ( field.elements ) { - const validValues = field.elements.map( ( f ) => f.value ); - if ( ! validValues.includes( Number( value ) ) ) { - return false; - } - } - - // Nothing to validate. - return true; + return field.isValid( item, { elements: field.elements } ); } ); }