Skip to content

Commit

Permalink
fix: Implement emoji validation util on various fields
Browse files Browse the repository at this point in the history
  • Loading branch information
amattu2 committed Oct 10, 2024
1 parent 761eb53 commit 68d7be0
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 15 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const config = {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 11,
ecmaVersion: 2015,
project: "./tsconfig.json",
sourceType: "module",
},
Expand Down
120 changes: 120 additions & 0 deletions src/components/DataSubmissions/CreateDataSubmissionDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -896,4 +896,124 @@ describe("Basic Functionality", () => {
expect(createButton).not.toBeInTheDocument();
});
});

it("should show an error if the Submission Name contains emojis", async () => {
const { getByTestId, getByRole, getByText } = render(
<CreateDataSubmissionDialog onCreate={handleCreate} />,
{
wrapper: (p) => (
<TestParent
mocks={baseMocks}
authCtxState={{ ...baseAuthCtx, user: { ...baseUser, role: "Submitter" } }}
{...p}
/>
),
}
);

const openDialogButton = getByRole("button", { name: "Create a Data Submission" });
expect(openDialogButton).toBeInTheDocument();

await waitFor(() => expect(openDialogButton).toBeEnabled());

userEvent.click(openDialogButton);

await waitFor(() => {
expect(getByTestId("create-submission-dialog")).toBeInTheDocument();
});

await waitFor(() => {
expect(getByTestId("create-data-submission-dialog-study-id-input")).toBeEnabled();
});

const studySelectButton = within(
getByTestId("create-data-submission-dialog-study-id-input")
).getByRole("button");
expect(studySelectButton).toBeInTheDocument();

userEvent.click(studySelectButton);

await waitFor(() => {
expect(studySelectButton).toHaveAttribute("aria-expanded", "true");
});
userEvent.click(getByText("SN"));

expect(studySelectButton).toHaveTextContent("SN");

const dbGaPIDWrapper = getByTestId("create-data-submission-dialog-dbgap-id-input");
const dbGaPIDInput = within(dbGaPIDWrapper).getByRole("textbox");
userEvent.type(dbGaPIDInput, "001");

const submissionNameWrapper = getByTestId(
"create-data-submission-dialog-submission-name-input"
);
const submissionNameInput = within(submissionNameWrapper).getByRole("textbox");
userEvent.type(submissionNameInput, "😁 Emojis are not valid 😁");

userEvent.click(getByText("Create"));

await waitFor(() => {
expect(getByText("This field contains invalid characters")).toBeInTheDocument();
});
});

it("should show an error if the dbGaP ID contains emojis", async () => {
const { getByTestId, getByRole, getByText } = render(
<CreateDataSubmissionDialog onCreate={handleCreate} />,
{
wrapper: (p) => (
<TestParent
mocks={baseMocks}
authCtxState={{ ...baseAuthCtx, user: { ...baseUser, role: "Submitter" } }}
{...p}
/>
),
}
);

const openDialogButton = getByRole("button", { name: "Create a Data Submission" });
expect(openDialogButton).toBeInTheDocument();

await waitFor(() => expect(openDialogButton).toBeEnabled());

userEvent.click(openDialogButton);

await waitFor(() => {
expect(getByTestId("create-submission-dialog")).toBeInTheDocument();
});

await waitFor(() => {
expect(getByTestId("create-data-submission-dialog-study-id-input")).toBeEnabled();
});

const studySelectButton = within(
getByTestId("create-data-submission-dialog-study-id-input")
).getByRole("button");
expect(studySelectButton).toBeInTheDocument();

userEvent.click(studySelectButton);

await waitFor(() => {
expect(studySelectButton).toHaveAttribute("aria-expanded", "true");
});
userEvent.click(getByText("SN"));

expect(studySelectButton).toHaveTextContent("SN");

const dbGaPIDWrapper = getByTestId("create-data-submission-dialog-dbgap-id-input");
const dbGaPIDInput = within(dbGaPIDWrapper).getByRole("textbox");
userEvent.type(dbGaPIDInput, "🚧");

const submissionNameWrapper = getByTestId(
"create-data-submission-dialog-submission-name-input"
);
const submissionNameInput = within(submissionNameWrapper).getByRole("textbox");
userEvent.type(submissionNameInput, "this is a valid name");

userEvent.click(getByText("Create"));

await waitFor(() => {
expect(getByText("This field contains invalid characters")).toBeInTheDocument();
});
});
});
15 changes: 11 additions & 4 deletions src/components/DataSubmissions/CreateDataSubmissionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import StyledAsterisk from "../StyledFormComponents/StyledAsterisk";
import StyledLabel from "../StyledFormComponents/StyledLabel";
import BaseStyledHelperText from "../StyledFormComponents/StyledHelperText";
import Tooltip from "../Tooltip";
import { validateEmoji } from "../../utils";

