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 isNumber from 'lodash/isNumber';
import isSingleRelation from './isSingleRelation';
import isFieldTypeNumber from '../../../../utils/isFieldTypeNumber';
export default function hasContent(type, content, metadatas, fieldSchema) {
if (type === 'component') {
const {
mainField: { name: mainFieldName },
mainField: { name: mainFieldName, type: mainFieldType },
} = metadatas;
// 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 !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') {
@ -25,5 +43,13 @@ export default function hasContent(type, content, metadatas, fieldSchema) {
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);
}

View File

@ -1,6 +1,39 @@
import hasContent from '../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', () => {
const normalizedContent = hasContent('text', 'content');
expect(normalizedContent).toEqual(true);
@ -15,7 +48,9 @@ describe('hasContent', () => {
const normalizedContent = hasContent('text', undefined);
expect(normalizedContent).toEqual(false);
});
});
describe('single component', () => {
it('extracts content from single components with content', () => {
const normalizedContent = hasContent(
'component',
@ -34,6 +69,80 @@ describe('hasContent', () => {
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', () => {
const normalizedContent = hasContent(
'component',
@ -73,7 +182,9 @@ describe('hasContent', () => {
);
expect(normalizedContent).toEqual(false);
});
});
describe('relations', () => {
it('extracts content from multiple relations with content', () => {
const normalizedContent = hasContent('relation', { count: 1 }, undefined, {
relation: 'manyToMany',
@ -122,4 +233,5 @@ describe('hasContent', () => {
});
expect(normalizedContent).toEqual(false);
});
});
});

View File

@ -7,12 +7,14 @@ import toNumber from 'lodash/toNumber';
import * as yup from 'yup';
import { translatedErrors as errorsTrads } from '@strapi/helper-plugin';
import isFieldTypeNumber from '../../../utils/isFieldTypeNumber';
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) {
return this.test('notEmptyMin', errorsTrads.min, value => {
return this.test('notEmptyMin', errorsTrads.min, (value) => {
if (isEmpty(value)) {
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 = (
model,
@ -95,7 +97,7 @@ const createYupSchema = (
if (attribute.repeatable === true) {
const { min, max, required } = attribute;
let componentSchema = yup.lazy(value => {
let componentSchema = yup.lazy((value) => {
let baseSchema = yup.array().of(componentFieldSchema);
if (min) {
@ -121,7 +123,7 @@ const createYupSchema = (
return acc;
}
const componentSchema = yup.lazy(obj => {
const componentSchema = yup.lazy((obj) => {
if (obj !== undefined) {
return attribute.required === true && !options.isDraft
? componentFieldSchema.defined()
@ -152,7 +154,7 @@ const createYupSchema = (
if (min) {
if (attribute.required) {
dynamicZoneSchema = dynamicZoneSchema
.test('min', errorsTrads.min, value => {
.test('min', errorsTrads.min, (value) => {
if (options.isCreatingEntry) {
return value && value.length >= min;
}
@ -163,7 +165,7 @@ const createYupSchema = (
return value !== null && value.length >= min;
})
.test('required', errorsTrads.required, value => {
.test('required', errorsTrads.required, (value) => {
if (options.isCreatingEntry) {
return value !== null || value !== undefined;
}
@ -178,7 +180,7 @@ const createYupSchema = (
dynamicZoneSchema = dynamicZoneSchema.notEmptyMin(min);
}
} else if (attribute.required && !options.isDraft) {
dynamicZoneSchema = dynamicZoneSchema.test('required', errorsTrads.required, value => {
dynamicZoneSchema = dynamicZoneSchema.test('required', errorsTrads.required, (value) => {
if (options.isCreatingEntry) {
return value !== null || value !== undefined;
}
@ -213,7 +215,7 @@ const createYupSchemaAttribute = (type, validations, options) => {
if (type === 'json') {
schema = yup
.mixed(errorsTrads.json)
.test('isJSON', errorsTrads.json, value => {
.test('isJSON', errorsTrads.json, (value) => {
if (value === undefined) {
return true;
}
@ -236,19 +238,19 @@ const createYupSchemaAttribute = (type, validations, options) => {
if (['number', 'integer', 'float', 'decimal'].includes(type)) {
schema = yup
.number()
.transform(cv => (isNaN(cv) ? undefined : cv))
.transform((cv) => (isNaN(cv) ? undefined : cv))
.typeError();
}
if (['date', 'datetime'].includes(type)) {
schema = yup.date();
}
if (type === 'biginteger') {
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];
if (
@ -267,13 +269,13 @@ const createYupSchemaAttribute = (type, validations, options) => {
if (options.isCreatingEntry) {
schema = schema.required(errorsTrads.required);
} 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
if (value === undefined && !options.isFromComponent) {
return true;
}
if (['number', 'integer', 'biginteger', 'float', 'decimal'].includes(type)) {
if (isFieldTypeNumber(type)) {
if (value === 0) {
return true;
}
@ -344,12 +346,12 @@ const createYupSchemaAttribute = (type, validations, options) => {
}
break;
case 'positive':
if (['number', 'integer', 'bigint', 'float', 'decimal'].includes(type)) {
if (isFieldTypeNumber(type)) {
schema = schema.positive();
}
break;
case 'negative':
if (['number', 'integer', 'bigint', 'float', 'decimal'].includes(type)) {
if (isFieldTypeNumber(type)) {
schema = schema.negative();
}
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);
});
});
});