Skip to content

Commit

Permalink
WD-11592 - refactor: remove nested components in ApplicationsTab (#1772)
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimir-cucu authored Jun 18, 2024
1 parent 136ccb8 commit 592b3c4
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { screen, within } from "@testing-library/react";

import type { RootState } from "store/store";
import { rootStateFactory } from "testing/factories";
import { applicationOfferStatusFactory } from "testing/factories/juju/ClientV6";
import {
modelDataFactory,
modelDataInfoFactory,
} from "testing/factories/juju/juju";
import { renderComponent } from "testing/utils";

import AppOffersTable from "./AppOffersTable";
import { Label } from "./types";

describe("ApplicationsTab", () => {
let state: RootState;
const path = "/models/:userName/:modelName";
const url = "/models/test@external/test-model";

beforeEach(() => {
state = rootStateFactory.build({});
});

it("should show empty message", () => {
renderComponent(<AppOffersTable />, { path, url, state });
expect(within(screen.getByRole("grid")).getAllByRole("row")).toHaveLength(
1,
);
expect(screen.getByText(Label.NO_OFFERS)).toBeInTheDocument();
});

it("should show application offers", () => {
state.juju.modelData = {
test123: modelDataFactory.build({
info: modelDataInfoFactory.build({
uuid: "test123",
name: "test-model",
}),
offers: {
db: applicationOfferStatusFactory.build({
endpoints: {
mockEndpoint: {
interface: "mockInterface",
limit: 1,
name: "mockName",
role: "mockRole",
},
},
}),
},
}),
};
renderComponent(<AppOffersTable />, { path, url, state });
const rows = within(screen.getByRole("grid")).getAllByRole("row");
expect(rows).toHaveLength(2);
const headers = within(rows[0]).getAllByRole("columnheader");
expect(headers).toHaveLength(4);
expect(headers[0]).toHaveTextContent("offers");
expect(headers[1]).toHaveTextContent("interface");
expect(headers[2]).toHaveTextContent("connection");
expect(headers[3]).toHaveTextContent("offer url");
const appOffersRow = within(rows[1]).getAllByRole("gridcell");
expect(appOffersRow).toHaveLength(4);
expect(appOffersRow[0]).toHaveTextContent("db");
expect(appOffersRow[1]).toHaveTextContent("mockEndpoint");
expect(appOffersRow[2]).toHaveTextContent("1 / 2");
expect(appOffersRow[3]).toHaveTextContent("-");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { MainTable } from "@canonical/react-components";
import { useMemo } from "react";

import useModelStatus from "hooks/useModelStatus";
import { appsOffersTableHeaders } from "tables/tableHeaders";
import { generateAppOffersRows } from "tables/tableRows";

import { Label } from "./types";

const AppOffersTable = () => {
const modelStatusData = useModelStatus();
const appOffersRows = useMemo(
() => generateAppOffersRows(modelStatusData),
[modelStatusData],
);

return (
<MainTable
headers={appsOffersTableHeaders}
rows={appOffersRows}
className="entity-details__offers p-main-table"
sortable
emptyStateMsg={Label.NO_OFFERS}
/>
);
};

export default AppOffersTable;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./AppOffersTable";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum Label {
NO_OFFERS = "There are no offers associated with this model",
}
85 changes: 12 additions & 73 deletions src/pages/EntityDetails/Model/ApplicationsTab/ApplicationsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { MainTable } from "@canonical/react-components";
import { useMemo } from "react";
import { useSelector } from "react-redux";
import { useParams } from "react-router-dom";

import type { Chip } from "components/ChipGroup";
import ChipGroup from "components/ChipGroup";
import ContentReveal from "components/ContentReveal";
import type { EntityDetailsRoute } from "components/Routes";
import useModelStatus from "hooks/useModelStatus";
Expand All @@ -13,38 +9,15 @@ import {
getModelApplications,
getModelUUIDFromList,
} from "store/juju/selectors";
import { pluralize } from "store/juju/utils/models";
import {
appsOffersTableHeaders,
remoteApplicationTableHeaders,
} from "tables/tableHeaders";
import {
generateAppOffersRows,
generateRemoteApplicationRows,
} from "tables/tableRows";

import { renderCounts } from "../../counts";

import AppOffersTable from "./AppOffersTable";
import ContentRevealTitle from "./ContentRevealTitle";
import LocalAppsTable from "./LocalAppsTable";
import RemoteAppsTable from "./RemoteAppsTable";
import SearchResults from "./SearchResults/SearchResults";

const ContentRevealTitle = ({
count,
subject,
chips,
}: {
count: number;
subject: "Offer" | "Local application" | "Remote application";
chips: Chip | null;
}) => (
<>
<span>
{count} {pluralize(count, subject)}
</span>
<ChipGroup chips={chips} className="u-no-margin" descriptor={null} />
</>
);

export default function ApplicationsTab() {
const [queryParams] = useQueryParams<{
entity: string | null;
Expand All @@ -65,23 +38,18 @@ export default function ApplicationsTab() {
? Object.keys(applications).length
: 0;

const remoteApplicationTableRows = useMemo(() => {
return modelName && userName
? generateRemoteApplicationRows(modelStatusData)
: [];
}, [modelStatusData, modelName, userName]);

const appOffersRows = useMemo(
() => generateAppOffersRows(modelStatusData),
[modelStatusData],
);
const LocalAppChips = renderCounts("localApps", modelStatusData);
const appOffersChips = renderCounts("offers", modelStatusData);
const remoteAppChips = renderCounts("remoteApps", modelStatusData);

const appOffersTableLength = appOffersRows?.length;
const appOffersTableLength = modelStatusData
? Object.keys(modelStatusData?.offers).length
: 0;

const remoteAppsTableLength = remoteApplicationTableRows?.length;
const remoteAppsTableLength =
modelName && userName && modelStatusData
? Object.keys(modelStatusData["remote-applications"]).length
: 0;

const countVisibleTables = (tablesLengths: number[]) =>
tablesLengths.filter((rowLength) => rowLength > 0).length;
Expand All @@ -92,35 +60,6 @@ export default function ApplicationsTab() {
appOffersTableLength,
]);

const AppOffersTable = () => (
<>
{!!appOffersTableLength && (
<>
<MainTable
headers={appsOffersTableHeaders}
rows={appOffersRows}
className="entity-details__offers p-main-table"
sortable
emptyStateMsg={"There are no offers associated with this model"}
/>
</>
)}
</>
);

const RemoteAppsTable = () => (
<>
{!!remoteAppsTableLength && (
<MainTable
headers={remoteApplicationTableHeaders}
rows={remoteApplicationTableRows}
className="entity-details__remote-apps p-main-table"
sortable
emptyStateMsg={"There are no remote applications in this model"}
/>
)}
</>
);
const getContentReveals = () => {
return (
<>
Expand Down Expand Up @@ -189,8 +128,8 @@ export default function ApplicationsTab() {
return (
<>
<LocalAppsTable applications={applications} />
<AppOffersTable />
<RemoteAppsTable />
{!!appOffersTableLength && <AppOffersTable />}
{!!remoteAppsTableLength && <RemoteAppsTable />}
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { screen } from "@testing-library/react";

import { renderComponent } from "testing/utils";

import ContentRevealTitle from "./ContentRevealTitle";

describe("ContentRevealTitle", () => {
it("should display correct info for single offer and no chip group", () => {
renderComponent(
<ContentRevealTitle count={1} subject="Offer" chips={null} />,
);
expect(screen.getByText("1 Offer")).toBeInTheDocument();
expect(document.querySelector(".chip-group")).not.toBeInTheDocument();
});

it("should display correct info for multiple local applications and no chip group", () => {
renderComponent(
<ContentRevealTitle count={3} subject="Local application" chips={null} />,
);
expect(screen.getByText("3 Local applications")).toBeInTheDocument();
expect(document.querySelector(".chip-group")).not.toBeInTheDocument();
});

it("should display correct info for single remote application and chip group", () => {
const fakeChips = {
foo: 1,
bar: 2,
baz: 3,
};
renderComponent(
<ContentRevealTitle
count={1}
subject="Remote application"
chips={fakeChips}
/>,
);
expect(screen.getByText("1 Remote application")).toBeInTheDocument();
expect(document.querySelector(".chip-group")).toBeInTheDocument();
expect(screen.getByText("1 foo")).toHaveClass("is-foo");
expect(screen.getByText("2 bar")).toHaveClass("is-bar");
expect(screen.getByText("3 baz")).toHaveClass("is-baz");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Chip } from "components/ChipGroup";
import ChipGroup from "components/ChipGroup";
import { pluralize } from "store/juju/utils/models";

type Props = {
count: number;
subject: "Offer" | "Local application" | "Remote application";
chips: Chip | null;
};

const ContentRevealTitle = ({ count, subject, chips }: Props) => (
<>
<span>
{count} {pluralize(count, subject)}
</span>
<ChipGroup chips={chips} className="u-no-margin" descriptor={null} />
</>
);

export default ContentRevealTitle;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./ContentRevealTitle";
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { screen, within } from "@testing-library/react";

import type { RootState } from "store/store";
import { rootStateFactory } from "testing/factories";
import { remoteApplicationStatusFactory } from "testing/factories/juju/ClientV6";
import {
modelDataFactory,
modelDataInfoFactory,
} from "testing/factories/juju/juju";
import { renderComponent } from "testing/utils";

import RemoteAppsTable from "./RemoteAppsTable";
import { Label } from "./types";

describe("ApplicationsTab", () => {
let state: RootState;
const path = "/models/:userName/:modelName";
const url = "/models/test@external/test-model";

beforeEach(() => {
state = rootStateFactory.build({});
});

it("should show empty message", () => {
renderComponent(<RemoteAppsTable />, { path, url, state });
expect(within(screen.getByRole("grid")).getAllByRole("row")).toHaveLength(
1,
);
expect(screen.getByText(Label.NO_REMOTE_APPS)).toBeInTheDocument();
});

it("should show remote applications", () => {
state.juju.modelData = {
test123: modelDataFactory.build({
info: modelDataInfoFactory.build({
uuid: "test123",
name: "test-model",
}),
"remote-applications": {
mysql: remoteApplicationStatusFactory.build({
relations: { mockRelation: [] },
}),
},
}),
};
renderComponent(<RemoteAppsTable />, { path, url, state });
const rows = within(screen.getByRole("grid")).getAllByRole("row");
expect(rows).toHaveLength(2);
const headers = within(rows[0]).getAllByRole("columnheader");
expect(headers).toHaveLength(5);
expect(headers[0]).toHaveTextContent("remote apps");
expect(headers[1]).toHaveTextContent("status");
expect(headers[2]).toHaveTextContent("interface");
expect(headers[3]).toHaveTextContent("offer url");
expect(headers[4]).toHaveTextContent("store");
const remoteAppsRow = within(rows[1]).getAllByRole("gridcell");
expect(remoteAppsRow).toHaveLength(5);
expect(remoteAppsRow[0]).toHaveTextContent("mysql");
expect(remoteAppsRow[1]).toHaveTextContent("active");
expect(remoteAppsRow[2]).toHaveTextContent("mockRelation");
expect(remoteAppsRow[3]).toHaveTextContent(
"juju-controller:admin/cmr.mysql",
);
expect(remoteAppsRow[4]).toHaveTextContent("-");
});
});
Loading

0 comments on commit 592b3c4

Please sign in to comment.