Skip to content

Commit

Permalink
fix(web): reference field updating bug (#1236)
Browse files Browse the repository at this point in the history
* fix: ref field bug

* feat: toggle confirm button

* improve test
  • Loading branch information
caichi-t authored and yk-eukarya committed Oct 1, 2024
1 parent 45ed094 commit 86f1e2b
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 109 deletions.
40 changes: 38 additions & 2 deletions web/e2e/project/item/fields/reference.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ test("One-way reference field creating and updating has succeeded", async ({ pag
page.getByLabel("Create Reference Field").getByText("ref model #ref-model"),
).toBeVisible();

await page.getByLabel("One-way reference").check();
await expect(page.getByLabel("One-way reference")).toBeChecked();
await page.getByRole("button", { name: "Next" }).click();
await expect(page.getByRole("button", { name: "Confirm" })).toBeDisabled();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("ref");
await expect(page.getByRole("button", { name: "Confirm" })).toBeEnabled();
await page.getByLabel("Description(optional)").click();
await page.getByLabel("Description(optional)").fill("ref description");
await expect(
Expand All @@ -82,6 +84,15 @@ test("One-way reference field creating and updating has succeeded", async ({ pag
await expect(
page.locator("label").filter({ hasText: "Two-way reference" }).locator("span").first(),
).toBeDisabled();
await expect(page.getByRole("button", { name: "Next" })).toBeEnabled();
await page.getByRole("button", { name: "Next" }).click();
await expect(page.getByRole("button", { name: "Confirm" })).toBeDisabled();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("reff");
await expect(page.getByRole("button", { name: "Confirm" })).toBeEnabled();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("ref");
await expect(page.getByRole("button", { name: "Confirm" })).toBeDisabled();
await page.getByLabel("Close", { exact: true }).first().click();
await page.getByText("Content").click();
await expect(page.locator("thead")).toContainText("ref");
Expand Down Expand Up @@ -167,8 +178,10 @@ test("Two-way reference field editing has succeeded", async ({ page }) => {
await page.getByText("ref model #ref-model").click();
await page.getByLabel("Two-way reference").check();
await page.getByRole("button", { name: "Next" }).click();
await expect(page.getByRole("button", { name: "Next" })).toBeDisabled();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("ref1");
await expect(page.getByRole("button", { name: "Next" })).toBeEnabled();
await page.getByLabel("Description(optional)").click();
await page.getByLabel("Description(optional)").fill("ref1 description");
await expect(
Expand All @@ -181,16 +194,18 @@ test("Two-way reference field editing has succeeded", async ({ page }) => {
).toBeDisabled();
await page.getByRole("button", { name: "Next" }).click();

await expect(page.getByRole("button", { name: "Confirm" })).toBeDisabled();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("ref2");
await expect(page.getByRole("button", { name: "Confirm" })).toBeEnabled();
await page.getByLabel("Description(optional)").click();
await page.getByLabel("Description(optional)").fill("ref2 description");
await page.getByRole("tab", { name: "Validation" }).click();
await page.getByLabel("Make field required").check();
await page.getByRole("button", { name: "Previous" }).click();
await expect(page.getByLabel("Display name")).toHaveValue("ref1");
await expect(page.getByLabel("Field Key")).toHaveValue("ref1");
await page.getByLabel("Description(optional)").click();
await expect(page.getByLabel("Description(optional)")).toHaveValue("ref1 description");
await page.getByRole("tab", { name: "Validation" }).click();
await expect(page.getByLabel("Make field required")).toBeChecked();
await page.getByRole("button", { name: "Next" }).click();
Expand All @@ -212,6 +227,27 @@ test("Two-way reference field editing has succeeded", async ({ page }) => {
await expect(
page.locator("label").filter({ hasText: "Two-way reference" }).locator("span").first(),
).toBeDisabled();
await expect(page.getByRole("button", { name: "Next" })).toBeEnabled();
await page.getByRole("button", { name: "Next" }).click();
await expect(page.getByRole("button", { name: "Next" })).toBeEnabled();
await page.getByRole("button", { name: "Next" }).click();
await expect(page.getByRole("button", { name: "Confirm" })).toBeDisabled();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("reff");
await expect(page.getByRole("button", { name: "Confirm" })).toBeEnabled();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("ref2");
await expect(page.getByRole("button", { name: "Confirm" })).toBeDisabled();
await page.getByRole("button", { name: "Previous" }).click();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("reff");
await page.getByRole("button", { name: "Next" }).click();
await expect(page.getByRole("button", { name: "Confirm" })).toBeEnabled();
await page.getByRole("button", { name: "Previous" }).click();
await page.getByLabel("Display name").click();
await page.getByLabel("Display name").fill("ref1");
await page.getByRole("button", { name: "Next" }).click();
await expect(page.getByRole("button", { name: "Confirm" })).toBeDisabled();
await page.getByLabel("Close", { exact: true }).first().click();
await page.getByText("Content").click();
await expect(page.locator("thead")).toContainText("ref1");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import styled from "@emotion/styled";
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
import { useCallback, useEffect, useMemo, useState, useRef, MutableRefObject } from "react";

import Button from "@reearth-cms/components/atoms/Button";
import Checkbox from "@reearth-cms/components/atoms/Checkbox";
Expand All @@ -23,6 +23,7 @@ import {
FieldModalTabs,
FieldType,
FormValues,
CorrespondingField,
} from "@reearth-cms/components/molecules/Schema/types";
import { useT } from "@reearth-cms/i18n";
import { MAX_KEY_LENGTH, validateKey } from "@reearth-cms/utils/regex";
Expand Down Expand Up @@ -70,17 +71,42 @@ const FieldCreationModalWithSteps: React.FC<Props> = ({
const isDisabledCache = useRef<boolean>(true);
const prevFieldKey = useRef<{ key: string; isSuccess: boolean }>();
const prevCorrespondingKey = useRef<{ key: string; isSuccess: boolean }>();
const defaultFieldValues = useRef<Field>();
const defaultCorrespondingValues = useRef<CorrespondingField>();
const changedKeys = useRef(new Set<string>());

const formValidate = useCallback(
(form: FormInstance) => {
if (
form.getFieldValue("model") ||
(form.getFieldValue("title") && form.getFieldValue("key"))
) {
form
.validateFields()
.then(() => setIsDisabled(currentStep === numSteps && changedKeys.current.size === 0))
.catch(() => setIsDisabled(true));
} else {
setIsDisabled(true);
}
},
[currentStep, numSteps],
);

const formValidate = useCallback((form: FormInstance) => {
if (form.getFieldValue("model") || (form.getFieldValue("title") && form.getFieldValue("key"))) {
form
.validateFields()
.then(() => setIsDisabled(false))
.catch(() => setIsDisabled(true));
} else {
setIsDisabled(true);
}
}, []);
const handleValuesChange = useCallback(
async (
changedValues: Field | CorrespondingField,
ref: MutableRefObject<typeof changedValues | undefined>,
) => {
const [key, value] = Object.entries(changedValues)[0];
const defaultValue = ref.current?.[key as keyof typeof changedValues];
if (value === defaultValue) {
changedKeys.current.delete(currentStep + key);
} else {
changedKeys.current.add(currentStep + key);
}
},
[currentStep],
);

const SettingValues = Form.useWatch([], modelForm);
useEffect(() => {
Expand All @@ -104,16 +130,14 @@ const FieldCreationModalWithSteps: React.FC<Props> = ({
});

setSelectedModelId(selectedField?.typeProperty?.modelId);
schemaIdRef.current = selectedField?.typeProperty?.schema?.id;
setNumSteps(selectedField?.typeProperty?.correspondingField ? 2 : 1);
setIsDisabled(!selectedField);
field1Form.setFieldsValue({
...selectedField,
});
if (selectedField?.typeProperty?.correspondingField) {
field2Form.setFieldsValue({
...selectedField.typeProperty.correspondingField,
});
}
field1Form.setFieldsValue(selectedField);
defaultFieldValues.current = selectedField ?? undefined;
field2Form.setFieldsValue(selectedField?.typeProperty?.correspondingField);
defaultCorrespondingValues.current = selectedField?.typeProperty?.correspondingField;
changedKeys.current.clear();
}, [modelForm, selectedField, field1Form, field2Form, setNumSteps, setSelectedModelId]);

const initialValues: FormValues = useMemo(
Expand Down Expand Up @@ -170,15 +194,26 @@ const FieldCreationModalWithSteps: React.FC<Props> = ({
[setSelectedModelId],
);

const clearFormFields = useCallback(() => {
const formReset = useCallback(() => {
prevFieldKey.current = undefined;
prevCorrespondingKey.current = undefined;
modelForm.resetFields();
field1Form.resetFields();
field2Form.resetFields();
}, [field1Form, field2Form, modelForm]);

const modalReset = useCallback(() => {
setCurrentStep(0);
setNumSteps(1);
setIsDisabled(true);
setField1FormValues(initialValues);
}, [modelForm, field1Form, field2Form, initialValues, setCurrentStep]);
}, [initialValues, setCurrentStep]);

const handleCancel = useCallback(() => {
onClose();
formReset();
modalReset();
}, [formReset, modalReset, onClose]);

const prevStep = useCallback(() => {
if (currentStep > 0) setCurrentStep(currentStep - 1);
Expand All @@ -200,84 +235,64 @@ const FieldCreationModalWithSteps: React.FC<Props> = ({
nextStep();
}, [handleReferencedModelGet, nextStep, numSteps, selectedModelId]);

const handleFirstField = useCallback(() => {
field1Form
.validateFields()
.then(async values => {
values.type = "Reference";
values.typeProperty = {
reference: {
modelId: selectedModelId,
schemaId: schemaIdRef.current,
correspondingField: null,
},
};
setField1FormValues(values);
if (currentStep < numSteps) {
nextStep();
const handleFirstField = useCallback(async () => {
try {
const values = await field1Form.validateFields();
values.type = "Reference";
values.typeProperty = {
reference: {
modelId: selectedModelId,
schemaId: schemaIdRef.current,
correspondingField: null,
},
};
setField1FormValues(values);
if (currentStep < numSteps) {
nextStep();
} else {
if (selectedField) {
await onUpdate({ ...values, fieldId: selectedField.id });
} else {
if (selectedField) {
await onUpdate({ ...values, fieldId: selectedField.id });
} else {
await onSubmit(values);
}
await onSubmit(values);
}
})
.catch(() => {
setActiveTab("settings");
});
onClose();
}
} catch (e) {
console.error(e);
}
}, [
field1Form,
selectedModelId,
currentStep,
numSteps,
nextStep,
selectedField,
onClose,
onUpdate,
onSubmit,
]);

const handleSecondField = useCallback(() => {
if (selectedField) {
field2Form
.validateFields()
.then(async fields2Values => {
field1FormValues.typeProperty = {
reference: {
modelId: selectedModelId ?? "",
schemaId: schemaIdRef.current ?? "",
correspondingField: {
...fields2Values,
fieldId: selectedField?.typeProperty?.correspondingField?.id,
},
},
};
await onUpdate({ ...field1FormValues, fieldId: selectedField.id });
onClose();
})
.catch(() => {
setActiveTab("settings");
});
} else {
field2Form
.validateFields()
.then(async fields2Values => {
field1FormValues.typeProperty = {
reference: {
modelId: selectedModelId ?? "",
schemaId: schemaIdRef.current ?? "",
correspondingField: {
...fields2Values,
},
},
};

await onSubmit(field1FormValues);
onClose();
})
.catch(() => {
setActiveTab("settings");
});
const handleSecondField = useCallback(async () => {
try {
const fields2Values = await field2Form.validateFields();
field1FormValues.typeProperty = {
reference: {
modelId: selectedModelId ?? "",
schemaId: schemaIdRef.current ?? "",
correspondingField: {
...fields2Values,
fieldId: selectedField?.typeProperty?.correspondingField?.id,
},
},
};
if (selectedField) {
await onUpdate({ ...field1FormValues, fieldId: selectedField.id });
} else {
await onSubmit(field1FormValues);
}
onClose();
} catch (e) {
console.error(e);
}
}, [onClose, onSubmit, onUpdate, selectedField, field1FormValues, field2Form, selectedModelId]);

Expand Down Expand Up @@ -328,13 +343,8 @@ const FieldCreationModalWithSteps: React.FC<Props> = ({
</FieldThumbnail>
) : null
}
onCancel={() => {
onClose();
clearFormFields();
}}
afterClose={() => {
clearFormFields();
}}
onCancel={handleCancel}
afterClose={modalReset}
width={572}
open={open}
footer={
Expand Down Expand Up @@ -424,7 +434,10 @@ const FieldCreationModalWithSteps: React.FC<Props> = ({
form={field1Form}
layout="vertical"
initialValues={initialValues}
requiredMark={requiredMark}>
requiredMark={requiredMark}
onValuesChange={changedValues => {
handleValuesChange(changedValues, defaultFieldValues);
}}>
<Tabs activeKey={activeTab} onChange={handleTabChange}>
<TabPane tab={t("Settings")} key="settings" forceRender>
<Form.Item
Expand Down Expand Up @@ -521,7 +534,10 @@ const FieldCreationModalWithSteps: React.FC<Props> = ({
form={field2Form}
layout="vertical"
initialValues={initialValues}
requiredMark={requiredMark}>
requiredMark={requiredMark}
onValuesChange={changedValues => {
handleValuesChange(changedValues, defaultCorrespondingValues);
}}>
<Tabs activeKey={activeTab} onChange={handleTabChange}>
<TabPane tab={t("Settings")} key="settings" forceRender>
<Form.Item
Expand Down
1 change: 1 addition & 0 deletions web/src/components/molecules/Schema/FieldModal/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ export default (
);

const handleModalReset = useCallback(() => {
prevKey.current = undefined;
form.resetFields();
setActiveTab("settings");
setMultipleValue(false);
Expand Down
Loading

0 comments on commit 86f1e2b

Please sign in to comment.