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

fix(web): after deleting the first view, the current view is not changed #1106

Merged
merged 8 commits into from
Mar 21, 2024
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
@@ -1,11 +1,10 @@
import { closeNotification } from "@reearth-cms/e2e/common/notification";
import { crudComment } from "@reearth-cms/e2e/project/utils/comment";
import { handleFieldForm } from "@reearth-cms/e2e/project/utils/field";
import { createModel } from "@reearth-cms/e2e/project/utils/model";
import { createProject, deleteProject } from "@reearth-cms/e2e/project/utils/project";
import { expect, test } from "@reearth-cms/e2e/utils";

import { crudComment } from "./utils/comment";
import { handleFieldForm } from "./utils/field";
import { createModel } from "./utils/model";
import { createProject, deleteProject } from "./utils/project";

test.beforeEach(async ({ reearth, page }) => {
await reearth.goto("/", { waitUntil: "domcontentloaded" });
await createProject(page);
Expand Down
169 changes: 169 additions & 0 deletions web/e2e/project/content/view.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { Page } from "@playwright/test";

import { closeNotification } from "@reearth-cms/e2e/common/notification";
import { handleFieldForm } from "@reearth-cms/e2e/project/utils/field";
import { createModel } from "@reearth-cms/e2e/project/utils/model";
import { createProject, deleteProject } from "@reearth-cms/e2e/project/utils/project";
import { createWorkspace, deleteWorkspace } from "@reearth-cms/e2e/project/utils/workspace";
import { expect, test } from "@reearth-cms/e2e/utils";

test.beforeEach(async ({ reearth, page }) => {
await reearth.goto("/", { waitUntil: "domcontentloaded" });
await createWorkspace(page);
await createProject(page);
await createModel(page);
});

test.afterEach(async ({ page }) => {
await deleteProject(page);
await deleteWorkspace(page);
});

const itemAdd = async (page: Page, data: string) => {
await page.getByRole("button", { name: "plus New Item" }).click();
await page.getByLabel("text").click();
await page.getByLabel("text").fill(data);
await page.getByRole("button", { name: "Save" }).click();
await expect(page.getByRole("alert").last()).toContainText("Successfully created Item!");
await closeNotification(page);
await page.getByLabel("Back").click();
};

test("View CRUD has succeeded", async ({ page }) => {
test.slow();
await page.locator("li").filter({ hasText: "Text" }).locator("div").first().click();
await handleFieldForm(page, "text");
await closeNotification(page);
await page.getByText("Content").click();
await itemAdd(page, "text1");
await itemAdd(page, "text2");
await itemAdd(page, "sample1");
await itemAdd(page, "sample2");
await page.getByRole("button", { name: "Save as new view" }).click();
await page.getByLabel("View Name").click();
await page.getByLabel("View Name").fill("view1");
await page.getByRole("button", { name: "OK" }).click();
await expect(page.getByRole("alert").last()).toContainText("Successfully created view!");
await closeNotification(page);
await expect(page.getByText("view1")).toBeVisible();
await expect(page.getByRole("tab").nth(0)).toHaveAttribute("aria-selected", "true");

await page.getByLabel("more").locator("svg").click();
await page.getByText("Rename").click();
await page.getByLabel("View Name").click();
await page.getByLabel("View Name").fill("new view1");
await page.getByRole("button", { name: "OK" }).click();
await expect(page.getByRole("alert").last()).toContainText("Successfully renamed view!");
await closeNotification(page);
await expect(page.getByText("new view1")).toBeVisible();
await page.getByLabel("more").locator("svg").click();
await page.getByText("Remove View").click();
await page.getByRole("button", { name: "Remove" }).click();
await expect(page.getByRole("alert").last()).toContainText(
"input: deleteView model should have at least one view",
);
await closeNotification(page);

await page.getByText("text", { exact: true }).click();
await expect(
page.getByRole("cell", { name: "text caret-up caret-down" }).locator(".anticon-caret-up"),
).toHaveClass(/active/);
await expect(page.locator(".ant-table-row").nth(0)).toContainText("sample1");
await expect(page.locator(".ant-table-row").nth(1)).toContainText("sample2");

await page.getByRole("button", { name: "plus Filter" }).click();
await page.getByRole("menuitem", { name: "text" }).click();
await expect(page.getByRole("button", { name: "text close" })).toBeVisible();
await page.getByText("is").first().click();
await page.getByText("contains", { exact: true }).click();
await page.getByPlaceholder("Enter the value").click();
await page.getByPlaceholder("Enter the value").fill("text");
await page.getByRole("button", { name: "Confirm" }).click();

await page.getByRole("main").getByLabel("setting").locator("svg").click();
await expect(page.getByRole("cell", { name: "Status" })).toBeVisible();
await page.locator("div:nth-child(3) > .ant-tree-checkbox").click();
await expect(page.getByRole("cell", { name: "Status" })).not.toBeVisible();

await page.getByRole("button", { name: "Save as new view" }).click();
await page.getByLabel("View Name").click();
await page.getByLabel("View Name").fill("view2");
await page.getByRole("button", { name: "OK" }).click();
await expect(page.getByRole("alert").last()).toContainText("Successfully created view!");
await closeNotification(page);
await expect(page.getByRole("tab").nth(0)).toHaveAttribute("aria-selected", "false");
await expect(page.getByRole("tab").nth(1)).toHaveAttribute("aria-selected", "true");
await expect(
page.getByRole("cell", { name: "text caret-up caret-down" }).locator(".anticon-caret-up"),
).toHaveClass(/active/);
await expect(page.locator(".ant-table-row").nth(0)).toContainText("text1");
await expect(page.locator(".ant-table-row").nth(1)).toContainText("text2");

await page.getByText("new view1").click();
await expect(page.getByRole("tab").nth(0)).toHaveAttribute("aria-selected", "true");
await expect(page.getByRole("tab").nth(1)).toHaveAttribute("aria-selected", "false");
await expect(
page.getByRole("cell", { name: "text caret-up caret-down" }).locator(".anticon-caret-up"),
).not.toHaveClass(/active/);
await expect(page.getByRole("button", { name: "text close" })).not.toBeVisible();
await expect(page.locator(".ant-table-row").nth(0)).toContainText("sample2");
await expect(page.locator(".ant-table-row").nth(1)).toContainText("sample1");
await expect(page.getByRole("cell", { name: "Status" })).toBeVisible();
await page.getByRole("main").getByLabel("setting").locator("svg").click();
await expect(page.locator("div:nth-child(3) > .ant-tree-checkbox")).toHaveClass(
/ant-tree-checkbox-checked/,
);
await page.getByRole("main").getByLabel("setting").locator("svg").click();

await page.getByText("text", { exact: true }).first().click();
await page.getByText("text", { exact: true }).first().click();
await page.getByRole("button", { name: "plus Filter" }).click();
await page.getByRole("menuitem", { name: "text" }).click();
await expect(page.getByRole("button", { name: "text close" })).toBeVisible();
await page.getByText("is", { exact: true }).first().click();
await page.getByText("end with", { exact: true }).click();
await page.getByPlaceholder("Enter the value").click();
await page.getByPlaceholder("Enter the value").fill("1");
await page.getByRole("button", { name: "Confirm" }).click();

await page.getByRole("tab", { name: "new view1 more" }).locator("svg").click();
await page.getByText("Update View").click();
await expect(page.getByRole("alert").last()).toContainText("Successfully updated view!");
await closeNotification(page);

await page.getByText("view2").click();
await expect(
page.getByRole("cell", { name: "text caret-up caret-down" }).locator(".anticon-caret-up"),
).toHaveClass(/active/);
await expect(page.getByRole("button", { name: "text close" })).toBeVisible();
await expect(page.locator(".ant-table-row").nth(0)).toContainText("text1");
await expect(page.locator(".ant-table-row").nth(1)).toContainText("text2");
await expect(page.getByRole("cell", { name: "Status" })).not.toBeVisible();
await page.getByRole("main").getByLabel("setting").locator("svg").click();
await expect(page.locator("div:nth-child(3) > .ant-tree-checkbox")).not.toHaveClass(
/ant-tree-checkbox-checked/,
);
await page.getByRole("main").getByLabel("setting").locator("svg").click();

await page.getByText("new view1").click();
await expect(
page.getByRole("cell", { name: "text caret-up caret-down" }).locator(".anticon-caret-down"),
).toHaveClass(/active/);
await expect(page.locator(".ant-table-row").nth(0)).toContainText("text1");
await expect(page.locator(".ant-table-row").nth(1)).toContainText("sample1");

await page.getByRole("tab", { name: "new view1 more" }).click();
await page.getByRole("tab", { name: "new view1 more" }).locator("svg").click();
await page.getByText("Remove View").click();
await page.getByRole("button", { name: "Remove" }).click();
await expect(page.getByRole("alert").last()).toContainText("Successfully deleted view!");
await closeNotification(page);
await expect(page.getByText("new view1")).not.toBeVisible();
await expect(page.getByText("view2")).toBeVisible();
await expect(page.getByRole("tab").nth(0)).toHaveAttribute("aria-selected", "true");
await expect(
page.getByRole("cell", { name: "text caret-up caret-down" }).locator(".anticon-caret-up"),
).toHaveClass(/active/);
await expect(page.locator(".ant-table-row").nth(0)).toContainText("text1");
await expect(page.locator(".ant-table-row").nth(1)).toContainText("text2");
});
1 change: 1 addition & 0 deletions web/e2e/project/item/fields/group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ test.afterEach(async ({ page }) => {
});

test("Group field creating and updating has succeeded", async ({ page }) => {
test.slow();
await expect(
page.locator("li").filter({ hasText: "Reference" }).locator("div").first(),
).toBeVisible();
Expand Down
40 changes: 19 additions & 21 deletions web/src/components/organisms/Project/Content/ViewsMenu/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
import { useParams } from "react-router-dom";

import Notification from "@reearth-cms/components/atoms/Notification";
import { AndCondition, View } from "@reearth-cms/components/molecules/View/types";
Expand All @@ -22,18 +23,19 @@ import { useProject } from "@reearth-cms/state";
import { CurrentViewType } from "../ContentList/hooks";

type Params = {
modelId?: string;
currentView: CurrentViewType;
setCurrentView: Dispatch<SetStateAction<CurrentViewType>>;
onViewChange: () => void;
};

export type modalStateType = "rename" | "create";

export default ({ modelId, currentView, setCurrentView, onViewChange }: Params) => {
export default ({ currentView, setCurrentView, onViewChange }: Params) => {
const { modelId } = useParams();
const t = useT();
const [prevModelId, setPrevModelId] = useState<string>();
const [viewModalShown, setViewModalShown] = useState(false);
const [views, setViews] = useState<View[]>([]);
const [selectedView, setSelectedView] = useState<View>();
const [modalState, setModalState] = useState<modalStateType>("create");
const [submitting, setSubmitting] = useState(false);
Expand All @@ -46,17 +48,22 @@ export default ({ modelId, currentView, setCurrentView, onViewChange }: Params)
skip: !modelId,
});

const views = useMemo(() => {
useEffect(() => {
const viewList = data?.view
?.map<View | undefined>(view => fromGraphQLView(view as GQLView))
?.map(view => fromGraphQLView(view as GQLView))
.filter((view): view is View => !!view);

if (prevModelId !== modelId && viewList) {
setSelectedView(viewList && viewList.length > 0 ? viewList[0] : undefined);
setPrevModelId(modelId);
if (viewList) {
if (prevModelId !== modelId) {
setSelectedView(viewList[0]);
setPrevModelId(modelId);
} else if (viewList.length > views.length) {
setSelectedView(viewList[viewList.length - 1]);
} else if (viewList.length < views.length) {
setSelectedView(viewList[0]);
}
}
return viewList ?? [];
}, [data?.view, modelId, prevModelId]);
setViews(viewList ?? []);
}, [data?.view, modelId, prevModelId, views.length]);

useEffect(() => {
if (selectedView) {
Expand Down Expand Up @@ -129,7 +136,6 @@ export default ({ modelId, currentView, setCurrentView, onViewChange }: Params)
Notification.error({ message: t("Failed to create view.") });
return;
}
setSelectedView(fromGraphQLView(view.data.createView.view as GQLView));
setViewModalShown(false);
onViewChange();
Notification.success({ message: t("Successfully created view!") });
Expand Down Expand Up @@ -173,7 +179,6 @@ export default ({ modelId, currentView, setCurrentView, onViewChange }: Params)
Notification.error({ message: t("Failed to update view.") });
return;
}
setSelectedView(fromGraphQLView(view.data.updateView.view as GQLView));
Notification.success({ message: t("Successfully updated view!") });
handleViewModalReset();
},
Expand Down Expand Up @@ -206,7 +211,6 @@ export default ({ modelId, currentView, setCurrentView, onViewChange }: Params)
Notification.error({ message: t("Failed to rename view.") });
return;
}
setSelectedView(fromGraphQLView(view.data.updateView.view as GQLView));
Notification.success({ message: t("Successfully renamed view!") });
handleViewModalReset();
},
Expand All @@ -217,10 +221,6 @@ export default ({ modelId, currentView, setCurrentView, onViewChange }: Params)
refetchQueries: ["GetViews"],
});

const handleViewDeletionModalClose = useCallback(() => {
setSelectedView(fromGraphQLView(views[0] as GQLView));
}, [views]);

const handleViewDelete = useCallback(
async (viewId?: string) => {
if (!viewId) return;
Expand All @@ -230,19 +230,16 @@ export default ({ modelId, currentView, setCurrentView, onViewChange }: Params)
} else {
Notification.success({ message: t("Successfully deleted view!") });
onViewChange();
handleViewDeletionModalClose();
}
},
[deleteView, handleViewDeletionModalClose, onViewChange, t],
[deleteView, onViewChange, t],
);

