Merge pull request #12972 from strapi/fix/cm-list-number

CM: Fix displaying number fields in the DynamicTable
This commit is contained in:
Gustav Hansen 2022-03-29 16:31:20 +02:00 committed by GitHub
commit f05ce9b317
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 285 additions and 124 deletions

View File

@ -1,11 +1,13 @@
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import isNumber from 'lodash/isNumber';
import isSingleRelation from './isSingleRelation'; import isSingleRelation from './isSingleRelation';
import isFieldTypeNumber from '../../../../utils/isFieldTypeNumber';
export default function hasContent(type, content, metadatas, fieldSchema) { export default function hasContent(type, content, metadatas, fieldSchema) {
if (type === 'component') { if (type === 'component') {
const { const {
mainField: { name: mainFieldName }, mainField: { name: mainFieldName, type: mainFieldType },
} = metadatas; } = metadatas;
// Repeatable fields show the ID as fallback, in case the mainField // Repeatable fields show the ID as fallback, in case the mainField
@ -14,7 +16,23 @@ export default function hasContent(type, content, metadatas, fieldSchema) {
return content.length > 0; return content.length > 0;
} }
return !isEmpty(content[mainFieldName]); const value = content[mainFieldName];
/* The ID field reports itself as type `integer`, which makes it
impossible to distinguish it from other number fields.
Biginteger fields need to be treated as strings, as `isNumber`
doesn't deal with them.
*/
if (
isFieldTypeNumber(mainFieldType) &&
mainFieldType !== 'biginteger' &&
mainFieldName !== 'id'
) {
return isNumber(value);
}
return !isEmpty(value);
} }
if (type === 'relation') { if (type === 'relation') {
@ -25,5 +43,13 @@ export default function hasContent(type, content, metadatas, fieldSchema) {
return content.count > 0; return content.count > 0;
} }
/*
Biginteger fields need to be treated as strings, as `isNumber`
doesn't deal with them.
*/
if (isFieldTypeNumber(type) && type !== 'biginteger') {
return isNumber(content);
}
return !isEmpty(content); return !isEmpty(content);
} }

View File