const CreateSubmissionDialog = styled(Dialog)({
"& .MuiDialog-paper": {
Expand Down Expand Up @@ -332,7 +333,8 @@ const CreateDataSubmissionDialog: FC<Props> = ({ onCreate }) => {
createSubmission(data);
};

const validateEmpty = (value: string) => (!value?.trim() ? "This field is required" : null);
const validateEmpty = (value: string): string | null =>
!value?.trim() ? "This field is required" : null;

useEffect(() => {
if (intention === "New/Update") {
Expand Down Expand Up @@ -508,8 +510,10 @@ const CreateDataSubmissionDialog: FC<Props> = ({ onCreate }) => {
</StyledLabel>
<StyledOutlinedInput
{...register("dbGaPID", {
required: isDbGapRequired ? "This field is required" : null,
validate: isDbGapRequired ? validateEmpty : null,
validate: {
empty: isDbGapRequired ? validateEmpty : () => null,
emoji: validateEmoji,
},
})}
inputProps={{ maxLength: 50 }}
placeholder="Input dbGaP ID"
Expand All @@ -528,7 +532,10 @@ const CreateDataSubmissionDialog: FC<Props> = ({ onCreate }) => {
<StyledOutlinedInputMultiline
{...register("name", {
maxLength: 25,
validate: validateEmpty,
validate: {
empty: validateEmpty,
emoji: validateEmoji,
},
})}
multiline
rows={3}
Expand Down
2 changes: 2 additions & 0 deletions src/content/questionnaire/sections/B.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
findProgram,
mapObjectWithKey,
programToSelectOption,
validateEmoji,
} from "../../../utils";
import AddRemoveButton from "../../../components/Questionnaire/AddRemoveButton";
import PlannedPublication from "../../../components/Questionnaire/PlannedPublication";
Expand Down Expand Up @@ -422,6 +423,7 @@ const FormSectionB: FC<FormSectionProps> = ({ SectionOption, refs }: FormSection
value={study.name}
maxLength={100}
placeholder="100 characters allowed"
validate={(input: string) => !validateEmoji(input)}
readOnly={readOnlyInputs}
hideValidation={readOnlyInputs}
tooltipText="A descriptive name that will be used to identify the study."
Expand Down
26 changes: 26 additions & 0 deletions src/utils/formUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,3 +511,29 @@ describe("renderStudySelectionValue cases", () => {
expect(result).toBe("");
});
});

describe("validateEmoji cases", () => {
it("should return null for a string without emojis", () => {
expect(utils.validateEmoji("This is a test string")).toBe(null);
});

it("should return null for a string containing numbers", () => {
expect(utils.validateEmoji("Test123 string with numbers 456 789 101112")).toBe(null);
});

it("should return an error message for a string with emojis and other characters", () => {
expect(utils.validateEmoji("This is a test string 😊 with emojis")).toEqual(expect.any(String));
});

it("should return an error message for a string with only emojis", () => {
expect(utils.validateEmoji("😊😊😊😊😊")).toEqual(expect.any(String));
});

// NOTE: We're testing various different types of emojis here
it.each<string>(["😊", "👨🏿‍🎤", "🔴", "1️⃣", "🇵🇷"])(
"should return an error message for a string with emojis",
(value) => {
expect(utils.validateEmoji(value)).toEqual(expect.any(String));
}
);
});
17 changes: 17 additions & 0 deletions src/utils/formUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,20 @@ export const formatStudySelectionValue = (

return mappedStudies.length > 0 ? mappedStudies[0] : fallback;
};

/**
* Provides a validation function to test against a string for invalid characters.
*
* @param value The input string to validate
* @returns A string if the value contains invalid characters, otherwise null
*/
export const validateEmoji = (value: string): string | null => {
const EmojiRegex =
/(?![*#0-9]+)[\p{Emoji}\p{Emoji_Modifier}\p{Emoji_Component}\p{Emoji_Modifier_Base}\p{Emoji_Presentation}]/u;

if (EmojiRegex.test(value)) {
return "This field contains invalid characters";
}

return null;
};
12 changes: 2 additions & 10 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"downlevelIteration": true,
// fix Type 'Element' is not assignable to type 'ReactNode TS warning
"paths": {
"react": ["./node_modules/@types/react"]
},
"target": "ES2015",
"lib": [
"dom",
"dom.iterable",
Expand All @@ -24,10 +19,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"types": [
"node"
]
"jsx": "react-jsx"
},
"include": [
"src",
Expand Down

0 comments on commit 68d7be0

Please sign in to comment.