Skip to content

Commit

Permalink
DataForm: move validation logic to the field type definition (#64164)
Browse files Browse the repository at this point in the history
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
  • Loading branch information
3 people authored Aug 1, 2024
1 parent 1c3e7be commit a47a25d
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 27 deletions.
13 changes: 11 additions & 2 deletions packages/dataviews/src/field-types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Internal dependencies
*/
import { default as integer } from './integer';
import type { FieldType } from '../types';
import type { FieldType, ValidationContext } from '../types';

/**
*
Expand All @@ -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;
},
};
}
23 changes: 22 additions & 1 deletion packages/dataviews/src/field-types/integer.tsx
Original file line number Diff line number Diff line change
@@ -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,
};
10 changes: 10 additions & 0 deletions packages/dataviews/src/normalize-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
} );
}
36 changes: 35 additions & 1 deletion packages/dataviews/src/test/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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< {} >[] = [
{
Expand All @@ -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 );
} );
} );
10 changes: 10 additions & 0 deletions packages/dataviews/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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;
};

/**
Expand Down
24 changes: 1 addition & 23 deletions packages/dataviews/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } );
} );
}

0 comments on commit a47a25d

Please sign in to comment.