return {
views,
modalState,
handleViewRenameModalOpen,
handleViewCreateModalOpen,
handleViewDelete,
handleViewDeletionModalClose,
selectedView,
setSelectedView,
viewModalShown,
Expand All @@ -251,5 +248,6 @@ export default ({ modelId, currentView, setCurrentView, onViewChange }: Params)
handleViewCreate,
handleViewUpdate,
handleViewRename,
handleViewDelete,
};
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { Dispatch, SetStateAction } from "react";
import { useParams } from "react-router-dom";

import ViewFormModal from "@reearth-cms/components/molecules/View/ViewFormModal";
import ViewsMenuMolecule from "@reearth-cms/components/molecules/View/viewsMenu";
Expand All @@ -15,8 +14,6 @@ export type Props = {
};

const ViewsMenu: React.FC<Props> = ({ currentView, setCurrentView, onViewChange }) => {
const { modelId } = useParams();

const {
views,
modalState,
Expand All @@ -28,10 +25,10 @@ const ViewsMenu: React.FC<Props> = ({ currentView, setCurrentView, onViewChange
submitting,
handleViewModalReset,
handleViewCreate,
handleViewRename,
handleViewUpdate,
handleViewRename,
handleViewDelete,
} = useHooks({ modelId, currentView, setCurrentView, onViewChange });
} = useHooks({ currentView, setCurrentView, onViewChange });

return (
<>
Expand Down
Loading