diff --git a/CHANGELOG.md b/CHANGELOG.md index 2de093277..9a43235a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Instructions empty state to show when instructions are editable (#1165, #1168) - Allow `instructions` attribute to override instructions attached to the project (#1169) - Instructions tabs for edit and viewing (#1167) - +- Add remove instructions button modal (#1176) ### Changed diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx index d57e6cf98..2371ec5a9 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx @@ -16,8 +16,10 @@ import { setCurrentStepPosition } from "../../../../redux/InstructionsSlice"; import DesignSystemButton from "../../../DesignSystemButton/DesignSystemButton"; import { setProjectInstructions } from "../../../../redux/EditorSlice"; import demoInstructions from "../../../../assets/markdown/demoInstructions.md"; +import RemoveInstructionsModal from "../../../Modals/RemoveInstructionsModal"; const InstructionsPanel = () => { + const [showModal, setShowModal] = useState(false); const instructionsEditable = useSelector( (state) => state.editor?.instructionsEditable, ); @@ -104,6 +106,11 @@ const InstructionsPanel = () => { dispatch(setProjectInstructions(demoInstructions)); }; + const removeInstructions = () => { + dispatch(setProjectInstructions(null)); + setShowModal(false); + }; + const AddInstructionsButton = () => { return ( { ); }; + const RemoveInstructionsButton = () => { + return ( + { + setShowModal(true); + }} + fill + textAlways + small + /> + ); + }; const onChange = (e) => { dispatch(setProjectInstructions(e.target.value)); }; @@ -126,7 +147,13 @@ const InstructionsPanel = () => {
@@ -191,6 +218,29 @@ const InstructionsPanel = () => { >
)} + {showModal && ( + , + setShowModal(false)} + />, + ]} + isOpen={showModal} + setShowModal={setShowModal} + /> + )}
); }; diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js index 54c443aae..167af5152 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js @@ -4,6 +4,7 @@ import { Provider } from "react-redux"; import configureStore from "redux-mock-store"; import { setProjectInstructions } from "../../../../redux/EditorSlice"; import { act } from "react"; +import Modal from "react-modal"; window.HTMLElement.prototype.scrollTo = jest.fn(); window.Prism = { @@ -14,6 +15,13 @@ describe("When instructionsEditable is true", () => { describe("When there are instructions", () => { let store; + beforeAll(() => { + const root = global.document.createElement("div"); + root.setAttribute("id", "app"); + global.document.body.appendChild(root); + Modal.setAppElement("#app"); + }); + beforeEach(() => { const mockStore = configureStore([]); const initialState = { @@ -67,6 +75,21 @@ describe("When instructionsEditable is true", () => { screen.queryByText("instructionsPanel.emptyState.addInstructions"), ).not.toBeInTheDocument(); }); + + test("Renders the remove instructions button", () => { + expect( + screen.queryByText("instructionsPanel.removeInstructions"), + ).toBeInTheDocument(); + }); + + test("Remove instructions modal is opened", () => { + const button = screen.queryByText("instructionsPanel.removeInstructions"); + fireEvent.click(button); + + expect( + screen.queryByText("instructionsPanel.removeInstructionsModal.heading"), + ).toBeInTheDocument(); + }); }); describe("When there are no instructions", () => { @@ -101,6 +124,12 @@ describe("When instructionsEditable is true", () => { ).toBeInTheDocument(); }); + test("Does not render the remove instructions button", () => { + expect( + screen.queryByText("instructionsPanel.removeInstructions"), + ).not.toBeInTheDocument(); + }); + test("Clicking the add instructions button adds the demo instructions", () => { const addInstructionsButton = screen.getByText( "instructionsPanel.emptyState.addInstructions", @@ -153,6 +182,12 @@ describe("When instructions are not editable", () => { ).not.toBeInTheDocument(); }); + test("Does not render the remove instructions button", () => { + expect( + screen.queryByText("instructionsPanel.removeInstructions"), + ).not.toBeInTheDocument(); + }); + test("Does not render the instructions explanation", () => { expect( screen.queryByText("instructionsPanel.emptyState.purpose"), diff --git a/src/components/Menus/Sidebar/Sidebar.jsx b/src/components/Menus/Sidebar/Sidebar.jsx index 18205614f..86987a52b 100644 --- a/src/components/Menus/Sidebar/Sidebar.jsx +++ b/src/components/Menus/Sidebar/Sidebar.jsx @@ -107,11 +107,13 @@ const Sidebar = ({ options = [] }) => { instructionsEditable || instructionsSteps ? "instructions" : "file", ); + const hasInstructions = instructionsSteps && instructionsSteps.length > 0; + useEffect(() => { - if (instructionsEditable || instructionsSteps) { + if (instructionsEditable || hasInstructions) { setOption("instructions"); } - }, [instructionsEditable, instructionsSteps]); + }, [instructionsEditable, hasInstructions]); const toggleOption = (newOption) => { if (option !== newOption) { diff --git a/src/components/Modals/RemoveInstructionsModal.jsx b/src/components/Modals/RemoveInstructionsModal.jsx new file mode 100644 index 000000000..cb1422d6b --- /dev/null +++ b/src/components/Modals/RemoveInstructionsModal.jsx @@ -0,0 +1,60 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +import GeneralModal from "./GeneralModal"; + +const RemoveInstructionsModal = (props) => { + const { + buttons = null, + isOpen = false, + withCloseButton = false, + setShowModal = null, + } = props; + + const { t } = useTranslation(); + + return ( + setShowModal(false)} + withCloseButton={withCloseButton} + > +
+

+ {t( + "instructionsPanel.removeInstructionsModal.resultRemovingInstructions", + )} +

+
    +
  • + {t( + "instructionsPanel.removeInstructionsModal.instructionsWillBeDeleted", + )} +
  • +
  • + {t( + "instructionsPanel.removeInstructionsModal.studentsWorkingProjectNotRecievedInstructions", + )} +
  • +
  • + {t( + "instructionsPanel.removeInstructionsModal.studentsStartedWillSeeInstructions", + )} +
  • +
+
+
+ ); +}; + +export default RemoveInstructionsModal; diff --git a/src/components/WebComponentProject/WebComponentProject.jsx b/src/components/WebComponentProject/WebComponentProject.jsx index 92156014f..1a17274cb 100644 --- a/src/components/WebComponentProject/WebComponentProject.jsx +++ b/src/components/WebComponentProject/WebComponentProject.jsx @@ -83,18 +83,20 @@ const WebComponentProject = ({ }, [projectIdentifier]); useEffect(() => { - if (!projectInstructions || !permitInstructionsOverride) return; + if (!permitInstructionsOverride) return; dispatch( setInstructions({ project: { - steps: [ - { - quiz: false, - title: "", - content: marked.parse(projectInstructions), - }, - ], + steps: projectInstructions + ? [ + { + quiz: false, + title: "", + content: marked.parse(projectInstructions), + }, + ] + : [], }, permitOverride: true, }), diff --git a/src/components/WebComponentProject/WebComponentProject.test.js b/src/components/WebComponentProject/WebComponentProject.test.js index 2141c095d..7aaad7e45 100644 --- a/src/components/WebComponentProject/WebComponentProject.test.js +++ b/src/components/WebComponentProject/WebComponentProject.test.js @@ -120,11 +120,16 @@ describe("When there are no instructions", () => { }); test("Does not dispatch action to set instructions", () => { - expect(store.getActions()).not.toEqual( + expect(store.getActions()).toEqual( expect.arrayContaining([ { type: "instructions/setInstructions", - payload: expect.any(Object), + payload: { + permitOverride: true, + project: { + steps: [], + }, + }, }, ]), ); diff --git a/src/utils/i18n.js b/src/utils/i18n.js index c69e5a40c..554844233 100644 --- a/src/utils/i18n.js +++ b/src/utils/i18n.js @@ -216,11 +216,27 @@ i18n purpose: "Instructions can be added to your project to guide students.", }, + removeInstructions: "Remove instructions", nextStep: "Next step", previousStep: "Previous step", projectSteps: "Project steps", edit: "Edit", view: "View", + removeInstructionsModal: { + removeInstructions: "Remove instructions", + close: "Close", + heading: "Remove project instructions?", + removeInstuctionsWarning: + "You are about to remove the instructions from the project.", + resultRemovingInstructions: + "As a result of removing these instructions:", + instructionsWillBeDeleted: + "Instructions content will be deleted.", + studentsWorkingProjectNotRecievedInstructions: + "Students who start working on this project will not receive instructions.", + studentsStartedWillSeeInstructions: + "Students who have already started working on this project will still be able to see the instructions as they were when they started.", + }, }, projectsPanel: { projects: "Projects",