Skip to content

Commit

Permalink
Merge pull request #2192 from AntaresSimulatorTeam/feature/add-utils-…
Browse files Browse the repository at this point in the history
…tests

test(ui-utils): add tests for validation utils & refactor imports
  • Loading branch information
hdinia authored Oct 18, 2024
2 parents c6f4692 + 43429b6 commit 67c2d14
Show file tree
Hide file tree
Showing 28 changed files with 523 additions and 171 deletions.
2 changes: 2 additions & 0 deletions webapp/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,3 +659,5 @@ export interface TaskView {
type: TaskType;
status: string;
}

export type ValidationReturn = string | true;
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { getGroups, getUsers } from "../../../../../../services/api/user";
import { getAuthUser } from "../../../../../../redux/selectors";
import useAppSelector from "../../../../../../redux/hooks/useAppSelector";
import { UseFormReturnPlus } from "../../../../../common/Form/types";
import { validateString } from "../../../../../../utils/validationUtils";
import { validateString } from "@/utils/validation/string";

function GroupForm(props: UseFormReturnPlus) {
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@ import usePromise from "../../../../../../hooks/usePromise";
import { getGroups, getUsers } from "../../../../../../services/api/user";
import { UserFormDialogProps } from ".";
import { UseFormReturnPlus } from "../../../../../common/Form/types";
import {
validatePassword,
validateString,
} from "../../../../../../utils/validationUtils";
import { validatePassword, validateString } from "@/utils/validation/string";

interface Props extends UseFormReturnPlus {
onlyPermissions?: UserFormDialogProps["onlyPermissions"];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import StringFE from "../../../../common/fieldEditors/StringFE";
import Fieldset from "../../../../common/Fieldset";
import SelectFE from "../../../../common/fieldEditors/SelectFE";
import { SubmitHandlerPlus } from "../../../../common/Form/types";
import { validateString } from "../../../../../utils/validationUtils";
import { validateString } from "@/utils/validation/string";

interface Props {
parentId: string;
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/components/App/Singlestudy/PropertiesDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import Fieldset from "../../common/Fieldset";
import { SubmitHandlerPlus } from "../../common/Form/types";
import useAppDispatch from "../../../redux/hooks/useAppDispatch";
import { updateStudy } from "../../../redux/ducks/studies";
import { validateString } from "../../../utils/validationUtils";
import { validateString } from "@/utils/validation/string";

const logErr = debug("antares:createstudyform:error");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Fieldset from "../../../../../common/Fieldset";
import { useFormContextPlus } from "../../../../../common/Form";
import { AdequacyPatchFormFields, PRICE_TAKING_ORDER_OPTIONS } from "./utils";
import { StudyMetadata } from "../../../../../../common/types";
import { validateNumber } from "../../../../../../utils/validationUtils";
import { validateNumber } from "@/utils/validation/number";

function Fields() {
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { FieldArrayWithId } from "react-hook-form";
import NumberFE from "../../../../../../../common/fieldEditors/NumberFE";
import { useFormContextPlus } from "../../../../../../../common/Form";
import { AllocationFormFields } from "./utils";
import { validateNumber } from "../../../../../../../../utils/validationUtils";
import { validateNumber } from "@/utils/validation/number";

interface Props {
field: FieldArrayWithId<AllocationFormFields, "allocation">;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { CorrelationFormFields } from "./utils";
import { useFormContextPlus } from "../../../../../../../common/Form";
import useAppSelector from "../../../../../../../../redux/hooks/useAppSelector";
import { getCurrentArea } from "../../../../../../../../redux/selectors";
import { validateNumber } from "../../../../../../../../utils/validationUtils";
import { validateNumber } from "@/utils/validation/number";

interface Props {
field: FieldArrayWithId<CorrelationFormFields, "correlation">;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { useFormContextPlus } from "../../../../../../common/Form";
import { STORAGE_GROUPS, Storage } from "./utils";
import { useOutletContext } from "react-router";
import { StudyMetadata } from "../../../../../../../common/types";
import { validateNumber } from "../../../../../../../utils/validationUtils";
import { validateNumber } from "@/utils/validation/number";

function Fields() {
const [t] = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
TS_GENERATION_OPTIONS,
TS_LAW_OPTIONS,
} from "./utils";
import { validateNumber } from "../../../../../../../utils/validationUtils";
import { validateNumber } from "@/utils/validation/number";

function Fields() {
const [t] = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ import SelectFE from "../../../../../common/fieldEditors/SelectFE";
import StringFE from "../../../../../common/fieldEditors/StringFE";
import SwitchFE from "../../../../../common/fieldEditors/SwitchFE";
import { StudyMetadata } from "../../../../../../common/types";
import { validateString } from "../../../../../../utils/validationUtils";
import { setCurrentBindingConst } from "../../../../../../redux/ducks/studySyntheses";
import useAppDispatch from "../../../../../../redux/hooks/useAppDispatch";
import { useOutletContext } from "react-router";
import { validateString } from "@/utils/validation/string";

interface Props {
open: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ import SwitchFE from "../../../../../../common/fieldEditors/SwitchFE";
import { useFormContextPlus } from "../../../../../../common/Form";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { validateString } from "../../../../../../../utils/validationUtils";
import Matrix from "./Matrix";
import { Box, Button } from "@mui/material";
import { Dataset } from "@mui/icons-material";
import { validateString } from "@/utils/validation/string";

interface Props {
study: StudyMetadata;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import StringFE from "../../../../../common/fieldEditors/StringFE";
import { SubmitHandlerPlus } from "../../../../../common/Form/types";
import useAppSelector from "../../../../../../redux/hooks/useAppSelector";
import { getAreas } from "../../../../../../redux/selectors";
import { validateString } from "../../../../../../utils/validationUtils";
import Fieldset from "../../../../../common/Fieldset";
import { validateString } from "@/utils/validation/string";

interface Props {
studyId: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import useAppDispatch from "../../../../../../../../redux/hooks/useAppDispatch";
import { createStudyMapDistrict } from "../../../../../../../../redux/ducks/studyMaps";
import useAppSelector from "../../../../../../../../redux/hooks/useAppSelector";
import { getStudyMapDistrictsById } from "../../../../../../../../redux/selectors";
import { validateString } from "../../../../../../../../utils/validationUtils";
import { validateString } from "@/utils/validation/string";

interface Props {
open: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import useAppDispatch from "../../../../../../../../redux/hooks/useAppDispatch";
import useEnqueueErrorSnackbar from "../../../../../../../../hooks/useEnqueueErrorSnackbar";
import useAppSelector from "../../../../../../../../redux/hooks/useAppSelector";
import { getStudyMapLayersById } from "../../../../../../../../redux/selectors";
import { validateString } from "../../../../../../../../utils/validationUtils";
import { validateString } from "@/utils/validation/string";

interface Props {
open: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
updateStudyMapLayer,
} from "../../../../../../../../redux/ducks/studyMaps";
import useAppDispatch from "../../../../../../../../redux/hooks/useAppDispatch";
import { validateString } from "../../../../../../../../utils/validationUtils";
import { validateString } from "@/utils/validation/string";

interface Props {
open: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ import SelectFE from "../../../../../common/fieldEditors/SelectFE";
import StringFE from "../../../../../common/fieldEditors/StringFE";
import { getTableColumnsForType, type TableTemplate } from "../utils";
import { TABLE_MODE_TYPES } from "../../../../../../services/api/studies/tableMode/constants";
import {
validateArray,
validateString,
} from "../../../../../../utils/validationUtils";
import { useMemo } from "react";
import { validateArray } from "@/utils/validation/array";
import { validateString } from "@/utils/validation/string";

export interface TableTemplateFormDialogProps
extends Pick<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import Fieldset from "../../../../../common/Fieldset";
import SelectFE from "../../../../../common/fieldEditors/SelectFE";
import NumberFE from "../../../../../common/fieldEditors/NumberFE";
import { SubmitHandlerPlus } from "../../../../../common/Form/types";
import { validateString } from "../../../../../../utils/validationUtils";
import { validateString } from "@/utils/validation/string";

interface PropType {
open: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import StringFE from "../fieldEditors/StringFE";
import Fieldset from "../Fieldset";
import { SubmitHandlerPlus } from "../Form/types";
import SelectFE from "../fieldEditors/SelectFE";
import { validateString } from "../../../utils/validationUtils";
import type { TRow } from "./types";
import { useTranslation } from "react-i18next";
import { validateString } from "@/utils/validation/string";

interface Props {
open: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Fieldset from "../Fieldset";
import FormDialog from "../dialogs/FormDialog";
import { SubmitHandlerPlus } from "../Form/types";
import StringFE from "../fieldEditors/StringFE";
import { validateString } from "../../../utils/validationUtils";
import { validateString } from "@/utils/validation/string";

interface Props {
open: boolean;
Expand Down
68 changes: 68 additions & 0 deletions webapp/src/utils/validation/array.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Copyright (c) 2024, RTE (https://www.rte-france.com)
*
* See AUTHORS.txt
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* SPDX-License-Identifier: MPL-2.0
*
* This file is part of the Antares project.
*/

import { validateArray } from "./array";

vi.mock("i18next", () => ({
t: vi.fn((key) => key),
}));

describe("validateArray", () => {
beforeEach(() => {
vi.clearAllMocks();
});

test("should return true for a non-empty array with no duplicates", () => {
expect(validateArray([1, 2, 3])).toBe(true);
});

test("should return an error message for an empty array when not allowed", () => {
expect(validateArray([])).toBe("form.field.required");
});

test("should return true for an empty array when allowed", () => {
expect(validateArray([], { allowEmpty: true })).toBe(true);
});

test("should return an error message for an array with duplicates when not allowed", () => {
expect(validateArray([1, 2, 2, 3])).toBe("form.field.duplicateNotAllowed");
});

test("should return true for an array with duplicates when allowed", () => {
expect(validateArray([1, 2, 2, 3], { allowDuplicate: true })).toBe(true);
});

test("should work with currying", () => {
const validator = validateArray({ allowDuplicate: false });
expect(validator([1, 2, 3])).toBe(true);
expect(validator([1, 1, 2, 3])).toBe("form.field.duplicateNotAllowed");
});

test("should work with different types of array elements", () => {
expect(validateArray(["a", "b", "c"])).toBe(true);
expect(validateArray([{ id: 1 }, { id: 2 }])).toBe(true);
});

test("should handle both options simultaneously", () => {
expect(validateArray([], { allowEmpty: true, allowDuplicate: false })).toBe(
true,
);
expect(
validateArray([1, 1], { allowEmpty: true, allowDuplicate: true }),
).toBe(true);
expect(
validateArray([1, 1], { allowEmpty: true, allowDuplicate: false }),
).toBe("form.field.duplicateNotAllowed");
});
});
73 changes: 73 additions & 0 deletions webapp/src/utils/validation/array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Copyright (c) 2024, RTE (https://www.rte-france.com)
*
* See AUTHORS.txt
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* SPDX-License-Identifier: MPL-2.0
*
* This file is part of the Antares project.
*/

import { ValidationReturn } from "@/common/types";
import { t } from "i18next";
import * as R from "ramda";

interface ArrayValidationOptions {
allowEmpty?: boolean;
allowDuplicate?: boolean;
}

/**
* Validates an array against specified criteria.
* This function checks for duplicate values in the array.
*
* @example
* validateArray([1, 2, 3], { allowDuplicate: false }); // true
* validateArray([1, 1, 2, 3], { allowDuplicate: false }); // Error message
*
*
* @example <caption>With currying.</caption>
* const fn = validateArray({ allowDuplicate: false });
* fn([1, 2, 3]); // true
* fn([1, 1, 2, 3]); // Error message
*
* @param value - The array to validate.
* @param [options] - Configuration options for validation.
* @param [options.allowEmpty=false] - Sets whether empty array is allowed or not.
* @param [options.allowDuplicate=false] - Sets whether duplicate values are allowed or not.
* @returns True if validation is successful, or a localized error message if it fails.
*/
export function validateArray<T>(
value: T[],
options?: ArrayValidationOptions,
): ValidationReturn;

export function validateArray<T>(
options?: ArrayValidationOptions,
): (value: T[]) => ValidationReturn;

export function validateArray<T>(
valueOrOpts?: T[] | ArrayValidationOptions,
options: ArrayValidationOptions = {},
): ValidationReturn | ((value: T[]) => ValidationReturn) {
if (!Array.isArray(valueOrOpts)) {
return (v: T[]) => validateArray(v, valueOrOpts);
}

const value = valueOrOpts;
const { allowEmpty, allowDuplicate } = options;

if (!value.length && !allowEmpty) {
return t("form.field.required");
}

if (!allowDuplicate && R.uniq(value).length !== value.length) {
return t("form.field.duplicateNotAllowed");
}

return true;
}
Loading

0 comments on commit 67c2d14

Please sign in to comment.