Skip to content

Commit

Permalink
[CreateProject] Improve empty name handling (#3184)
Browse files Browse the repository at this point in the history
  • Loading branch information
imnasnainaec authored Jul 9, 2024
1 parent 85c12dd commit 0258f37
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 33 deletions.
42 changes: 28 additions & 14 deletions src/components/ProjectScreen/CreateProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
type FormEvent,
Fragment,
type ReactElement,
useEffect,
useState,
} from "react";
import { Trans, useTranslation } from "react-i18next";
Expand Down Expand Up @@ -55,6 +56,14 @@ export default function CreateProject(): ReactElement {
const [vernLangIsOther, setVernLangIsOther] = useState(false);
const [vernLangOptions, setVernLangOptions] = useState<WritingSystem[]>([]);

useEffect(() => {
// Turn on the empty name error if the name is empty and another field isn't empty.
const empty =
!name.trim() &&
(!!languageData || (!!vernLang.bcp47 && vernLang.bcp47 !== undBcp47));
setError((prev) => (empty === prev.empty ? prev : { ...prev, empty }));
}, [languageData, name, vernLang.bcp47]);

const { t } = useTranslation();

const setVernBcp47 = (bcp47: string): void => {
Expand Down Expand Up @@ -98,9 +107,10 @@ export default function CreateProject(): ReactElement {
const updateName = (
e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement | HTMLSelectElement>
): void => {
const name = e.target.value;
setName(name);
setError({ empty: !name, nameTaken: false });
setName(e.target.value);

// Turn off the nameTaken error when name is changed.
setError((prev) => (prev.nameTaken ? { ...prev, nameTaken: false } : prev));
};

const updateLanguageData = async (langData?: File): Promise<void> => {
Expand Down Expand Up @@ -159,29 +169,31 @@ export default function CreateProject(): ReactElement {

const createProject = async (e: FormEvent<EventTarget>): Promise<void> => {
e.preventDefault();
if (success) {
return;
}
const trimmedName = name.trim();
if (!trimmedName) {
setError({ empty: true, nameTaken: false });
if (
success ||
!trimmedName ||
!vernLang.bcp47 ||
vernLang.bcp47 === undBcp47
) {
// Backstop for cases when this function shouldn't have run in the first place.
return;
}
if (await projectDuplicateCheck(name)) {
if (await projectDuplicateCheck(trimmedName)) {
setError({ empty: false, nameTaken: true });
return;
}

setLoading(true);

if (languageData) {
await dispatch(asyncFinishProject(name, vernLang)).then(() =>
await dispatch(asyncFinishProject(trimmedName, vernLang)).then(() =>
setSuccess(true)
);
} else {
await dispatch(asyncCreateProject(name, vernLang, [analysisLang])).then(
() => setSuccess(true)
);
await dispatch(
asyncCreateProject(trimmedName, vernLang, [analysisLang])
).then(() => setSuccess(true));
}
};

Expand Down Expand Up @@ -294,7 +306,9 @@ export default function CreateProject(): ReactElement {
>
<LoadingDoneButton
buttonProps={{ color: "primary", id: buttonIdSubmit }}
disabled={!vernLang.bcp47 || vernLang.bcp47 === undBcp47}
disabled={
!name.trim() || !vernLang.bcp47 || vernLang.bcp47 === undBcp47
}
done={success}
doneText={t("createProject.success")}
loading={loading}
Expand Down
42 changes: 23 additions & 19 deletions src/components/ProjectScreen/tests/CreateProject.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ const mockSubmitEvent = (): Partial<FormEvent<HTMLFormElement>> => ({
let projectMaster: ReactTestRenderer;
let projectHandle: ReactTestInstance;

beforeAll(async () => {
beforeEach(async () => {
jest.resetAllMocks();
await act(async () => {
projectMaster = create(
<Provider store={mockStore}>
Expand All @@ -55,27 +56,13 @@ beforeAll(async () => {
projectHandle = projectMaster.root;
});

beforeEach(() => {
jest.resetAllMocks();
});

describe("CreateProject", () => {
it("errors on empty name", async () => {
const nameField = projectHandle.findByProps({ id: fieldIdName });
expect(nameField.props.error).toBeFalsy();

await act(async () => {
projectHandle
.findByProps({ id: formId })
.props.onSubmit(mockSubmitEvent());
});
expect(nameField.props.error).toBeTruthy();
});

it("errors on taken name", async () => {
const nameField = projectHandle.findByProps({ id: fieldIdName });
const langPickers = projectHandle.findAllByType(LanguagePicker);
await act(async () => {
nameField.props.onChange(mockChangeEvent("non-empty-value"));
nameField.props.onChange(mockChangeEvent("non-empty-name"));
langPickers[0].props.setCode("non-empty-code");
});
expect(nameField.props.error).toBeFalsy();

Expand All @@ -88,16 +75,33 @@ describe("CreateProject", () => {
expect(nameField.props.error).toBeTruthy();
});

it("disables submit button when no vern lang bcp code", async () => {
it("disables submit button when empty name or empty vern lang bcp code", async () => {
const nameField = projectHandle.findByProps({ id: fieldIdName });
const button = projectHandle.findByProps({ id: buttonIdSubmit });

// Start with empty name and vern language: button disabled.
expect(button.props.disabled).toBeTruthy();

// Add name but still no vern language: button still disabled.
await act(async () => {
nameField.props.onChange(mockChangeEvent("non-empty-value"));
});
expect(button.props.disabled).toBeTruthy();

// Also add a vern language: button enabled.
const langPickers = projectHandle.findAllByType(LanguagePicker);
expect(langPickers).toHaveLength(2);

await act(async () => {
langPickers[0].props.setCode("non-empty");
});
expect(button.props.disabled).toBeFalsy();

// Change name to whitespace: button disabled again.
await act(async () => {
nameField.props.onChange(mockChangeEvent(" "));
});
expect(button.props.disabled).toBeTruthy();
});

it("disables analysis language pickers when file selected", async () => {
Expand Down

0 comments on commit 0258f37

Please sign in to comment.