Skip to content

Commit

Permalink
feat: move domain set default form to sidepanel (#5290)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jay-Topher committed Jan 18, 2024
1 parent 469960d commit 1b19de7
Show file tree
Hide file tree
Showing 24 changed files with 251 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import ModelDeleteForm from "./ModelDeleteForm";
import ModelActionForm from "./ModelActionForm";

import { renderWithBrowserRouter, screen, userEvent } from "@/testing/utils";

it("renders", () => {
renderWithBrowserRouter(
<ModelDeleteForm
<ModelActionForm
initialValues={{}}
modelType="machine"
onSubmit={vi.fn()}
submitLabel="Delete"
/>
);
expect(
screen.getByText("Are you sure you want to delete this machine?")
screen.getByText(
"Are you sure you want to delete this machine? This action is permanent and can not be undone."
)
).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Delete" })).toBeInTheDocument();
});

it("can confirm", async () => {
const onSubmit = vi.fn();
renderWithBrowserRouter(
<ModelDeleteForm
<ModelActionForm
initialValues={{}}
modelType="machine"
onSubmit={onSubmit}
Expand All @@ -35,7 +37,7 @@ it("can confirm", async () => {
it("can cancel", async () => {
const onCancel = vi.fn();
renderWithBrowserRouter(
<ModelDeleteForm
<ModelActionForm
cancelLabel="Cancel"
initialValues={{}}
modelType="machine"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type Props = {
message?: string;
} & FormikFormProps<EmptyObject>;

const ModelDeleteForm = ({
const ModelActionForm = ({
modelType,
message,
submitAppearance = "negative",
Expand All @@ -29,15 +29,12 @@ const ModelDeleteForm = ({
<p className="u-nudge-down--small">
{message
? message
: `Are you sure you want to delete this ${modelType}?`}
: `Are you sure you want to delete this ${modelType}? This action is permanent and can not be undone.`}
</p>
<span className="u-text--light">
This action is permanent and can not be undone.
</span>
</Col>
</Row>
</FormikForm>
);
};

export default ModelDeleteForm;
export default ModelActionForm;
1 change: 1 addition & 0 deletions src/app/base/components/ModelActionForm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./ModelActionForm";
1 change: 0 additions & 1 deletion src/app/base/components/ModelDeleteForm/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useEffect } from "react";
import { Notification } from "@canonical/react-components";
import { useDispatch, useSelector } from "react-redux";

import ModelDeleteForm from "@/app/base/components/ModelDeleteForm";
import ModelActionForm from "@/app/base/components/ModelActionForm";
import { useCycled, useSendAnalyticsWhen } from "@/app/base/hooks";
import { actions as deviceActions } from "@/app/store/device";
import deviceSelectors from "@/app/store/device/selectors";
Expand Down Expand Up @@ -57,7 +57,7 @@ const RemoveInterface = ({
</span>
</Notification>
) : null}
<ModelDeleteForm
<ModelActionForm
aria-label="Remove interface"
initialValues={{}}
modelType="interface"
Expand Down
51 changes: 51 additions & 0 deletions src/app/domains/components/DomainForm/DomainForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { DomainListSidePanelContent } from "../../views/DomainsList/constants";
import { DomainListSidePanelViews } from "../../views/DomainsList/constants";

import DomainForm from "./DomainForm";

import { Labels as AddDomainLabels } from "@/app/domains/views/DomainsList/DomainListHeaderForm/DomainListHeaderForm";
import { Labels as DomainTableLabels } from "@/app/domains/views/DomainsList/DomainsTable/DomainsTable";
import {
domain as domainFactory,
domainState as domainStateFactory,
rootState as rootStateFactory,
} from "@/testing/factories";
import { renderWithBrowserRouter, screen } from "@/testing/utils";

const domain = domainFactory({ name: "test" });
const state = rootStateFactory({
domain: domainStateFactory({
items: [domain],
}),
});

it("can render the AddDomain form", () => {
const sidePanelContent: DomainListSidePanelContent = {
view: DomainListSidePanelViews.ADD_DOMAIN,
};
renderWithBrowserRouter(
<DomainForm
setSidePanelContent={vi.fn()}
sidePanelContent={sidePanelContent}
/>,
{ state }
);
expect(screen.getByRole("form", { name: AddDomainLabels.FormLabel }));
});

it("can render the SetDefault form", () => {
const sidePanelContent: DomainListSidePanelContent = {
view: DomainListSidePanelViews.SET_DEFAULT,
extras: {
id: domain.id,
},
};
renderWithBrowserRouter(
<DomainForm
setSidePanelContent={vi.fn()}
sidePanelContent={sidePanelContent}
/>,
{ state }
);
expect(screen.getByRole("form", { name: DomainTableLabels.FormTitle }));
});
41 changes: 41 additions & 0 deletions src/app/domains/components/DomainForm/DomainForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useCallback } from "react";

import SetDefaultForm from "../SetDefaultForm";

import type { SidePanelContentTypes } from "@/app/base/side-panel-context";
import DomainListHeaderForm from "@/app/domains/views/DomainsList/DomainListHeaderForm";
import { DomainListSidePanelViews } from "@/app/domains/views/DomainsList/constants";
import { isId } from "@/app/utils";

type Props = SidePanelContentTypes & {};

const DomainForm = ({
sidePanelContent,
setSidePanelContent,
}: Props): JSX.Element | null => {
const clearSidePanelContent = useCallback(
() => setSidePanelContent(null),
[setSidePanelContent]
);

if (!sidePanelContent) {
return null;
}

switch (sidePanelContent.view) {
case DomainListSidePanelViews.ADD_DOMAIN:
return <DomainListHeaderForm closeForm={clearSidePanelContent} />;
case DomainListSidePanelViews.SET_DEFAULT: {
const id =
sidePanelContent.extras && "id" in sidePanelContent.extras
? sidePanelContent.extras.id
: null;
if (!isId(id)) return null;
return <SetDefaultForm id={id} onClose={clearSidePanelContent} />;
}
default:
return null;
}
};

export default DomainForm;
1 change: 1 addition & 0 deletions src/app/domains/components/DomainForm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./DomainForm";
41 changes: 41 additions & 0 deletions src/app/domains/components/SetDefaultForm/SetDefaultForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import configureStore from "redux-mock-store";

import SetDefaultForm from "./SetDefaultForm";

import { Labels as DomainTableLabels } from "@/app/domains/views/DomainsList/DomainsTable/DomainsTable";
import type { RootState } from "@/app/store/root/types";
import {
domain as domainFactory,
domainState as domainStateFactory,
rootState as rootStateFactory,
} from "@/testing/factories";
import { renderWithBrowserRouter, screen, userEvent } from "@/testing/utils";

const mockStore = configureStore<RootState>();
const domain = domainFactory({ name: "test" });
const state = rootStateFactory({
domain: domainStateFactory({
items: [domain],
}),
});

it("renders", () => {
renderWithBrowserRouter(<SetDefaultForm id={domain.id} onClose={vi.fn()} />, {
state,
});
expect(screen.getByRole("form", { name: DomainTableLabels.FormTitle }));
expect(screen.getByText(DomainTableLabels.AreYouSure)).toBeInTheDocument();
});

it("dispatches the set default action", async () => {
const store = mockStore(state);
renderWithBrowserRouter(<SetDefaultForm id={domain.id} onClose={vi.fn()} />, {
store,
});
await userEvent.click(
screen.getByRole("button", { name: DomainTableLabels.ConfirmSetDefault })
);
expect(
store.getActions().some((action) => action.type === "domain/setDefault")
).toBe(true);
});
44 changes: 44 additions & 0 deletions src/app/domains/components/SetDefaultForm/SetDefaultForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useDispatch, useSelector } from "react-redux";

import ModelActionForm from "@/app/base/components/ModelActionForm";
import { Labels } from "@/app/domains/views/DomainsList/DomainsTable/DomainsTable";
import { actions as domainActions } from "@/app/store/domain";
import domainSelectors from "@/app/store/domain/selectors";
import type { Domain, DomainMeta } from "@/app/store/domain/types";

type Props = {
id: Domain[DomainMeta.PK];
onClose: () => void;
};
const SetDefaultForm = ({ id, onClose }: Props) => {
const dispatch = useDispatch();
const errors = useSelector(domainSelectors.errors);
const saving = useSelector(domainSelectors.saving);
const saved = useSelector(domainSelectors.saved);
return (
<ModelActionForm
aria-label={Labels.FormTitle}
errors={errors}
initialValues={{}}
message={Labels.AreYouSure}
modelType="DNS"
onCancel={() => {
dispatch(domainActions.cleanup());
onClose();
}}
onSubmit={() => {
dispatch(domainActions.setDefault(id));
}}
onSuccess={() => {
dispatch(domainActions.cleanup());
onClose();
}}
saved={saved}
saving={saving}
submitAppearance="positive"
submitLabel={Labels.ConfirmSetDefault}
/>
);
};

export default SetDefaultForm;
1 change: 1 addition & 0 deletions src/app/domains/components/SetDefaultForm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./SetDefaultForm";
27 changes: 11 additions & 16 deletions src/app/domains/views/DomainsList/DomainsList.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,36 @@
import { useSelector } from "react-redux";

import DomainListHeader from "./DomainListHeader";
import DomainListHeaderForm from "./DomainListHeaderForm";
import DomainsTable from "./DomainsTable";
import { DomainListSidePanelViews } from "./constants";

import PageContent from "@/app/base/components/PageContent";
import { useFetchActions, useWindowTitle } from "@/app/base/hooks";
import { useSidePanel } from "@/app/base/side-panel-context";
import DomainForm from "@/app/domains/components/DomainForm";
import { actions } from "@/app/store/domain";
import domainsSelectors from "@/app/store/domain/selectors";
import { getSidePanelTitle } from "@/app/store/utils/node/base";

const DomainsList = (): JSX.Element => {
const domains = useSelector(domainsSelectors.all);
const { sidePanelContent, setSidePanelContent } = useSidePanel();

let content: JSX.Element | null = null;

if (sidePanelContent?.view === DomainListSidePanelViews.ADD_DOMAIN) {
content = (
<DomainListHeaderForm
closeForm={() => {
setSidePanelContent(null);
}}
/>
);
}

useWindowTitle("DNS");

useFetchActions([actions.fetch]);

return (
<PageContent
header={<DomainListHeader setSidePanelContent={setSidePanelContent} />}
sidePanelContent={content}
sidePanelTitle={"Add domains"}
sidePanelContent={
sidePanelContent && (
<DomainForm
setSidePanelContent={setSidePanelContent}
sidePanelContent={sidePanelContent}
/>
)
}
sidePanelTitle={getSidePanelTitle("Domains", sidePanelContent)}
>
{domains.length > 0 && <DomainsTable />}
</PageContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { MemoryRouter } from "react-router-dom";
import { CompatRouter } from "react-router-dom-v5-compat";
import configureStore from "redux-mock-store";

import { DomainListSidePanelViews } from "../constants";

import DomainsTable, { Labels as DomainsTableLabels } from "./DomainsTable";

import * as sidePanelHooks from "@/app/base/side-panel-context";
import type { RootState } from "@/app/store/root/types";
import {
domain as domainFactory,
Expand All @@ -23,7 +26,14 @@ const mockStore = configureStore();

describe("DomainsTable", () => {
let state: RootState;
const setSidePanelContent = vi.fn();
beforeEach(() => {
vi.spyOn(sidePanelHooks, "useSidePanel").mockReturnValue({
setSidePanelContent,
sidePanelContent: null,
setSidePanelSize: vi.fn(),
sidePanelSize: "regular",
});
state = rootStateFactory({
domain: domainStateFactory({
items: [
Expand Down Expand Up @@ -87,7 +97,7 @@ describe("DomainsTable", () => {
).toBeInTheDocument();
});

it("calls the setDefault action if set default is clicked", async () => {
it("triggers the setDefault sidepanel if set default is clicked", async () => {
const store = mockStore(state);

render(
Expand Down Expand Up @@ -116,29 +126,9 @@ describe("DomainsTable", () => {
screen.getByRole("button", { name: DomainsTableLabels.SetDefault })
);

row = screen.getByRole("row", { name: "a" });
await userEvent.click(
within(
within(row).getByRole("gridcell", {
name: DomainsTableLabels.TableAction,
})
).getByRole("button", { name: DomainsTableLabels.ConfirmSetDefault })
expect(setSidePanelContent).toHaveBeenCalledWith(
expect.objectContaining({ view: DomainListSidePanelViews.SET_DEFAULT })
);

expect(
store.getActions().find((action) => action.type === "domain/setDefault")
).toStrictEqual({
type: "domain/setDefault",
meta: {
method: "set_default",
model: "domain",
},
payload: {
params: {
domain: 3,
},
},
});
});

it("displays an empty table message", () => {
Expand Down
Loading

0 comments on commit 1b19de7

Please sign in to comment.