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: Restore EditorNavMenu for platformAdmins #3999

Merged
merged 2 commits into from
Nov 22, 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
205 changes: 205 additions & 0 deletions editor.planx.uk/src/components/EditorNavMenu.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { within } from "@testing-library/react";
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import * as ReactNavi from "react-navi";
import { setup } from "testUtils";
import { Mocked, vi } from "vitest";

import EditorNavMenu from "./EditorNavMenu";

vi.mock("react-navi", () => ({
useCurrentRoute: vi.fn(),
useNavigation: () => ({ navigate: vi.fn() }),
// Mock completed loading process
useLoadingRoute: () => undefined,
}));

const mockNavi = ReactNavi as Mocked<typeof ReactNavi>;

let mockTeamName: string | undefined = undefined;
let mockFlowName: string | undefined = undefined;
let mockAnalyticsLink: string | undefined = undefined;
const mockGetUserRoleForCurrentTeam = vi.fn();

vi.mock("pages/FlowEditor/lib/store", async () => ({
useStore: vi.fn(() => [
mockTeamName,
mockFlowName,
mockAnalyticsLink,
mockGetUserRoleForCurrentTeam(),
]),
}));

describe("globalLayoutRoutes", () => {
beforeEach(() => {
mockNavi.useCurrentRoute.mockReturnValue({
url: { href: "/" },
} as ReturnType<typeof mockNavi.useCurrentRoute>);
});

it("does not display for teamEditors", () => {
mockGetUserRoleForCurrentTeam.mockReturnValue("teamEditor");

const { queryAllByRole } = setup(<EditorNavMenu />);
const menuItems = queryAllByRole("listitem");
expect(menuItems).toHaveLength(0);
});

it("displays for platformAdmins", () => {
mockGetUserRoleForCurrentTeam.mockReturnValue("platformAdmin");

const { getAllByRole } = setup(<EditorNavMenu />);
const menuItems = getAllByRole("listitem");
expect(menuItems).toHaveLength(3);
expect(within(menuItems[0]).getByText("Select a team")).toBeInTheDocument();
});
});

describe("teamLayoutRoutes", () => {
beforeEach(() => {
mockNavi.useCurrentRoute.mockReturnValue({
url: { href: "/test-team" },
} as ReturnType<typeof mockNavi.useCurrentRoute>);
mockTeamName = "test-team";
});

it("does not display for teamViewers", () => {
mockGetUserRoleForCurrentTeam.mockReturnValue("teamViewer");

const { queryAllByRole } = setup(<EditorNavMenu />);
const menuItems = queryAllByRole("listitem");
expect(menuItems).toHaveLength(0);
});

it("displays for teamEditors", () => {
mockGetUserRoleForCurrentTeam.mockReturnValue("teamEditor");

const { getAllByRole } = setup(<EditorNavMenu />);
const menuItems = getAllByRole("listitem");
expect(menuItems).toHaveLength(4);
expect(within(menuItems[0]).getByText("Services")).toBeInTheDocument();
});

it("displays for platformAdmins", () => {
mockGetUserRoleForCurrentTeam.mockReturnValue("platformAdmin");

const { getAllByRole } = setup(<EditorNavMenu />);
const menuItems = getAllByRole("listitem");
expect(menuItems).toHaveLength(4);
expect(within(menuItems[0]).getByText("Services")).toBeInTheDocument();
});
});

describe("flowLayoutRoutes", () => {
beforeEach(() => {
mockNavi.useCurrentRoute.mockReturnValue({
url: { href: "/test-team/test-flow" },
} as ReturnType<typeof mockNavi.useCurrentRoute>);
mockTeamName = "test-team";
mockFlowName = "test-flow";
});

it("does not display for teamViewers", () => {
mockGetUserRoleForCurrentTeam.mockReturnValue("teamViewer");

const { queryAllByRole } = setup(<EditorNavMenu />);
const menuItems = queryAllByRole("listitem");
expect(menuItems).toHaveLength(0);
});

it("displays for teamEditors", () => {
mockGetUserRoleForCurrentTeam.mockReturnValue("teamEditor");

const { getAllByRole, getByLabelText } = setup(<EditorNavMenu />);
const menuItems = getAllByRole("listitem");
expect(menuItems).toHaveLength(5);
expect(getByLabelText("Submissions log")).toBeInTheDocument();
});

it("displays for platformAdmins", () => {
mockGetUserRoleForCurrentTeam.mockReturnValue("platformAdmin");

const { getAllByRole, getByLabelText } = setup(<EditorNavMenu />);
const menuItems = getAllByRole("listitem");
expect(menuItems).toHaveLength(5);
expect(getByLabelText("Submissions log")).toBeInTheDocument();
});
});