@ -1,6 +1,39 @@
import hasContent from '../hasContent'; import hasContent from '../hasContent';
describe('hasContent', () => { describe('hasContent', () => {
describe('number fields', () => {
it('returns true for integer', () => {
const normalizedContent = hasContent('integer', 1);
expect(normalizedContent).toEqual(true);
});
it('returns false for string integer', () => {
const normalizedContent = hasContent('integer', '1');
expect(normalizedContent).toEqual(false);
});
it('returns false for undefined text', () => {
const normalizedContent = hasContent('integer', undefined);
expect(normalizedContent).toEqual(false);
});
it('returns true for float', () => {
const normalizedContent = hasContent('float', 1.111);
expect(normalizedContent).toEqual(true);
});
it('returns true for decimal', () => {
const normalizedContent = hasContent('decimal', 1.111);
expect(normalizedContent).toEqual(true);
});
it('returns true for biginteger', () => {
const normalizedContent = hasContent('biginteger', '12345678901234567890');
expect(normalizedContent).toEqual(true);
});
});
describe('text', () => {
it('returns true for text content', () => { it('returns true for text content', () => {
const normalizedContent = hasContent('text', 'content'); const normalizedContent = hasContent('text', 'content');
expect(normalizedContent).toEqual(true); expect(normalizedContent).toEqual(true);
@ -15,7 +48,9 @@ describe('hasContent', () => {
const normalizedContent = hasContent('text', undefined); const normalizedContent = hasContent('text', undefined);
expect(normalizedContent).toEqual(false); expect(normalizedContent).toEqual(false);
}); });
});
describe('single component', () => {
it('extracts content from single components with content', () => { it('extracts content from single components with content', () => {
const normalizedContent = hasContent( const normalizedContent = hasContent(
'component', 'component',
@ -34,6 +69,80 @@ describe('hasContent', () => {
expect(normalizedContent).toEqual(false); expect(normalizedContent).toEqual(false);
}); });
it('extracts integers from single components with content', () => {
const normalizedContent = hasContent(
'component',
{ number: 1, id: 1 },
{ mainField: { name: 'number', type: 'integer' } }
);
expect(normalizedContent).toEqual(true);
});
it('extracts integers from single components without content', () => {
const normalizedContent = hasContent(
'component',
{ number: null, id: 1 },
{ mainField: { name: 'number', type: 'integer' } }
);
expect(normalizedContent).toEqual(false);
});
it('extracts float from single components with content', () => {
const normalizedContent = hasContent(
'component',
{ number: 1.11, id: 1 },
{ mainField: { name: 'number', type: 'float' } }
);
expect(normalizedContent).toEqual(true);
});
it('extracts float from single components without content', () => {
const normalizedContent = hasContent(
'component',
{ number: null, id: 1 },
{ mainField: { name: 'number', type: 'float' } }
);
expect(normalizedContent).toEqual(false);
});
it('extracts decimal from single components with content', () => {
const normalizedContent = hasContent(
'component',
{ number: 1.11, id: 1 },
{ mainField: { name: 'number', type: 'decimal' } }
);
expect(normalizedContent).toEqual(true);
});
it('extracts decimal from single components without content', () => {
const normalizedContent = hasContent(
'component',
{ number: null, id: 1 },
{ mainField: { name: 'number', type: 'decimal' } }
);
expect(normalizedContent).toEqual(false);
});
it('extracts biginteger from single components with content', () => {
const normalizedContent = hasContent(
'component',
{ number: '12345678901234567890', id: 1 },
{ mainField: { name: 'number', type: 'biginteger' } }
);
expect(normalizedContent).toEqual(true);
});
it('extracts biginteger from single components without content', () => {
const normalizedContent = hasContent(
'component',
{ number: null, id: 1 },
{ mainField: { name: 'number', type: 'biginteger' } }
);
expect(normalizedContent).toEqual(false);
});
});
describe('repeatable components', () => {
it('extracts content from repeatable components with content', () => { it('extracts content from repeatable components with content', () => {
const normalizedContent = hasContent( const normalizedContent = hasContent(
'component', 'component',
@ -73,7 +182,9 @@ describe('hasContent', () => {
); );
expect(normalizedContent).toEqual(false); expect(normalizedContent).toEqual(false);
}); });
});
describe('relations', () => {
it('extracts content from multiple relations with content', () => { it('extracts content from multiple relations with content', () => {
const normalizedContent = hasContent('relation', { count: 1 }, undefined, { const normalizedContent = hasContent('relation', { count: 1 }, undefined, {
relation: 'manyToMany', relation: 'manyToMany',
@ -122,4 +233,5 @@ describe('hasContent', () => {
}); });
expect(normalizedContent).toEqual(false); expect(normalizedContent).toEqual(false);
}); });
});
}); });

View File

@ -7,12 +7,14 @@ import toNumber from 'lodash/toNumber';
import * as yup from 'yup'; import * as yup from 'yup';
import { translatedErrors as errorsTrads } from '@strapi/helper-plugin'; import { translatedErrors as errorsTrads } from '@strapi/helper-plugin';
import isFieldTypeNumber from '../../../utils/isFieldTypeNumber';
yup.addMethod(yup.mixed, 'defined', function() { yup.addMethod(yup.mixed, 'defined', function() {
return this.test('defined', errorsTrads.required, value => value !== undefined); return this.test('defined', errorsTrads.required, (value) => value !== undefined);
}); });
yup.addMethod(yup.array, 'notEmptyMin', function(min) { yup.addMethod(yup.array, 'notEmptyMin', function(min) {
return this.test('notEmptyMin', errorsTrads.min, value => { return this.test('notEmptyMin', errorsTrads.min, (value) => {
if (isEmpty(value)) { if (isEmpty(value)) {
return true; return true;
} }
@ -49,7 +51,7 @@ yup.addMethod(yup.string, 'isSuperior', function(message, min) {
}); });
}); });
const getAttributes = data => get(data, ['attributes'], {}); const getAttributes = (data) => get(data, ['attributes'], {});
const createYupSchema = ( const createYupSchema = (
model, model,
@ -95,7 +97,7 @@ const createYupSchema = (
if (attribute.repeatable === true) { if (attribute.repeatable === true) {
const { min, max, required } = attribute; const { min, max, required } = attribute;
let componentSchema = yup.lazy(value => { let componentSchema = yup.lazy((value) => {
let baseSchema = yup.array().of(componentFieldSchema); let baseSchema = yup.array().of(componentFieldSchema);
if (min) { if (min) {
@ -121,7 +123,7 @@ const createYupSchema = (
return acc; return acc;
} }
const componentSchema = yup.lazy(obj => { const componentSchema = yup.lazy((obj) => {
if (obj !== undefined) { if (obj !== undefined) {
return attribute.required === true && !options.isDraft return attribute.required === true && !options.isDraft
? componentFieldSchema.defined() ? componentFieldSchema.defined()
@ -152,7 +154,7 @@ const createYupSchema = (
if (min) { if (min) {
if (attribute.required) { if (attribute.required) {
dynamicZoneSchema = dynamicZoneSchema dynamicZoneSchema = dynamicZoneSchema
.test('min', errorsTrads.min, value => { .test('min', errorsTrads.min, (value) => {
if (options.isCreatingEntry) { if (options.isCreatingEntry) {
return value && value.length >= min; return value && value.length >= min;
} }
@ -163,7 +165,7 @@ const createYupSchema = (
return value !== null && value.length >= min; return value !== null && value.length >= min;
}) })
.test('required', errorsTrads.required, value => { .test('required', errorsTrads.required, (value) => {
if (options.isCreatingEntry) { if (options.isCreatingEntry) {
return value !== null || value !== undefined; return value !== null || value !== undefined;
} }
@ -178,7 +180,7 @@ const createYupSchema = (
dynamicZoneSchema = dynamicZoneSchema.notEmptyMin(min); dynamicZoneSchema = dynamicZoneSchema.notEmptyMin(min);
} }
} else if (attribute.required && !options.isDraft) { } else if (attribute.required && !options.isDraft) {
dynamicZoneSchema = dynamicZoneSchema.test('required', errorsTrads.required, value => { dynamicZoneSchema = dynamicZoneSchema.test('required', errorsTrads.required, (value) => {
if (options.isCreatingEntry) { if (options.isCreatingEntry) {
return value !== null || value !== undefined; return value !== null || value !== undefined;
} }
@ -213,7 +215,7 @@ const createYupSchemaAttribute = (type, validations, options) => {
if (type === 'json') { if (type === 'json') {
schema = yup schema = yup
.mixed(errorsTrads.json) .mixed(errorsTrads.json)
.test('isJSON', errorsTrads.json, value => { .test('isJSON', errorsTrads.json, (value) => {
if (value === undefined) { if (value === undefined) {
return true; return true;
} }
@ -236,19 +238,19 @@ const createYupSchemaAttribute = (type, validations, options) => {
if (['number', 'integer', 'float', 'decimal'].includes(type)) { if (['number', 'integer', 'float', 'decimal'].includes(type)) {
schema = yup schema = yup
.number() .number()
.transform(cv => (isNaN(cv) ? undefined : cv)) .transform((cv) => (isNaN(cv) ? undefined : cv))
.typeError(); .typeError();
} }
if (['date', 'datetime'].includes(type)) {
schema = yup.date();
}
if (type === 'biginteger') { if (type === 'biginteger') {
schema = yup.string().matches(/^-?\d*$/); schema = yup.string().matches(/^-?\d*$/);
} }
Object.keys(validations).forEach(validation => { if (['date', 'datetime'].includes(type)) {
schema = yup.date();
}
Object.keys(validations).forEach((validation) => {
const validationValue = validations[validation]; const validationValue = validations[validation];
if ( if (
@ -267,13 +269,13 @@ const createYupSchemaAttribute = (type, validations, options) => {
if (options.isCreatingEntry) { if (options.isCreatingEntry) {
schema = schema.required(errorsTrads.required); schema = schema.required(errorsTrads.required);
} else { } else {
schema = schema.test('required', errorsTrads.required, value => { schema = schema.test('required', errorsTrads.required, (value) => {
// Field is not touched and the user is editing the entry // Field is not touched and the user is editing the entry
if (value === undefined && !options.isFromComponent) { if (value === undefined && !options.isFromComponent) {
return true; return true;
} }
if (['number', 'integer', 'biginteger', 'float', 'decimal'].includes(type)) { if (isFieldTypeNumber(type)) {
if (value === 0) { if (value === 0) {
return true; return true;
} }
@ -344,12 +346,12 @@ const createYupSchemaAttribute = (type, validations, options) => {
} }
break; break;
case 'positive': case 'positive':
if (['number', 'integer', 'bigint', 'float', 'decimal'].includes(type)) { if (isFieldTypeNumber(type)) {
schema = schema.positive(); schema = schema.positive();
} }
break; break;
case 'negative': case 'negative':
if (['number', 'integer', 'bigint', 'float', 'decimal'].includes(type)) { if (isFieldTypeNumber(type)) {
schema = schema.negative(); schema = schema.negative();
} }
break; break;

View File

@ -0,0 +1,3 @@
export default function isFieldTypeNumber(type) {
return ['integer', 'biginteger', 'decimal', 'float', 'number'].includes(type);
}

View File

@ -0,0 +1,18 @@
import isFieldTypeNumber from '../isFieldTypeNumber';
const FIXTURE = [
['integer', true],
['float', true],
['decimal', true],
['biginteger', true],
['number', true],
['text', false],
];
describe('isFieldTypeNumber', () => {
FIXTURE.forEach(([type, expectation]) => {
test(`${type} is ${expectation}`, () => {
expect(isFieldTypeNumber(type)).toBe(expectation);
});
});
});