Skip to content

Commit

Permalink
fix(web): after deleting the first view, the current view is not chan…
Browse files Browse the repository at this point in the history
…ged (#1106)

* move file

* add: view test

* fix: the behavior when removing first view

* add: filter saving

* add: saving column setting

* add: test.setTimeout

* fix: change test.timeout() to test.slow()
  • Loading branch information
caichi-t authored and yk-eukarya committed Oct 1, 2024
1 parent b1bf22e commit 8668190
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 31 deletions.
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

0 comments on commit 8668190

Please sign in to comment.