describe("flowAnalyticsRoute", () => {
beforeEach(() => {
mockNavi.useCurrentRoute.mockReturnValue({
url: { href: "/test-team/test-flow" },
} as ReturnType<typeof mockNavi.useCurrentRoute>);
mockTeamName = "test-team";
mockFlowName = "test-flow";
mockGetUserRoleForCurrentTeam.mockReturnValue("teamEditor");
});

it("is disabled without an analytics link", () => {
const { getByRole } = setup(<EditorNavMenu />);
expect(getByRole("button", { name: /Analytics/ })).toBeDisabled();
});

it("is enabled with an analytics link", () => {
mockAnalyticsLink = "https://link-to-metabase";

const { getByRole } = setup(<EditorNavMenu />);
expect(getByRole("button", { name: /Analytics/ })).not.toBeDisabled();
});
});

describe("layout", () => {
it("displays in a full mode on global routes", () => {
mockNavi.useCurrentRoute.mockReturnValue({
url: { href: "/" },
} as ReturnType<typeof mockNavi.useCurrentRoute>);
mockGetUserRoleForCurrentTeam.mockReturnValue("platformAdmin");

const { queryAllByRole, queryByLabelText } = setup(<EditorNavMenu />);
const menuItems = queryAllByRole("listitem");

// Tooltip not present
expect(queryByLabelText("Select a team")).not.toBeInTheDocument();

// Full text present
expect(within(menuItems[0]).getByText("Select a team")).toBeInTheDocument();
});

it("displays in a full mode on team routes", () => {
mockNavi.useCurrentRoute.mockReturnValue({
url: { href: "/test-team" },
} as ReturnType<typeof mockNavi.useCurrentRoute>);
mockGetUserRoleForCurrentTeam.mockReturnValue("platformAdmin");
mockTeamName = "test-team";

const { queryAllByRole, queryByLabelText } = setup(<EditorNavMenu />);
const menuItems = queryAllByRole("listitem");

// Tooltip not present
expect(queryByLabelText("Services")).not.toBeInTheDocument();

// Full text present
expect(within(menuItems[0]).getByText("Services")).toBeInTheDocument();
});

it("displays in a compact mode on flow routes", () => {
mockNavi.useCurrentRoute.mockReturnValue({
url: { href: "/test-team/test-flow" },
} as ReturnType<typeof mockNavi.useCurrentRoute>);
mockGetUserRoleForCurrentTeam.mockReturnValue("platformAdmin");
mockTeamName = "test-team";
mockFlowName = "test-flow";

const { queryAllByRole, getByLabelText } = setup(<EditorNavMenu />);
const menuItems = queryAllByRole("listitem");

// Tooltip present
expect(getByLabelText("Submissions log")).toBeInTheDocument();

// Full text present
expect(
within(menuItems[0]).queryByText("Submissions log"),
).not.toBeInTheDocument();
});
});
18 changes: 10 additions & 8 deletions editor.planx.uk/src/components/EditorNavMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,12 @@ function EditorNavMenu() {
const { navigate } = useNavigation();
const { url } = useCurrentRoute();
const isRouteLoading = useLoadingRoute();
const [teamSlug, flowSlug, flowAnalyticsLink, role] =
useStore((state) => [
state.teamSlug,
state.flowSlug,
state.flowAnalyticsLink,
state.getUserRoleForCurrentTeam()
]);
const [teamSlug, flowSlug, flowAnalyticsLink, role] = useStore((state) => [
state.teamSlug,
state.flowSlug,
state.flowAnalyticsLink,
state.getUserRoleForCurrentTeam(),
]);

const isActive = (route: string) => url.href.endsWith(route);

Expand Down Expand Up @@ -242,7 +241,9 @@ function EditorNavMenu() {

const { routes, compact } = getRoutesForUrl(url.href);

const visibleRoutes = routes.filter(({ accessibleBy }) => role && accessibleBy.includes(role));
const visibleRoutes = routes.filter(
({ accessibleBy }) => role && accessibleBy.includes(role),
);

// Hide menu if the user does not have a selection of items
if (visibleRoutes.length < 2) return null;
Expand All @@ -256,6 +257,7 @@ function EditorNavMenu() {
<Tooltip title={title} placement="right">
<Box component="span">
<MenuButton
title={title}
isActive={isActive(route)}
disabled={disabled}
disableRipple
Expand Down
8 changes: 5 additions & 3 deletions editor.planx.uk/src/pages/FlowEditor/lib/store/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,17 @@ export const userStore: StateCreator<

getUserRoleForCurrentTeam: () => {
const { user, teamSlug } = get();
if (!user || !teamSlug) return;
if (!user) return;

if (user.isPlatformAdmin) return "platformAdmin";

const currentUserTeam = user.teams.find(({ team: { slug } }) => slug === teamSlug );
const currentUserTeam = user.teams.find(
({ team: { slug } }) => slug === teamSlug,
);
if (!currentUserTeam) return;

return currentUserTeam.role;
}
},
});

const getLoggedInUser = async () => {
Expand Down
Loading