From 4717f4cb90899d64204a05464b19b6c48612ce28 Mon Sep 17 00:00:00 2001 From: Srikar Samudrala Date: Thu, 10 Aug 2023 10:55:57 +0530 Subject: [PATCH] fix(882): fixes negative number submission for employees input (#1130) * fix(882): fixes negative number submission for employees input * formatting * fix linting --- .../companies/constants/companyViewFields.tsx | 1 + .../ui/editable-field/types/ViewField.ts | 1 + .../GenericEditableNumberCellEditMode.tsx | 21 +++- .../cast-as-positive-integer-or-null.test.ts | 116 ++++++++++++++++++ .../utils/cast-as-positive-integer-or-null.ts | 64 ++++++++++ 5 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 front/src/utils/__tests__/cast-as-positive-integer-or-null.test.ts create mode 100644 front/src/utils/cast-as-positive-integer-or-null.ts diff --git a/front/src/modules/companies/constants/companyViewFields.tsx b/front/src/modules/companies/constants/companyViewFields.tsx index 6d842ab94..57b476160 100644 --- a/front/src/modules/companies/constants/companyViewFields.tsx +++ b/front/src/modules/companies/constants/companyViewFields.tsx @@ -81,6 +81,7 @@ export const companyViewFields: ViewFieldDefinition[] = [ metadata: { type: 'number', fieldName: 'employees', + isPositive: true, }, isVisible: true, } satisfies ViewFieldDefinition, diff --git a/front/src/modules/ui/editable-field/types/ViewField.ts b/front/src/modules/ui/editable-field/types/ViewField.ts index 695ecfdbb..7989dd9db 100644 --- a/front/src/modules/ui/editable-field/types/ViewField.ts +++ b/front/src/modules/ui/editable-field/types/ViewField.ts @@ -39,6 +39,7 @@ export type ViewFieldDateMetadata = { export type ViewFieldNumberMetadata = { type: 'number'; fieldName: string; + isPositive: boolean; }; export type ViewFieldRelationMetadata = { diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCellEditMode.tsx index 39b77ba2f..89db636a7 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCellEditMode.tsx @@ -7,6 +7,10 @@ import { import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; +import { + canBeCastAsPositiveIntegerOrNull, + castAsPositiveIntegerOrNull, +} from '~/utils/cast-as-positive-integer-or-null'; import { TextCellEdit } from './TextCellEdit'; @@ -31,12 +35,27 @@ export function GenericEditableNumberCellEditMode({ viewField }: OwnProps) { if (newText === fieldValue) return; try { - const numberValue = parseInt(newText); + let numberValue = parseInt(newText); if (isNaN(numberValue)) { throw new Error('Not a number'); } + if (viewField.metadata.isPositive) { + if (!canBeCastAsPositiveIntegerOrNull(newText)) { + return; + } + + const valueCastedAsPositiveNumberOrNull = + castAsPositiveIntegerOrNull(newText); + + if (valueCastedAsPositiveNumberOrNull === null) { + throw Error('Not a number'); + } + + numberValue = valueCastedAsPositiveNumberOrNull; + } + // TODO: find a way to store this better in DB if (numberValue > 2000000000) { throw new Error('Number too big'); diff --git a/front/src/utils/__tests__/cast-as-positive-integer-or-null.test.ts b/front/src/utils/__tests__/cast-as-positive-integer-or-null.test.ts new file mode 100644 index 000000000..1c3e9d10b --- /dev/null +++ b/front/src/utils/__tests__/cast-as-positive-integer-or-null.test.ts @@ -0,0 +1,116 @@ +import { + canBeCastAsPositiveIntegerOrNull, + castAsPositiveIntegerOrNull, +} from '~/utils/cast-as-positive-integer-or-null'; + +describe('canBeCastAsPositiveIntegerOrNull', () => { + it(`should return true if null`, () => { + expect(canBeCastAsPositiveIntegerOrNull(null)).toBeTruthy(); + }); + + it(`should return true if positive number`, () => { + expect(canBeCastAsPositiveIntegerOrNull(9)).toBeTruthy(); + }); + + it(`should return false if negative number`, () => { + expect(canBeCastAsPositiveIntegerOrNull(-9)).toBeFalsy(); + }); + + it(`should return false if zero`, () => { + expect(canBeCastAsPositiveIntegerOrNull(0)).toBeFalsy(); + }); + + it(`should return false if string 0`, () => { + expect(canBeCastAsPositiveIntegerOrNull('0')).toBeFalsy(); + }); + + it(`should return false if negative float`, () => { + expect(canBeCastAsPositiveIntegerOrNull(-1.22)).toBeFalsy(); + }); + + it(`should return false if positive float`, () => { + expect(canBeCastAsPositiveIntegerOrNull(1.22)).toBeFalsy(); + }); + + it(`should return false if positive float string`, () => { + expect(canBeCastAsPositiveIntegerOrNull('0.9')).toBeFalsy(); + }); + + it(`should return false if negative float string`, () => { + expect(canBeCastAsPositiveIntegerOrNull('-0.9')).toBeFalsy(); + }); + + it(`should return false if less than 1`, () => { + expect(canBeCastAsPositiveIntegerOrNull(0.22)).toBeFalsy(); + }); + + it(`should return true if empty string`, () => { + expect(canBeCastAsPositiveIntegerOrNull('')).toBeTruthy(); + }); + + it(`should return true if integer string`, () => { + expect(canBeCastAsPositiveIntegerOrNull('9')).toBeTruthy(); + }); + + it(`should return false if undefined`, () => { + expect(canBeCastAsPositiveIntegerOrNull(undefined)).toBeFalsy(); + }); + + it(`should return false if non numeric string`, () => { + expect(canBeCastAsPositiveIntegerOrNull('9a')).toBeFalsy(); + }); + + it(`should return false if non numeric string #2`, () => { + expect(canBeCastAsPositiveIntegerOrNull('a9a')).toBeFalsy(); + }); +}); + +describe('castAsPositiveIntegerOrNull', () => { + it(`should cast null to null`, () => { + expect(castAsPositiveIntegerOrNull(null)).toBe(null); + }); + + it(`should cast empty string to null`, () => { + expect(castAsPositiveIntegerOrNull('')).toBe(null); + }); + + it(`should cast an integer to positive integer`, () => { + expect(castAsPositiveIntegerOrNull(9)).toBe(9); + }); + + it(`should cast an integer string to positive integer`, () => { + expect(castAsPositiveIntegerOrNull('9')).toBe(9); + }); + + it(`should throw if trying to cast a 0 to positive integer`, () => { + expect(() => castAsPositiveIntegerOrNull(0)).toThrow(Error); + }); + + it(`should throw if trying to cast a string 0 to positive integer`, () => { + expect(() => castAsPositiveIntegerOrNull('0')).toThrow(Error); + }); + + it(`should throw if trying to cast a positive float string to positive integer`, () => { + expect(() => castAsPositiveIntegerOrNull('9.9')).toThrow(Error); + }); + + it(`should throw if trying to cast a negative float string to positive integer`, () => { + expect(() => castAsPositiveIntegerOrNull('-9.9')).toThrow(Error); + }); + + it(`should throw if trying to cast a positive float to positive integer`, () => { + expect(() => castAsPositiveIntegerOrNull(9.9)).toThrow(Error); + }); + + it(`should throw if trying to cast a negative float to positive integer`, () => { + expect(() => castAsPositiveIntegerOrNull(-9.9)).toThrow(Error); + }); + + it(`should throw if trying to cast a non numeric string to positive integer`, () => { + expect(() => castAsPositiveIntegerOrNull('9.9a')).toThrow(Error); + }); + + it(`should throw if trying to cast an undefined to positive integer`, () => { + expect(() => castAsPositiveIntegerOrNull(undefined)).toThrow(Error); + }); +}); diff --git a/front/src/utils/cast-as-positive-integer-or-null.ts b/front/src/utils/cast-as-positive-integer-or-null.ts new file mode 100644 index 000000000..56ad707fe --- /dev/null +++ b/front/src/utils/cast-as-positive-integer-or-null.ts @@ -0,0 +1,64 @@ +export function canBeCastAsPositiveIntegerOrNull( + probablePositiveNumberOrNull: string | undefined | number | null, +): probablePositiveNumberOrNull is number | null { + if (probablePositiveNumberOrNull === undefined) { + return false; + } + + if (typeof probablePositiveNumberOrNull === 'number') { + return ( + Number.isInteger(probablePositiveNumberOrNull) && + Math.sign(probablePositiveNumberOrNull) === 1 + ); + } + + if (probablePositiveNumberOrNull === null) { + return true; + } + + if (probablePositiveNumberOrNull === '') { + return true; + } + + if (typeof probablePositiveNumberOrNull === 'string') { + const stringAsNumber = +probablePositiveNumberOrNull; + + if (isNaN(stringAsNumber)) { + return false; + } + + if (Number.isInteger(stringAsNumber) && Math.sign(stringAsNumber) === 1) { + return true; + } + } + + return false; +} + +export function castAsPositiveIntegerOrNull( + probablePositiveNumberOrNull: string | undefined | number | null, +): number | null { + if ( + canBeCastAsPositiveIntegerOrNull(probablePositiveNumberOrNull) === false + ) { + throw new Error('Cannot cast to positive number or null'); + } + + if (probablePositiveNumberOrNull === null) { + return null; + } + + if (probablePositiveNumberOrNull === '') { + return null; + } + + if (typeof probablePositiveNumberOrNull === 'number') { + return probablePositiveNumberOrNull; + } + + if (typeof probablePositiveNumberOrNull === 'string') { + return +probablePositiveNumberOrNull; + } + + return null; +}