Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to properly cast a string, number, null to an integer #990

Merged
merged 1 commit into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { IconUsers } from '@/ui/icon';
import { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { Company, useUpdateOneCompanyMutation } from '~/generated/graphql';
import {
canBeCastAsIntegerOrNull,
castAsIntegerOrNull,
} from '~/utils/cast-as-integer-or-null';

type OwnProps = {
company: Pick<Company, 'id' | 'employees'>;
Expand All @@ -27,30 +31,25 @@ export function CompanyEmployeesEditableField({ company }: OwnProps) {
}

async function handleSubmit() {
if (!internalValue) return;

try {
const numberValue = parseInt(internalValue);
if (!canBeCastAsIntegerOrNull(internalValue)) {
handleCancel();
return;
}

if (isNaN(numberValue)) {
throw new Error('Not a number');
}
const valueCastedAsNumberOrNull = castAsIntegerOrNull(internalValue);

await updateCompany({
variables: {
where: {
id: company.id,
},
data: {
employees: numberValue,
},
await updateCompany({
variables: {
where: {
id: company.id,
},
});
data: {
employees: valueCastedAsNumberOrNull,
},
},
});

setInternalValue(numberValue.toString());
} catch {
handleCancel();
}
setInternalValue(valueCastedAsNumberOrNull?.toString());
}

async function handleCancel() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import {
canBeCastAsIntegerOrNull,
castAsIntegerOrNull,
} from '~/utils/cast-as-integer-or-null';

type OwnProps = {
icon?: React.ReactNode;
placeholder?: string;
value: number | null | undefined;
onSubmit?: (newValue: number) => void;
onSubmit?: (newValue: number | null) => void;
};

export function NumberEditableField({
Expand All @@ -29,26 +33,16 @@ export function NumberEditableField({
}

async function handleSubmit() {
if (!internalValue) return;

try {
const numberValue = parseInt(internalValue);

if (isNaN(numberValue)) {
throw new Error('Not a number');
}
if (!canBeCastAsIntegerOrNull(internalValue)) {
handleCancel();
return;
}

// TODO: find a way to store this better in DB
if (numberValue > 2000000000) {
throw new Error('Number too big');
}
const valueCastedAsNumberOrNull = castAsIntegerOrNull(internalValue);

onSubmit?.(numberValue);
onSubmit?.(valueCastedAsNumberOrNull);

setInternalValue(numberValue.toString());
} catch {
handleCancel();
}
setInternalValue(valueCastedAsNumberOrNull?.toString());
}

async function handleCancel() {
Expand Down
72 changes: 72 additions & 0 deletions front/src/utils/__tests__/cast-as-integer-or-null.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
canBeCastAsIntegerOrNull,
castAsIntegerOrNull,
} from '../cast-as-integer-or-null';

describe('canBeCastAsIntegerOrNull', () => {
it(`should return true if null`, () => {
expect(canBeCastAsIntegerOrNull(null)).toBeTruthy();
});

it(`should return true if number`, () => {
expect(canBeCastAsIntegerOrNull(9)).toBeTruthy();
});

it(`should return true if empty string`, () => {
expect(canBeCastAsIntegerOrNull('')).toBeTruthy();
});

it(`should return true if integer string`, () => {
expect(canBeCastAsIntegerOrNull('9')).toBeTruthy();
});

it(`should return false if undefined`, () => {
expect(canBeCastAsIntegerOrNull(undefined)).toBeFalsy();
});

it(`should return false if non numeric string`, () => {
expect(canBeCastAsIntegerOrNull('9a')).toBeFalsy();
});

it(`should return false if non numeric string #2`, () => {
expect(canBeCastAsIntegerOrNull('a9a')).toBeFalsy();
});

it(`should return false if float`, () => {
expect(canBeCastAsIntegerOrNull(0.9)).toBeFalsy();
});

it(`should return false if float string`, () => {
expect(canBeCastAsIntegerOrNull('0.9')).toBeFalsy();
});
});

describe('castAsIntegerOrNull', () => {
it(`should cast null to null`, () => {
expect(castAsIntegerOrNull(null)).toBe(null);
});

it(`should cast empty string to null`, () => {
expect(castAsIntegerOrNull('')).toBe(null);
});

it(`should cast an integer to an integer`, () => {
expect(castAsIntegerOrNull(9)).toBe(9);
});

it(`should cast an integer string to an integer`, () => {
expect(castAsIntegerOrNull('9')).toBe(9);
});

it(`should throw if trying to cast a float string to an integer`, () => {
expect(() => castAsIntegerOrNull('9.9')).toThrow(Error);
});

it(`should throw if trying to cast a non numeric string to an integer`, () => {
expect(() => castAsIntegerOrNull('9.9a')).toThrow(Error);
});

it(`should throw if trying to cast an undefined to an integer`, () => {
expect(() => castAsIntegerOrNull(undefined)).toThrow(Error);
});
});
58 changes: 58 additions & 0 deletions front/src/utils/cast-as-integer-or-null.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export function canBeCastAsIntegerOrNull(
probableNumberOrNull: string | undefined | number | null,
): probableNumberOrNull is number | null {
if (probableNumberOrNull === undefined) {
return false;
}

if (typeof probableNumberOrNull === 'number') {
return Number.isInteger(probableNumberOrNull);
}

if (probableNumberOrNull === null) {
return true;
}

if (probableNumberOrNull === '') {
return true;
}

if (typeof probableNumberOrNull === 'string') {
const stringAsNumber = +probableNumberOrNull;

if (isNaN(stringAsNumber)) {
return false;
}
if (Number.isInteger(stringAsNumber)) {
return true;
}
}

return false;
}

export function castAsIntegerOrNull(
probableNumberOrNull: string | undefined | number | null,
): number | null {
if (canBeCastAsIntegerOrNull(probableNumberOrNull) === false) {
throw new Error('Cannot cast to number or null');
}

if (probableNumberOrNull === null) {
return null;
}

if (probableNumberOrNull === '') {
return null;
}

if (typeof probableNumberOrNull === 'number') {
return probableNumberOrNull;
}

if (typeof probableNumberOrNull === 'string') {
return +probableNumberOrNull;
}

return null;
}