From 6647d3ab149470416c843c86abc7ff2d50ddf0f1 Mon Sep 17 00:00:00 2001 From: Rory Doak <138574807+RODO94@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:22:24 +0000 Subject: [PATCH] feat: remove `onDelete` for Placeholder tag for non Platform admiins (#3959) --- .../@planx/components/Checklist/Editor.tsx | 35 +++--- .../components/Flow/components/Tag.tsx | 5 +- .../src/ui/editor/ComponentTagSelect.test.tsx | 119 ++++++++++++++++++ .../src/ui/editor/ComponentTagSelect.tsx | 64 ++++++---- 4 files changed, 182 insertions(+), 41 deletions(-) create mode 100644 editor.planx.uk/src/ui/editor/ComponentTagSelect.test.tsx diff --git a/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx b/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx index f3bf25a6d5..0afc79ade7 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx @@ -1,7 +1,6 @@ import Delete from "@mui/icons-material/Delete"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; -import FormControlLabel from "@mui/material/FormControlLabel"; import IconButton from "@mui/material/IconButton"; import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; import { FormikErrors, FormikValues, useFormik } from "formik"; @@ -378,19 +377,19 @@ export const ChecklistComponent: React.FC = (props) => { /> - - formik.setValues({ - ...formik.values, - ...toggleExpandableChecklist({ - options: formik.values.options, - groupedOptions: formik.values.groupedOptions, - }), - }) - } - label="Expandable" - /> + + formik.setValues({ + ...formik.values, + ...toggleExpandableChecklist({ + options: formik.values.options, + groupedOptions: formik.values.groupedOptions, + }), + }) + } + label="Expandable" + /> = (props) => { !formik.values.allRequired, ) } - label="All required" - /> + label="All required" + /> + onChange={() => formik.setFieldValue( "neverAutoAnswer", - !formik.values.neverAutoAnswer + !formik.values.neverAutoAnswer, ) } label="Always put to user (forgo automation)" diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Tag.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Tag.tsx index c411ce2852..f1a64e42e2 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Tag.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Tag.tsx @@ -1,6 +1,6 @@ import Box from "@mui/material/Box"; import { Palette, useTheme } from "@mui/material/styles"; -import { NodeTag } from "@opensystemslab/planx-core/types"; +import { NodeTag, Role } from "@opensystemslab/planx-core/types"; import { useStore } from "pages/FlowEditor/lib/store"; import React from "react"; import { getContrastTextColor } from "styleUtils"; @@ -8,11 +8,12 @@ import { FONT_WEIGHT_SEMI_BOLD } from "theme"; export const TAG_DISPLAY_VALUES: Record< NodeTag, - { color: keyof Palette["nodeTag"]; displayName: string } + { color: keyof Palette["nodeTag"]; displayName: string; editableBy?: Role[] } > = { placeholder: { color: "blocking", displayName: "Placeholder", + editableBy: ["platformAdmin"], }, toReview: { color: "nonBlocking", diff --git a/editor.planx.uk/src/ui/editor/ComponentTagSelect.test.tsx b/editor.planx.uk/src/ui/editor/ComponentTagSelect.test.tsx new file mode 100644 index 0000000000..54efa0cc6d --- /dev/null +++ b/editor.planx.uk/src/ui/editor/ComponentTagSelect.test.tsx @@ -0,0 +1,119 @@ +import ChecklistComponent from "@planx/components/Checklist/Editor"; +import { within } from "@testing-library/react"; +import { TAG_DISPLAY_VALUES } from "pages/FlowEditor/components/Flow/components/Tag"; +import { useStore } from "pages/FlowEditor/lib/store"; +import React from "react"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { act } from "react-dom/test-utils"; +import { setup } from "testUtils"; +import { it } from "vitest"; + +const { setState } = useStore; + +const mockUser = { + id: 200, + firstName: "Testy", + lastName: "McTester", + email: "test@email.com", + teams: [], +}; + +describe("Checklist Component for a Platform Admin", () => { + beforeEach(() => + act(() => + setState({ + user: { + ...mockUser, + isPlatformAdmin: true, + }, + teamSlug: "team", + }), + ), + ); + + it("renders all tags with none selected", async () => { + const { getByRole, user } = setup( + + + , + ); + const tagSelect = getByRole("combobox", { name: /tag this component/i }); + + await user.click(tagSelect); + + const optionsList = getByRole("listbox", { name: /tag this component/i }); + const options = within(optionsList).getAllByRole("option"); + + const tagDisplayNames = Object.values(TAG_DISPLAY_VALUES).map( + (tag) => tag.displayName, + ); + const optionTexts = options.map((option) => option.textContent); + + expect(optionTexts).toEqual(expect.arrayContaining(tagDisplayNames)); + }); + + it("renders all tags with Placeholder selected as a button", async () => { + const { queryByTestId, queryByRole } = setup( + + + , + ); + + const placeholderChip = queryByTestId("placeholder-chip"); + const placeholderButton = queryByRole("button", { name: /placeholder/i }); + + expect(placeholderChip).toBeInTheDocument(); + expect(placeholderButton).toBeInTheDocument(); + }); +}); + +describe("Checklist Component for a non Platform Admin", () => { + beforeEach(() => + act(() => + setState({ + user: { + ...mockUser, + isPlatformAdmin: false, + }, + }), + ), + ); + + it("renders all tags except Placeholder with none selected", async () => { + const { getByRole, user } = setup( + + + , + ); + const tagSelect = getByRole("combobox", { name: /tag this component/i }); + + await user.click(tagSelect); + + const optionsList = getByRole("listbox", { name: /tag this component/i }); + const options = within(optionsList).getAllByRole("option"); + const optionTexts = options.map((option) => option.textContent); + + expect(optionTexts).not.toContain(/placeholder/i); + }); + + it("renders all tags with static Placeholder selected", async () => { + const { getByTestId, queryByRole } = setup( + + + , + ); + + const placeholderChip = getByTestId("placeholder-chip"); + const placeholderButton = queryByRole("button", { name: /placeholder/i }); + + expect(placeholderChip).toBeInTheDocument(); + expect(placeholderButton).not.toBeInTheDocument(); + }); +}); diff --git a/editor.planx.uk/src/ui/editor/ComponentTagSelect.tsx b/editor.planx.uk/src/ui/editor/ComponentTagSelect.tsx index d055481444..11ffc1579f 100644 --- a/editor.planx.uk/src/ui/editor/ComponentTagSelect.tsx +++ b/editor.planx.uk/src/ui/editor/ComponentTagSelect.tsx @@ -2,8 +2,9 @@ import BookmarksIcon from "@mui/icons-material/Bookmarks"; import { AutocompleteProps } from "@mui/material/Autocomplete"; import Chip from "@mui/material/Chip"; import ListItem from "@mui/material/ListItem"; -import { NODE_TAGS, NodeTag } from "@opensystemslab/planx-core/types"; +import { NODE_TAGS, NodeTag, Role } from "@opensystemslab/planx-core/types"; import { TAG_DISPLAY_VALUES } from "pages/FlowEditor/components/Flow/components/Tag"; +import { useStore } from "pages/FlowEditor/lib/store"; import React from "react"; import { getContrastTextColor } from "styleUtils"; import ModalSection from "ui/editor/ModalSection"; @@ -16,18 +17,29 @@ interface Props { onChange: (values: NodeTag[]) => void; } +const skipTag = (role?: Role) => { + const userRole = useStore.getState().getUserRoleForCurrentTeam(); + return role === userRole ? false : true; +}; + const renderOption: AutocompleteProps< NodeTag, true, true, false, "div" ->["renderOption"] = (props, tag, { selected }) => ( - - -); +>["renderOption"] = (props, tag, { selected }) => { + if (TAG_DISPLAY_VALUES[tag].editableBy?.some(skipTag)) return null; + return ( + + + ); +}; const renderTags: AutocompleteProps< NodeTag, @@ -36,20 +48,30 @@ const renderTags: AutocompleteProps< false, "div" >["renderTags"] = (value, getTagProps) => - value.map((tag, index) => ( - ({ - backgroundColor: theme.palette.nodeTag[TAG_DISPLAY_VALUES[tag].color], - color: getContrastTextColor( - theme.palette.nodeTag[TAG_DISPLAY_VALUES[tag].color], - "#FFF", - ), - })} - /> - )); + value.map((tag, index) => { + return ( + ({ + backgroundColor: theme.palette.nodeTag[TAG_DISPLAY_VALUES[tag].color], + color: getContrastTextColor( + theme.palette.nodeTag[TAG_DISPLAY_VALUES[tag].color], + "#FFF", + ), + })} + onDelete={ + TAG_DISPLAY_VALUES[tag].editableBy?.some(skipTag) + ? undefined + : getTagProps({ index }).onDelete + } + /> + ); + }); export const ComponentTagSelect: React.FC = ({ value, onChange }) => { return (