diff --git a/client/components/edit/EditParentsButton.test.tsx b/client/components/edit/EditParentsButton.test.tsx index 98b29e1b2..c968f5089 100644 --- a/client/components/edit/EditParentsButton.test.tsx +++ b/client/components/edit/EditParentsButton.test.tsx @@ -12,18 +12,31 @@ jest.mock("../../context/EditorContext", () => ({ }, })); +const mockUseGlobalContext = jest.fn(); +jest.mock("../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); + }, +})); + describe("EditParentsButton", () => { let editorValues; + let globalValues; let pid: string; beforeEach(() => { pid = "foo:123"; editorValues = { action: { setParentsModalActivePid: jest.fn(), - toggleParentsModal: jest.fn(), }, }; mockUseEditorContext.mockReturnValue(editorValues); + globalValues = { + action: { + openModal: jest.fn(), + }, + }; + mockUseGlobalContext.mockReturnValue(globalValues); }); it("renders", () => { @@ -36,6 +49,6 @@ describe("EditParentsButton", () => { await userEvent.setup().click(screen.getByRole("button")); expect(editorValues.action.setParentsModalActivePid).toHaveBeenCalledWith(pid); - expect(editorValues.action.toggleParentsModal).toHaveBeenCalled(); + expect(globalValues.action.openModal).toHaveBeenCalledWith("parents"); }); }); diff --git a/client/components/edit/EditParentsButton.tsx b/client/components/edit/EditParentsButton.tsx index 4c4e7c267..c25ae7ad1 100644 --- a/client/components/edit/EditParentsButton.tsx +++ b/client/components/edit/EditParentsButton.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { useGlobalContext } from "../../context/GlobalContext"; import { useEditorContext } from "../../context/EditorContext"; export interface ObjectStatusProps { @@ -7,12 +8,16 @@ export interface ObjectStatusProps { export const EditParentsButton = ({ pid }: ObjectStatusProps): React.ReactElement => { const { - action: { setParentsModalActivePid, toggleParentsModal }, + action: { openModal }, + } = useGlobalContext(); + + const { + action: { setParentsModalActivePid }, } = useEditorContext(); const clickAction = () => { setParentsModalActivePid(pid); - toggleParentsModal(); + openModal("parents"); }; return ; }; diff --git a/client/components/edit/EditorSnackbar.test.tsx b/client/components/edit/EditorSnackbar.test.tsx index 2c04c8f08..351f887b8 100644 --- a/client/components/edit/EditorSnackbar.test.tsx +++ b/client/components/edit/EditorSnackbar.test.tsx @@ -5,18 +5,18 @@ import userEvent from "@testing-library/user-event"; import renderer from "react-test-renderer"; import EditorSnackbar from "./EditorSnackbar"; -const mockUseEditorContext = jest.fn(); -jest.mock("../../context/EditorContext", () => ({ - useEditorContext: () => { - return mockUseEditorContext(); +const mockUseGlobalContext = jest.fn(); +jest.mock("../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); }, })); jest.mock("./children/ChildList", () => () => "ChildList"); describe("EditorSnackbar", () => { - let editorValues; + let globalValues; beforeEach(() => { - editorValues = { + globalValues = { state: { snackbarState: { message: "test1", @@ -28,7 +28,7 @@ describe("EditorSnackbar", () => { setSnackbarState: jest.fn(), }, }; - mockUseEditorContext.mockReturnValue(editorValues); + mockUseGlobalContext.mockReturnValue(globalValues); }); it("renders", () => { @@ -47,7 +47,7 @@ describe("EditorSnackbar", () => { await userEvent.setup().click(screen.getByRole("button")); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: false, message: "", severity: "info", diff --git a/client/components/edit/EditorSnackbar.tsx b/client/components/edit/EditorSnackbar.tsx index a24d14a3a..1c5c15dbf 100644 --- a/client/components/edit/EditorSnackbar.tsx +++ b/client/components/edit/EditorSnackbar.tsx @@ -3,7 +3,7 @@ import Alert from "@mui/material/Alert"; import IconButton from "@mui/material/IconButton"; import Snackbar from "@mui/material/Snackbar"; import CloseIcon from "@mui/icons-material/Close"; -import { useEditorContext } from "../../context/EditorContext"; +import { useGlobalContext } from "../../context/GlobalContext"; const EditorSnackbar = (): React.ReactElement => { const { @@ -11,7 +11,7 @@ const EditorSnackbar = (): React.ReactElement => { snackbarState: { message, open, severity }, }, action: { setSnackbarState }, - } = useEditorContext(); + } = useGlobalContext(); const handleClose = () => { setSnackbarState({ diff --git a/client/components/edit/ObjectStatus.test.tsx b/client/components/edit/ObjectStatus.test.tsx index 56c59186e..66e8ce92a 100644 --- a/client/components/edit/ObjectStatus.test.tsx +++ b/client/components/edit/ObjectStatus.test.tsx @@ -5,14 +5,18 @@ import renderer from "react-test-renderer"; import { ObjectStatusProps, ObjectStatus } from "./ObjectStatus"; import { EditorContextProvider, ObjectDetails } from "../../context/EditorContext"; import { FetchContextProvider } from "../../context/FetchContext"; +import { GlobalContextProvider } from "../../context/GlobalContext"; function getMountedObjectStatusComponent(props: ObjectStatusProps) { return renderer.create( - - - - - , + + + + + + + , + , ); } diff --git a/client/components/edit/ObjectStatus.tsx b/client/components/edit/ObjectStatus.tsx index 6e230ea7d..3cbfe67ca 100644 --- a/client/components/edit/ObjectStatus.tsx +++ b/client/components/edit/ObjectStatus.tsx @@ -1,5 +1,6 @@ import styles from "./ObjectStatus.module.css"; import React from "react"; +import { useGlobalContext } from "../../context/GlobalContext"; import { useEditorContext } from "../../context/EditorContext"; import ObjectLoader from "./ObjectLoader"; @@ -8,9 +9,12 @@ export interface ObjectStatusProps { } export const ObjectStatus = ({ pid }: ObjectStatusProps): React.ReactElement => { + const { + action: { openModal }, + } = useGlobalContext(); const { state: { objectDetailsStorage }, - action: { setStateModalActivePid, toggleStateModal }, + action: { setStateModalActivePid }, } = useEditorContext(); const loaded = Object.prototype.hasOwnProperty.call(objectDetailsStorage, pid); const details = loaded ? objectDetailsStorage[pid] : {}; @@ -18,7 +22,7 @@ export const ObjectStatus = ({ pid }: ObjectStatusProps): React.ReactElement => const stateTxt = details.state ?? "Unknown"; const clickAction = () => { setStateModalActivePid(pid); - toggleStateModal(); + openModal("state"); }; const stateMsg = loaded ? ( + + ◉ + +   + Unknown + , + ",", +] `; exports[`ObjectStatus displays the state found in the response 1`] = ` - + + ◉ + +   + Inactive + , + ",", +] `; diff --git a/client/components/edit/__snapshots__/StateModal.test.tsx.snap b/client/components/edit/__snapshots__/StateModal.test.tsx.snap index c00251fa2..8222bb942 100644 --- a/client/components/edit/__snapshots__/StateModal.test.tsx.snap +++ b/client/components/edit/__snapshots__/StateModal.test.tsx.snap @@ -11,7 +11,7 @@ exports[`StateModal renders correctly for a loaded object with children 1`] = ` className="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium closeButton css-78trlr-MuiButtonBase-root-MuiIconButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} @@ -316,7 +316,7 @@ exports[`StateModal renders correctly for a loaded object without children 1`] = className="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium closeButton css-78trlr-MuiButtonBase-root-MuiIconButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} @@ -567,7 +567,7 @@ exports[`StateModal renders correctly for a pending object 1`] = ` className="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium closeButton css-78trlr-MuiButtonBase-root-MuiIconButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} @@ -821,7 +821,7 @@ exports[`StateModal renders correctly when closed 1`] = ` className="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium closeButton css-78trlr-MuiButtonBase-root-MuiIconButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} diff --git a/client/components/edit/datastream/DatastreamAgentsContent.test.tsx b/client/components/edit/datastream/DatastreamAgentsContent.test.tsx index 2015c9a01..a00007663 100644 --- a/client/components/edit/datastream/DatastreamAgentsContent.test.tsx +++ b/client/components/edit/datastream/DatastreamAgentsContent.test.tsx @@ -22,6 +22,12 @@ jest.mock("../../../context/EditorContext", () => ({ return mockUseEditorContext(); }, })); +const mockUseGlobalContext = jest.fn(); +jest.mock("../../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); + }, +})); const mockUseDatastreamOperation = jest.fn(); jest.mock("../../../hooks/useDatastreamOperation", () => () => { return mockUseDatastreamOperation(); @@ -30,6 +36,7 @@ jest.mock("@mui/material/Grid", () => (props) => props.children); describe("DatastreamAgentsContent", () => { let editorValues; + let globalValues; let datastreamOperationValues; beforeEach(() => { editorValues = { @@ -45,7 +52,11 @@ describe("DatastreamAgentsContent", () => { }, action: { setCurrentAgents: jest.fn(), - toggleDatastreamModal: jest.fn(), + }, + }; + globalValues = { + action: { + closeModal: jest.fn(), }, }; datastreamOperationValues = { @@ -53,6 +64,7 @@ describe("DatastreamAgentsContent", () => { getAgents: jest.fn(), }; mockUseEditorContext.mockReturnValue(editorValues); + mockUseGlobalContext.mockReturnValue(globalValues); mockUseDatastreamOperation.mockReturnValue(datastreamOperationValues); }); @@ -81,7 +93,7 @@ describe("DatastreamAgentsContent", () => { await userEvent.setup().click(screen.getByText("Save Changes")); expect(datastreamOperationValues.uploadAgents).toHaveBeenCalled(); - expect(editorValues.action.toggleDatastreamModal).not.toHaveBeenCalled(); + expect(globalValues.action.closeModal).not.toHaveBeenCalled(); }); it("saves current agents and closes the modal", async () => { @@ -94,7 +106,7 @@ describe("DatastreamAgentsContent", () => { await userEvent.setup().click(screen.getByText("Save And Close")); expect(datastreamOperationValues.uploadAgents).toHaveBeenCalled(); - expect(editorValues.action.toggleDatastreamModal).toHaveBeenCalled(); + expect(globalValues.action.closeModal).toHaveBeenCalledWith("datastream"); }); it("resets current agents on cancel", async () => { @@ -109,6 +121,6 @@ describe("DatastreamAgentsContent", () => { expect(datastreamOperationValues.uploadAgents).not.toHaveBeenCalled(); expect(datastreamOperationValues.getAgents).toHaveBeenCalled(); expect(editorValues.action.setCurrentAgents).toHaveBeenCalled(); - expect(editorValues.action.toggleDatastreamModal).toHaveBeenCalled(); + expect(globalValues.action.closeModal).toHaveBeenCalledWith("datastream"); }); }); diff --git a/client/components/edit/datastream/DatastreamAgentsContent.tsx b/client/components/edit/datastream/DatastreamAgentsContent.tsx index 5ef90d239..e817f2b52 100644 --- a/client/components/edit/datastream/DatastreamAgentsContent.tsx +++ b/client/components/edit/datastream/DatastreamAgentsContent.tsx @@ -4,6 +4,7 @@ import DialogActions from "@mui/material/DialogActions"; import DialogContent from "@mui/material/DialogContent"; import Grid from "@mui/material/Grid"; import { useEditorContext } from "../../../context/EditorContext"; +import { useGlobalContext } from "../../../context/GlobalContext"; import useDatastreamOperation from "../../../hooks/useDatastreamOperation"; import CircularProgress from "@mui/material/CircularProgress"; import Divider from "@mui/material/Divider"; @@ -15,8 +16,11 @@ import Box from "@mui/material/Box"; const DatastreamAgentsContent = (): React.ReactElement => { const { state: { agentsCatalog, currentAgents }, - action: { setCurrentAgents, toggleDatastreamModal }, + action: { setCurrentAgents }, } = useEditorContext(); + const { + action: { closeModal }, + } = useGlobalContext(); const { uploadAgents, getAgents } = useDatastreamOperation(); const { defaults: { role, type, name }, @@ -150,7 +154,7 @@ const DatastreamAgentsContent = (): React.ReactElement => { disabled={!canSave} onClick={async () => { await saveCurrentAgents(); - toggleDatastreamModal(); + closeModal("datastream"); }} > Save And Close @@ -161,7 +165,7 @@ const DatastreamAgentsContent = (): React.ReactElement => { className="agentsCancelButton" onClick={async () => { setCurrentAgents(await getAgents()); - toggleDatastreamModal(); + closeModal("datastream"); }} > Cancel diff --git a/client/components/edit/datastream/DatastreamControlButton.test.tsx b/client/components/edit/datastream/DatastreamControlButton.test.tsx index fda675213..4ceb3b4a4 100644 --- a/client/components/edit/datastream/DatastreamControlButton.test.tsx +++ b/client/components/edit/datastream/DatastreamControlButton.test.tsx @@ -12,23 +12,35 @@ jest.mock("../../../context/EditorContext", () => ({ return mockUseEditorContext(); }, })); +const mockUseGlobalContext = jest.fn(); +jest.mock("../../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); + }, +})); const mockUseDatastreamOperation = jest.fn(); jest.mock("../../../hooks/useDatastreamOperation", () => () => mockUseDatastreamOperation()); describe("DatastreamControlButton", () => { let editorValues; + let globalValues; let datastreamOperationValues; beforeEach(() => { editorValues = { action: { - toggleDatastreamModal: jest.fn(), setActiveDatastream: jest.fn(), setDatastreamModalState: jest.fn(), }, }; + globalValues = { + action: { + openModal: jest.fn(), + }, + }; datastreamOperationValues = { downloadDatastream: jest.fn(), }; mockUseEditorContext.mockReturnValue(editorValues); + mockUseGlobalContext.mockReturnValue(globalValues); mockUseDatastreamOperation.mockReturnValue(datastreamOperationValues); }); @@ -52,6 +64,6 @@ describe("DatastreamControlButton", () => { await userEvent.setup().click(screen.getByRole("button")); expect(editorValues.action.setActiveDatastream).toHaveBeenCalledWith("THUMBNAIL"); expect(editorValues.action.setDatastreamModalState).toHaveBeenCalledWith("View"); - expect(editorValues.action.toggleDatastreamModal).toHaveBeenCalled(); + expect(globalValues.action.openModal).toHaveBeenCalledWith("datastream"); }); }); diff --git a/client/components/edit/datastream/DatastreamControlButton.tsx b/client/components/edit/datastream/DatastreamControlButton.tsx index cb91f285b..e9a0374b4 100644 --- a/client/components/edit/datastream/DatastreamControlButton.tsx +++ b/client/components/edit/datastream/DatastreamControlButton.tsx @@ -7,6 +7,7 @@ import Delete from "@mui/icons-material/Delete"; import Preview from "@mui/icons-material/Preview"; import UploadFile from "@mui/icons-material/UploadFile"; import { useEditorContext } from "../../../context/EditorContext"; +import { useGlobalContext } from "../../../context/GlobalContext"; import useDatastreamOperation from "../../../hooks/useDatastreamOperation"; const Icons = { @@ -30,15 +31,18 @@ const DatastreamControlButton = ({ }: DatastreamControlButtonProps): React.ReactElement => { const [isLoading, setLoading] = useState(false); const { - action: { toggleDatastreamModal, setActiveDatastream, setDatastreamModalState }, + action: { setActiveDatastream, setDatastreamModalState }, } = useEditorContext(); + const { + action: { openModal }, + } = useGlobalContext(); const { downloadDatastream } = useDatastreamOperation(); const onClick = (modalState) => { if (modalState !== "Download") { return () => { setActiveDatastream(datastream); setDatastreamModalState(modalState); - toggleDatastreamModal(); + openModal("datastream"); }; } return async () => { diff --git a/client/components/edit/datastream/DatastreamDeleteModalContent.test.tsx b/client/components/edit/datastream/DatastreamDeleteModalContent.test.tsx index f3a78c061..3670b006b 100644 --- a/client/components/edit/datastream/DatastreamDeleteModalContent.test.tsx +++ b/client/components/edit/datastream/DatastreamDeleteModalContent.test.tsx @@ -5,33 +5,28 @@ import userEvent from "@testing-library/user-event"; import renderer from "react-test-renderer"; import DatastreamDeleteModalContent from "./DatastreamDeleteModalContent"; -const mockUseEditorContext = jest.fn(); -jest.mock("../../../context/EditorContext", () => ({ - useEditorContext: () => { - return mockUseEditorContext(); +const mockUseGlobalContext = jest.fn(); +jest.mock("../../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); }, })); const mockUseDatastreamOperation = jest.fn(); jest.mock("../../../hooks/useDatastreamOperation", () => () => mockUseDatastreamOperation()); describe("DatastreamDeleteModalContent", () => { - let editorValues; + let globalValues; let datastreamOperationValues; beforeEach(() => { - editorValues = { - state: { - currentPid: "vudl:123", - activeDatastream: "THUMBNAIL", - }, + globalValues = { action: { - loadCurrentObjectDetails: jest.fn().mockResolvedValue({}), setSnackbarState: jest.fn(), - toggleDatastreamModal: jest.fn(), + closeModal: jest.fn(), }, }; datastreamOperationValues = { deleteDatastream: jest.fn(), }; - mockUseEditorContext.mockReturnValue(editorValues); + mockUseGlobalContext.mockReturnValue(globalValues); mockUseDatastreamOperation.mockReturnValue(datastreamOperationValues); }); @@ -45,4 +40,10 @@ describe("DatastreamDeleteModalContent", () => { await userEvent.setup().click(screen.getByText("Yes")); expect(datastreamOperationValues.deleteDatastream).toHaveBeenCalled(); }); + + it("can be stopped", async () => { + render(); + await userEvent.setup().click(screen.getByText("No")); + expect(globalValues.action.closeModal).toHaveBeenCalledWith("datastream"); + }); }); diff --git a/client/components/edit/datastream/DatastreamDeleteModalContent.tsx b/client/components/edit/datastream/DatastreamDeleteModalContent.tsx index 03c6452a0..b6fed1c85 100644 --- a/client/components/edit/datastream/DatastreamDeleteModalContent.tsx +++ b/client/components/edit/datastream/DatastreamDeleteModalContent.tsx @@ -3,13 +3,13 @@ import Button from "@mui/material/Button"; import DialogContent from "@mui/material/DialogContent"; import DialogActions from "@mui/material/DialogActions"; import DialogContentText from "@mui/material/DialogContentText"; -import { useEditorContext } from "../../../context/EditorContext"; +import { useGlobalContext } from "../../../context/GlobalContext"; import useDatastreamOperation from "../../../hooks/useDatastreamOperation"; const DatastreamDeleteModalContent = (): React.ReactElement => { const { - action: { toggleDatastreamModal }, - } = useEditorContext(); + action: { closeModal }, + } = useGlobalContext(); const { deleteDatastream } = useDatastreamOperation(); return ( @@ -18,7 +18,7 @@ const DatastreamDeleteModalContent = (): React.ReactElement => { Are you sure you want to delete the datastream? - - + ); diff --git a/client/components/edit/datastream/DatastreamLicenseContent.test.tsx b/client/components/edit/datastream/DatastreamLicenseContent.test.tsx index 03d9395cb..bce7579c9 100644 --- a/client/components/edit/datastream/DatastreamLicenseContent.test.tsx +++ b/client/components/edit/datastream/DatastreamLicenseContent.test.tsx @@ -12,6 +12,12 @@ jest.mock("../../../context/EditorContext", () => ({ return mockUseEditorContext(); }, })); +const mockUseGlobalContext = jest.fn(); +jest.mock("../../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); + }, +})); const mockUseDatastreamOperation = jest.fn(); jest.mock("../../../hooks/useDatastreamOperation", () => () => { return mockUseDatastreamOperation(); @@ -19,6 +25,7 @@ jest.mock("../../../hooks/useDatastreamOperation", () => () => { describe("DatastreamLicenseContent", () => { let editorValues; + let globalValues; let datastreamOperationValues; beforeEach(() => { editorValues = { @@ -29,8 +36,10 @@ describe("DatastreamLicenseContent", () => { }, }, }, + }; + globalValues = { action: { - toggleDatastreamModal: jest.fn(), + closeModal: jest.fn(), }, }; datastreamOperationValues = { @@ -38,6 +47,7 @@ describe("DatastreamLicenseContent", () => { getLicenseKey: jest.fn(), }; mockUseEditorContext.mockReturnValue(editorValues); + mockUseGlobalContext.mockReturnValue(globalValues); mockUseDatastreamOperation.mockReturnValue(datastreamOperationValues); }); @@ -55,4 +65,13 @@ describe("DatastreamLicenseContent", () => { expect(datastreamOperationValues.getLicenseKey).toHaveBeenCalled(); expect(datastreamOperationValues.uploadLicense).toHaveBeenCalled(); }); + + it("can be canceled", async () => { + await act(async () => { + await render(); + }); + await fireEvent.click(screen.getByText("Cancel")); + expect(datastreamOperationValues.uploadLicense).not.toHaveBeenCalled(); + expect(globalValues.action.closeModal).toHaveBeenCalledWith("datastream"); + }); }); diff --git a/client/components/edit/datastream/DatastreamLicenseContent.tsx b/client/components/edit/datastream/DatastreamLicenseContent.tsx index 25c9eb9eb..4c546658d 100644 --- a/client/components/edit/datastream/DatastreamLicenseContent.tsx +++ b/client/components/edit/datastream/DatastreamLicenseContent.tsx @@ -9,12 +9,15 @@ import Radio from "@mui/material/Radio"; import RadioGroup from "@mui/material/RadioGroup"; import useDatastreamOperation from "../../../hooks/useDatastreamOperation"; import { useEditorContext } from "../../../context/EditorContext"; +import { useGlobalContext } from "../../../context/GlobalContext"; const DatastreamLicenseContent = (): React.ReactElement => { const { state: { licensesCatalog }, - action: { toggleDatastreamModal }, } = useEditorContext(); + const { + action: { closeModal }, + } = useGlobalContext(); const { uploadLicense, getLicenseKey } = useDatastreamOperation(); const [licenseKey, setLicenseKey] = useState(""); useEffect(() => { @@ -53,7 +56,7 @@ const DatastreamLicenseContent = (): React.ReactElement => { > Save - + ); diff --git a/client/components/edit/datastream/DatastreamModal.test.tsx b/client/components/edit/datastream/DatastreamModal.test.tsx index 2311a2e41..5adba1102 100644 --- a/client/components/edit/datastream/DatastreamModal.test.tsx +++ b/client/components/edit/datastream/DatastreamModal.test.tsx @@ -10,28 +10,40 @@ jest.mock("../../../context/EditorContext", () => ({ return mockUseEditorContext(); }, })); +const mockUseGlobalContext = jest.fn(); +jest.mock("../../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); + }, +})); jest.mock("./DatastreamUploadModalContent", () => () => "DatastreamUploadModalContent"); jest.mock("./DatastreamDeleteModalContent", () => () => "DatastreamDeleteModalContent"); describe("DatastreamModal", () => { let editorValues; + let globalValues; beforeEach(() => { editorValues = { state: { datastreamModalState: "", - isDatastreamModalOpen: true, }, + }; + mockUseEditorContext.mockReturnValue(editorValues); + globalValues = { action: { - toggleDatastreamModal: jest.fn(), + closeModal: jest.fn(), + isModalOpen: jest.fn(), }, }; - mockUseEditorContext.mockReturnValue(editorValues); + mockUseGlobalContext.mockReturnValue(globalValues); + globalValues.action.isModalOpen.mockReturnValue(true); }); it("toggles the datastreamModal", async () => { render(); await userEvent.setup().click(screen.getByRole("button")); - expect(editorValues.action.toggleDatastreamModal).toHaveBeenCalled(); + expect(globalValues.action.isModalOpen).toHaveBeenCalledWith("datastream"); + expect(globalValues.action.closeModal).toHaveBeenCalledWith("datastream"); }); it("switches to the delete modal content", () => { @@ -39,6 +51,7 @@ describe("DatastreamModal", () => { render(); expect(screen.queryAllByText("DatastreamDeleteModalContent")).toHaveLength(1); + expect(globalValues.action.isModalOpen).toHaveBeenCalledWith("datastream"); }); it("switches to the upload modal content", () => { @@ -46,5 +59,6 @@ describe("DatastreamModal", () => { render(); expect(screen.queryAllByText("DatastreamUploadModalContent")).toHaveLength(1); + expect(globalValues.action.isModalOpen).toHaveBeenCalledWith("datastream"); }); }); diff --git a/client/components/edit/datastream/DatastreamModal.tsx b/client/components/edit/datastream/DatastreamModal.tsx index 9bb28b0ba..db92b513d 100644 --- a/client/components/edit/datastream/DatastreamModal.tsx +++ b/client/components/edit/datastream/DatastreamModal.tsx @@ -4,6 +4,7 @@ import DialogTitle from "@mui/material/DialogTitle"; import Grid from "@mui/material/Grid"; import IconButton from "@mui/material/IconButton"; import CloseIcon from "@mui/icons-material/Close"; +import { useGlobalContext } from "../../../context/GlobalContext"; import { useEditorContext } from "../../../context/EditorContext"; import DatastreamUploadModalContent from "./DatastreamUploadModalContent"; import DatastreamDeleteModalContent from "./DatastreamDeleteModalContent"; @@ -23,15 +24,17 @@ const DatastreamModalContent = ({ datastreamModalState }: { datastreamModalState const DatastreamModal = (): React.ReactElement => { const { - state: { datastreamModalState, isDatastreamModalOpen }, - action: { toggleDatastreamModal }, + state: { datastreamModalState }, } = useEditorContext(); + const { + action: { closeModal, isModalOpen }, + } = useGlobalContext(); return ( closeModal("datastream")} fullWidth={true} maxWidth={"lg"} > @@ -41,7 +44,7 @@ const DatastreamModal = (): React.ReactElement => { {datastreamModalState} - + closeModal("datastream")}> diff --git a/client/components/edit/datastream/DatastreamProcessMetadataContent.test.tsx b/client/components/edit/datastream/DatastreamProcessMetadataContent.test.tsx index 953e2ad1a..3bbc07710 100644 --- a/client/components/edit/datastream/DatastreamProcessMetadataContent.test.tsx +++ b/client/components/edit/datastream/DatastreamProcessMetadataContent.test.tsx @@ -6,10 +6,10 @@ import renderer from "react-test-renderer"; import DatastreamProcessMetadataContent from "./DatastreamProcessMetadataContent"; import { waitFor } from "@testing-library/react"; -const mockUseEditorContext = jest.fn(); -jest.mock("../../../context/EditorContext", () => ({ - useEditorContext: () => { - return mockUseEditorContext(); +const mockUseGlobalContext = jest.fn(); +jest.mock("../../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); }, })); @@ -39,7 +39,7 @@ jest.mock("@mui/x-date-pickers", () => ({ describe("DatastreamProcessMetadataContent", () => { let datastreamOperationValues; - let editorValues; + let globalValues; let processMetadataValues; const renderComponent = async (fakeData = {}) => { @@ -70,8 +70,8 @@ describe("DatastreamProcessMetadataContent", () => { getProcessMetadata: jest.fn(), }; mockUseDatastreamOperation.mockReturnValue(datastreamOperationValues); - editorValues = { action: { toggleDatastreamModal: jest.fn() } }; - mockUseEditorContext.mockReturnValue(editorValues); + globalValues = { action: { closeModal: jest.fn() } }; + mockUseGlobalContext.mockReturnValue(globalValues); mockDatastreamProcessMetadataTaskProps = []; processMetadataValues = { state: {}, @@ -141,4 +141,11 @@ describe("DatastreamProcessMetadataContent", () => { mockDatastreamProcessMetadataTaskProps[0].deleteTask(); expect(processMetadataValues.action.deleteTask).toHaveBeenCalledWith(0); }); + + it("can be canceled", async () => { + await renderComponent(); + await userEvent.setup().click(screen.getByText("Cancel")); + expect(datastreamOperationValues.uploadProcessMetadata).not.toHaveBeenCalled(); + expect(globalValues.action.closeModal).toHaveBeenCalledWith("datastream"); + }); }); diff --git a/client/components/edit/datastream/DatastreamProcessMetadataContent.tsx b/client/components/edit/datastream/DatastreamProcessMetadataContent.tsx index 8b0c3fccf..4b8f2c5c5 100644 --- a/client/components/edit/datastream/DatastreamProcessMetadataContent.tsx +++ b/client/components/edit/datastream/DatastreamProcessMetadataContent.tsx @@ -6,7 +6,7 @@ import FormControl from "@mui/material/FormControl"; import FormLabel from "@mui/material/FormLabel"; import TextField from "@mui/material/TextField"; import useDatastreamOperation from "../../../hooks/useDatastreamOperation"; -import { useEditorContext } from "../../../context/EditorContext"; +import { useGlobalContext } from "../../../context/GlobalContext"; import { useProcessMetadataContext } from "../../../context/ProcessMetadataContext"; import BlurSavingTextField from "../../shared/BlurSavingTextField"; import Grid from "@mui/material/Grid"; @@ -22,8 +22,8 @@ let taskKeyGeneration = 0; const DatastreamProcessMetadataContent = (): React.ReactElement => { const { - action: { toggleDatastreamModal }, - } = useEditorContext(); + action: { closeModal }, + } = useGlobalContext(); const { state: processMetadata, action: { @@ -133,7 +133,7 @@ const DatastreamProcessMetadataContent = (): React.ReactElement => { > Save - + ); diff --git a/client/components/edit/datastream/__snapshots__/DatastreamDeleteModalContent.test.tsx.snap b/client/components/edit/datastream/__snapshots__/DatastreamDeleteModalContent.test.tsx.snap index b2f7b39ee..58b3515c9 100644 --- a/client/components/edit/datastream/__snapshots__/DatastreamDeleteModalContent.test.tsx.snap +++ b/client/components/edit/datastream/__snapshots__/DatastreamDeleteModalContent.test.tsx.snap @@ -18,7 +18,7 @@ exports[`DatastreamDeleteModalContent renders 1`] = ` className="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary noButton css-1e6y48t-MuiButtonBase-root-MuiButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} diff --git a/client/components/edit/datastream/__snapshots__/DatastreamDublinCoreContent.test.tsx.snap b/client/components/edit/datastream/__snapshots__/DatastreamDublinCoreContent.test.tsx.snap index 63aae1860..1d374846d 100644 --- a/client/components/edit/datastream/__snapshots__/DatastreamDublinCoreContent.test.tsx.snap +++ b/client/components/edit/datastream/__snapshots__/DatastreamDublinCoreContent.test.tsx.snap @@ -52,6 +52,7 @@ exports[`DatastreamDublinCoreContent renders 1`] = ` className="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary css-1e6y48t-MuiButtonBase-root-MuiButton-root" disabled={false} onBlur={[Function]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} diff --git a/client/components/edit/datastream/__snapshots__/DatastreamLicenseContent.test.tsx.snap b/client/components/edit/datastream/__snapshots__/DatastreamLicenseContent.test.tsx.snap index a5e8c7363..c26a3bc72 100644 --- a/client/components/edit/datastream/__snapshots__/DatastreamLicenseContent.test.tsx.snap +++ b/client/components/edit/datastream/__snapshots__/DatastreamLicenseContent.test.tsx.snap @@ -44,7 +44,7 @@ exports[`DatastreamLicenseContent renders 1`] = ` className="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary css-1e6y48t-MuiButtonBase-root-MuiButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} diff --git a/client/components/edit/datastream/__snapshots__/DatastreamProcessMetadataContent.test.tsx.snap b/client/components/edit/datastream/__snapshots__/DatastreamProcessMetadataContent.test.tsx.snap index 43501f6eb..a76867301 100644 --- a/client/components/edit/datastream/__snapshots__/DatastreamProcessMetadataContent.test.tsx.snap +++ b/client/components/edit/datastream/__snapshots__/DatastreamProcessMetadataContent.test.tsx.snap @@ -223,7 +223,7 @@ exports[`DatastreamProcessMetadataContent renders a form when empty data is load className="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary css-1e6y48t-MuiButtonBase-root-MuiButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} @@ -465,7 +465,7 @@ exports[`DatastreamProcessMetadataContent renders a form when non-empty data is className="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary css-1e6y48t-MuiButtonBase-root-MuiButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} diff --git a/client/components/edit/parents/ParentList.test.tsx b/client/components/edit/parents/ParentList.test.tsx index 6dab4a12b..0d2f14597 100644 --- a/client/components/edit/parents/ParentList.test.tsx +++ b/client/components/edit/parents/ParentList.test.tsx @@ -6,6 +6,12 @@ import renderer from "react-test-renderer"; import ParentList from "./ParentList"; import { waitFor } from "@testing-library/dom"; +const mockUseGlobalContext = jest.fn(); +jest.mock("../../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); + }, +})); const mockUseEditorContext = jest.fn(); jest.mock("../../../context/EditorContext", () => ({ useEditorContext: () => { @@ -22,11 +28,17 @@ jest.mock("../../../context/FetchContext", () => ({ jest.mock("@mui/icons-material/Delete", () => (props) => props.titleAccess); describe("ParentList", () => { + let globalValues; let editorValues; let fetchValues; let pid: string; beforeEach(() => { pid = "foo:123"; + globalValues = { + action: { + setSnackbarState: jest.fn(), + }, + }; editorValues = { state: { parentDetailsStorage: { @@ -69,7 +81,6 @@ describe("ParentList", () => { loadParentDetailsIntoStorage: jest.fn(), removeFromObjectDetailsStorage: jest.fn(), removeFromParentDetailsStorage: jest.fn(), - setSnackbarState: jest.fn(), }, }; fetchValues = { @@ -77,6 +88,7 @@ describe("ParentList", () => { fetchText: jest.fn(), }, }; + mockUseGlobalContext.mockReturnValue(globalValues); mockUseEditorContext.mockReturnValue(editorValues); mockUseFetchContext.mockReturnValue(fetchValues); }); @@ -127,7 +139,7 @@ describe("ParentList", () => { expect(editorValues.action.removeFromObjectDetailsStorage).toHaveBeenCalledWith(pid); expect(editorValues.action.removeFromParentDetailsStorage).toHaveBeenCalledWith(pid); expect(editorValues.action.clearPidFromChildListStorage).toHaveBeenCalledWith("foo:122"); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ message: "Successfully removed foo:123 from foo:122", open: true, severity: "info", @@ -152,11 +164,11 @@ describe("ParentList", () => { "http://localhost:9000/api/edit/object/foo%3A123/parent/foo%3A122", { method: "DELETE" }, ); - await waitFor(() => expect(editorValues.action.setSnackbarState).toHaveBeenCalled()); + await waitFor(() => expect(globalValues.action.setSnackbarState).toHaveBeenCalled()); expect(editorValues.action.removeFromObjectDetailsStorage).not.toHaveBeenCalled(); expect(editorValues.action.removeFromParentDetailsStorage).not.toHaveBeenCalled(); expect(editorValues.action.clearPidFromChildListStorage).not.toHaveBeenCalled(); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ message: "not ok", open: true, severity: "error", @@ -175,11 +187,11 @@ describe("ParentList", () => { "http://localhost:9000/api/edit/object/foo%3A123/parent/foo%3A122", { method: "DELETE" }, ); - await waitFor(() => expect(editorValues.action.setSnackbarState).toHaveBeenCalled()); + await waitFor(() => expect(globalValues.action.setSnackbarState).toHaveBeenCalled()); expect(editorValues.action.removeFromObjectDetailsStorage).not.toHaveBeenCalled(); expect(editorValues.action.removeFromParentDetailsStorage).not.toHaveBeenCalled(); expect(editorValues.action.clearPidFromChildListStorage).not.toHaveBeenCalled(); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ message: "boom", open: true, severity: "error", diff --git a/client/components/edit/parents/ParentList.tsx b/client/components/edit/parents/ParentList.tsx index e1c45890e..a0b536108 100644 --- a/client/components/edit/parents/ParentList.tsx +++ b/client/components/edit/parents/ParentList.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from "react"; +import { useGlobalContext } from "../../../context/GlobalContext"; import { useEditorContext } from "../../../context/EditorContext"; import { useFetchContext } from "../../../context/FetchContext"; import { getParentUrl } from "../../../util/routes"; @@ -10,6 +11,9 @@ export interface ParentListProps { } const ParentList = ({ pid, initiallyShallow = true }: ParentListProps): React.ReactElement => { + const { + action: { setSnackbarState }, + } = useGlobalContext(); const { state: { parentDetailsStorage }, action: { @@ -17,7 +21,6 @@ const ParentList = ({ pid, initiallyShallow = true }: ParentListProps): React.Re loadParentDetailsIntoStorage, removeFromObjectDetailsStorage, removeFromParentDetailsStorage, - setSnackbarState, }, } = useEditorContext(); const { diff --git a/client/components/edit/parents/ParentPicker.test.tsx b/client/components/edit/parents/ParentPicker.test.tsx index 821c31aec..af2eebfe8 100644 --- a/client/components/edit/parents/ParentPicker.test.tsx +++ b/client/components/edit/parents/ParentPicker.test.tsx @@ -7,6 +7,12 @@ import renderer from "react-test-renderer"; import ParentPicker from "./ParentPicker"; import { waitFor } from "@testing-library/dom"; +const mockUseGlobalContext = jest.fn(); +jest.mock("../../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); + }, +})); const mockUseEditorContext = jest.fn(); jest.mock("../../../context/EditorContext", () => ({ useEditorContext: () => { @@ -36,11 +42,17 @@ jest.mock("../PidPicker", () => (args) => { }); describe("ParentPicker", () => { + let globalValues; let editorValues; let fetchValues; const pid = "foo:123"; const parentPid = "foo:122"; beforeEach(() => { + globalValues = { + action: { + setSnackbarState: jest.fn(), + }, + }; editorValues = { state: { objectDetailsStorage: {}, @@ -49,7 +61,6 @@ describe("ParentPicker", () => { clearPidFromChildListStorage: jest.fn(), removeFromObjectDetailsStorage: jest.fn(), removeFromParentDetailsStorage: jest.fn(), - setSnackbarState: jest.fn(), }, }; fetchValues = { @@ -57,6 +68,7 @@ describe("ParentPicker", () => { fetchText: jest.fn(), }, }; + mockUseGlobalContext.mockReturnValue(globalValues); mockUseEditorContext.mockReturnValue(editorValues); mockUseFetchContext.mockReturnValue(fetchValues); }); @@ -97,7 +109,7 @@ describe("ParentPicker", () => { render(); await act(() => setSelected(parentPid)); await userEvent.setup().click(screen.getByRole("button")); - await waitFor(() => expect(editorValues.action.setSnackbarState).toHaveBeenCalled()); + await waitFor(() => expect(globalValues.action.setSnackbarState).toHaveBeenCalled()); expect(fetchValues.action.fetchText).toHaveBeenCalledWith( "http://localhost:9000/api/edit/object/foo%3A123/parent/foo%3A122", { body: "", method: "PUT" }, @@ -105,7 +117,7 @@ describe("ParentPicker", () => { expect(editorValues.action.removeFromObjectDetailsStorage).toHaveBeenCalledWith(pid); expect(editorValues.action.removeFromParentDetailsStorage).toHaveBeenCalledWith(pid); expect(editorValues.action.clearPidFromChildListStorage).toHaveBeenCalledWith(parentPid); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ message: "Successfully added foo:123 to foo:122", open: true, severity: "info", @@ -122,7 +134,7 @@ describe("ParentPicker", () => { render(); await act(() => setSelected(parentPid)); await userEvent.setup().click(screen.getByRole("button")); - await waitFor(() => expect(editorValues.action.setSnackbarState).toHaveBeenCalled()); + await waitFor(() => expect(globalValues.action.setSnackbarState).toHaveBeenCalled()); expect(fetchValues.action.fetchText).toHaveBeenCalledWith( "http://localhost:9000/api/edit/object/foo%3A123/parent/foo%3A122", { body: "", method: "PUT" }, @@ -130,7 +142,7 @@ describe("ParentPicker", () => { expect(editorValues.action.removeFromObjectDetailsStorage).not.toHaveBeenCalled(); expect(editorValues.action.removeFromParentDetailsStorage).not.toHaveBeenCalled(); expect(editorValues.action.clearPidFromChildListStorage).not.toHaveBeenCalled(); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ message: "kaboom", open: true, severity: "error", @@ -145,7 +157,7 @@ describe("ParentPicker", () => { render(); await act(() => setSelected(parentPid)); await userEvent.setup().click(screen.getByRole("button")); - await waitFor(() => expect(editorValues.action.setSnackbarState).toHaveBeenCalled()); + await waitFor(() => expect(globalValues.action.setSnackbarState).toHaveBeenCalled()); expect(fetchValues.action.fetchText).toHaveBeenCalledWith( "http://localhost:9000/api/edit/object/foo%3A123/parent/foo%3A122", { body: "", method: "PUT" }, @@ -153,7 +165,7 @@ describe("ParentPicker", () => { expect(editorValues.action.removeFromObjectDetailsStorage).not.toHaveBeenCalled(); expect(editorValues.action.removeFromParentDetailsStorage).not.toHaveBeenCalled(); expect(editorValues.action.clearPidFromChildListStorage).not.toHaveBeenCalled(); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ message: "not ok", open: true, severity: "error", @@ -182,7 +194,7 @@ describe("ParentPicker", () => { fireEvent.change(screen.getByRole("textbox", { name: "Position:" }), { target: { value: "100" } }); }); await userEvent.setup().click(screen.getByRole("button", { name: "Add" })); - await waitFor(() => expect(editorValues.action.setSnackbarState).toHaveBeenCalled()); + await waitFor(() => expect(globalValues.action.setSnackbarState).toHaveBeenCalled()); expect(fetchValues.action.fetchText).toHaveBeenCalledWith( "http://localhost:9000/api/edit/object/foo%3A123/parent/foo%3A122", { body: "100", method: "PUT" }, @@ -190,7 +202,7 @@ describe("ParentPicker", () => { expect(editorValues.action.removeFromObjectDetailsStorage).toHaveBeenCalledWith(pid); expect(editorValues.action.removeFromParentDetailsStorage).toHaveBeenCalledWith(pid); expect(editorValues.action.clearPidFromChildListStorage).toHaveBeenCalledWith(parentPid); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ message: "Successfully added foo:123 to foo:122", open: true, severity: "info", @@ -208,7 +220,7 @@ describe("ParentPicker", () => { await userEvent.setup().click(screen.getByRole("button")); await waitFor(() => expect(fetchValues.action.fetchText).toHaveBeenCalled()); await userEvent.setup().click(screen.getByRole("button", { name: "Add" })); - await waitFor(() => expect(editorValues.action.setSnackbarState).toHaveBeenCalled()); + await waitFor(() => expect(globalValues.action.setSnackbarState).toHaveBeenCalled()); expect(fetchValues.action.fetchText).toHaveBeenNthCalledWith( 1, "http://localhost:9000/api/edit/object/foo%3A122/lastChildPosition", @@ -222,7 +234,7 @@ describe("ParentPicker", () => { expect(editorValues.action.removeFromObjectDetailsStorage).toHaveBeenCalledWith(pid); expect(editorValues.action.removeFromParentDetailsStorage).toHaveBeenCalledWith(pid); expect(editorValues.action.clearPidFromChildListStorage).toHaveBeenCalledWith(parentPid); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ message: "Successfully added foo:123 to foo:122", open: true, severity: "info", @@ -242,7 +254,7 @@ describe("ParentPicker", () => { await userEvent.setup().click(screen.getByRole("button")); await waitFor(() => expect(fetchValues.action.fetchText).toHaveBeenCalled()); await userEvent.setup().click(screen.getByRole("button", { name: "Add" })); - await waitFor(() => expect(editorValues.action.setSnackbarState).toHaveBeenCalled()); + await waitFor(() => expect(globalValues.action.setSnackbarState).toHaveBeenCalled()); expect(fetchValues.action.fetchText).toHaveBeenNthCalledWith( 1, "http://localhost:9000/api/edit/object/foo%3A122/lastChildPosition", @@ -256,7 +268,7 @@ describe("ParentPicker", () => { expect(editorValues.action.removeFromObjectDetailsStorage).toHaveBeenCalledWith(pid); expect(editorValues.action.removeFromParentDetailsStorage).toHaveBeenCalledWith(pid); expect(editorValues.action.clearPidFromChildListStorage).toHaveBeenCalledWith(parentPid); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ message: "Successfully added foo:123 to foo:122", open: true, severity: "info", @@ -271,8 +283,8 @@ describe("ParentPicker", () => { await renderer.act(async () => { errorCallback(parentPid); }); - await waitFor(() => expect(editorValues.action.setSnackbarState).toHaveBeenCalled()); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + await waitFor(() => expect(globalValues.action.setSnackbarState).toHaveBeenCalled()); + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ message: "Cannot load details for foo:122. Are you sure this is a valid PID?", open: true, severity: "error", diff --git a/client/components/edit/parents/ParentPicker.tsx b/client/components/edit/parents/ParentPicker.tsx index 7fdc70fe0..c4f35ff3c 100644 --- a/client/components/edit/parents/ParentPicker.tsx +++ b/client/components/edit/parents/ParentPicker.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import ObjectLoader from "../ObjectLoader"; import PidPicker from "../PidPicker"; +import { useGlobalContext } from "../../../context/GlobalContext"; import { useEditorContext } from "../../../context/EditorContext"; import { useFetchContext } from "../../../context/FetchContext"; import { getObjectLastChildPositionUrl, getParentUrl } from "../../../util/routes"; @@ -10,14 +11,12 @@ interface ParentPickerProps { } const ParentPicker = ({ pid }: ParentPickerProps): React.ReactElement => { + const { + action: { setSnackbarState }, + } = useGlobalContext(); const { state: { objectDetailsStorage }, - action: { - clearPidFromChildListStorage, - removeFromObjectDetailsStorage, - removeFromParentDetailsStorage, - setSnackbarState, - }, + action: { clearPidFromChildListStorage, removeFromObjectDetailsStorage, removeFromParentDetailsStorage }, } = useEditorContext(); const { action: { fetchText }, diff --git a/client/components/edit/parents/ParentsModal.test.tsx b/client/components/edit/parents/ParentsModal.test.tsx index afc0f13cf..1bb173f9a 100644 --- a/client/components/edit/parents/ParentsModal.test.tsx +++ b/client/components/edit/parents/ParentsModal.test.tsx @@ -11,6 +11,12 @@ jest.mock("../../../context/EditorContext", () => ({ return mockUseEditorContext(); }, })); +const mockUseGlobalContext = jest.fn(); +jest.mock("../../../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); + }, +})); jest.mock("@mui/material/Dialog", () => (props) => props.children); jest.mock("@mui/material/DialogContent", () => (props) => props.children); jest.mock("@mui/material/DialogTitle", () => (props) => props.children); @@ -21,6 +27,7 @@ jest.mock("./ParentPicker", () => (props) => "ParentPicker: " + JSON.stringify(p describe("ParentsModal", () => { let editorValues; + let globalValues; let pid: string; beforeEach(() => { pid = "foo:123"; @@ -30,33 +37,41 @@ describe("ParentsModal", () => { isParentsModalOpen: true, parentsModalActivePid: pid, }, + }; + mockUseEditorContext.mockReturnValue(editorValues); + globalValues = { action: { - toggleParentsModal: jest.fn(), + closeModal: jest.fn(), + isModalOpen: jest.fn(), }, }; - mockUseEditorContext.mockReturnValue(editorValues); + mockUseGlobalContext.mockReturnValue(globalValues); + globalValues.action.isModalOpen.mockReturnValue(true); }); it("renders correctly for a non-loaded PID", () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); + expect(globalValues.action.isModalOpen).toHaveBeenCalledWith("parents"); }); it("renders correctly for a loaded PID", () => { editorValues.state.objectDetailsStorage[pid] = { pid }; const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); + expect(globalValues.action.isModalOpen).toHaveBeenCalledWith("parents"); }); it("renders correctly when PID is unset", () => { editorValues.state.parentsModalActivePid = null; const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); + expect(globalValues.action.isModalOpen).toHaveBeenCalledWith("parents"); }); it("toggles the modal", async () => { render(); await userEvent.setup().click(screen.getByRole("button")); - expect(editorValues.action.toggleParentsModal).toHaveBeenCalled(); + expect(globalValues.action.closeModal).toHaveBeenCalledWith("parents"); }); }); diff --git a/client/components/edit/parents/ParentsModal.tsx b/client/components/edit/parents/ParentsModal.tsx index 65e168171..018786a1f 100644 --- a/client/components/edit/parents/ParentsModal.tsx +++ b/client/components/edit/parents/ParentsModal.tsx @@ -5,6 +5,7 @@ import DialogTitle from "@mui/material/DialogTitle"; import Grid from "@mui/material/Grid"; import IconButton from "@mui/material/IconButton"; import CloseIcon from "@mui/icons-material/Close"; +import { useGlobalContext } from "../../../context/GlobalContext"; import { useEditorContext } from "../../../context/EditorContext"; import ObjectLoader from "../ObjectLoader"; import ParentList from "./ParentList"; @@ -12,9 +13,16 @@ import ParentPicker from "./ParentPicker"; const ParentsModal = (): React.ReactElement => { const { - state: { isParentsModalOpen, objectDetailsStorage, parentsModalActivePid }, - action: { toggleParentsModal }, + action: { isModalOpen, closeModal }, + } = useGlobalContext(); + const { + state: { parentsModalActivePid, objectDetailsStorage }, } = useEditorContext(); + + function closeParentModal() { + closeModal("parents"); + } + const loaded = Object.prototype.hasOwnProperty.call(objectDetailsStorage, parentsModalActivePid); const contents = ( @@ -26,14 +34,14 @@ const ParentsModal = (): React.ReactElement => { ); return ( - + Parents Editor ({parentsModalActivePid}) - + diff --git a/client/components/edit/parents/__snapshots__/ParentsModal.test.tsx.snap b/client/components/edit/parents/__snapshots__/ParentsModal.test.tsx.snap index 8fe8bed39..51031d663 100644 --- a/client/components/edit/parents/__snapshots__/ParentsModal.test.tsx.snap +++ b/client/components/edit/parents/__snapshots__/ParentsModal.test.tsx.snap @@ -9,7 +9,7 @@ exports[`ParentsModal renders correctly for a loaded PID 1`] = ` className="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium closeButton css-78trlr-MuiButtonBase-root-MuiIconButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} @@ -60,7 +60,7 @@ exports[`ParentsModal renders correctly for a non-loaded PID 1`] = ` className="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium closeButton css-78trlr-MuiButtonBase-root-MuiIconButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} @@ -99,7 +99,7 @@ exports[`ParentsModal renders correctly when PID is unset 1`] = ` className="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium closeButton css-78trlr-MuiButtonBase-root-MuiIconButton-root" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onContextMenu={[Function]} onDragLeave={[Function]} onFocus={[Function]} diff --git a/client/context/EditorContext.test.tsx b/client/context/EditorContext.test.tsx index 34ba452e0..26da8c0ef 100644 --- a/client/context/EditorContext.test.tsx +++ b/client/context/EditorContext.test.tsx @@ -53,34 +53,6 @@ describe("useEditorContext", () => { }); }); - describe("toggleDatastreamModal", () => { - it("toggles the modal", async () => { - const { result } = await renderHook(() => useEditorContext(), { wrapper: EditorContextProvider }); - - expect(result.current.state.isDatastreamModalOpen).toBeFalsy(); - - await act(async () => { - await result.current.action.toggleDatastreamModal(); - }); - - expect(result.current.state.isDatastreamModalOpen).toBeTruthy(); - }); - }); - - describe("toggleParentsModal", () => { - it("toggles the modal", async () => { - const { result } = await renderHook(() => useEditorContext(), { wrapper: EditorContextProvider }); - - expect(result.current.state.isParentsModalOpen).toBeFalsy(); - - await act(async () => { - await result.current.action.toggleParentsModal(); - }); - - expect(result.current.state.isParentsModalOpen).toBeTruthy(); - }); - }); - describe("setDatastreamModalState", () => { it("sets the modal state", async () => { const { result } = await renderHook(() => useEditorContext(), { wrapper: EditorContextProvider }); @@ -235,33 +207,6 @@ describe("useEditorContext", () => { }); }); - describe("setSnackbarState", () => { - it("sets the snackbar state with text and severity", async () => { - const { result } = await renderHook(() => useEditorContext(), { wrapper: EditorContextProvider }); - - expect(result.current.state.snackbarState).toEqual({ - open: false, - message: "", - severity: "info" - }); - - await act(async () => { - await result.current.action.setSnackbarState({ - open: true, - message: "oh no!", - severity: "error" - }); - }); - - expect(result.current.state.snackbarState).toEqual({ - open: true, - message: "oh no!", - severity: "error" - }); - }); - - }); - describe("extractFirstMetadataValue", () => { it("returns a default value if no matching field is found", async () => { const { result } = await renderHook(() => useEditorContext(), { wrapper: EditorContextProvider }); @@ -383,20 +328,6 @@ describe("useEditorContext", () => { }); }); - describe("toggleStateModal ", () => { - it("toggles the modal", async () => { - const { result } = await renderHook(() => useEditorContext(), { wrapper: EditorContextProvider }); - - expect(result.current.state.isStateModalOpen).toBeFalsy(); - - await act(async () => { - await result.current.action.toggleStateModal(); - }); - - expect(result.current.state.isStateModalOpen).toBeTruthy(); - }); - }); - describe("setStateModalActivePid", () => { it("sets the modal's active pid value", async () => { const { result } = await renderHook(() => useEditorContext(), { wrapper: EditorContextProvider }); diff --git a/client/context/EditorContext.tsx b/client/context/EditorContext.tsx index 7dcb20711..2dfe28994 100644 --- a/client/context/EditorContext.tsx +++ b/client/context/EditorContext.tsx @@ -19,12 +19,6 @@ interface ChildrenResultPage { docs?: Record[]; } -interface SnackbarState { - open: boolean, - message: string, - severity: string -} - export interface FedoraDatastream { mimetype?: { allowedType: string; @@ -59,7 +53,6 @@ interface EditorState { datastreamModalState: string | null; parentsModalActivePid: string | null; stateModalActivePid: string | null; - snackbarState: SnackbarState; objectDetailsStorage: Record; parentDetailsStorage: Record>; childListStorage: Record; @@ -88,11 +81,6 @@ const editorContextParams: EditorState = { datastreamModalState: null, parentsModalActivePid: null, stateModalActivePid: null, - snackbarState: { - open: false, - message: "", - severity: "info" - }, objectDetailsStorage: {}, parentDetailsStorage: {}, childListStorage: {}, @@ -128,14 +116,13 @@ const reducerMapping: Record = { SET_DATASTREAM_MODAL_STATE: "datastreamModalState", SET_PARENTS_MODAL_ACTIVE_PID: "parentsModalActivePid", SET_STATE_MODAL_ACTIVE_PID: "stateModalActivePid", - SET_SNACKBAR_STATE: "snackbarState", SET_TOP_LEVEL_PIDS: "topLevelPids", }; /** * Update the shared states of react components. */ -const editorReducer = (state: EditorState, { type, payload }: { type: string, payload: SnackbarState | unknown}) => { +const editorReducer = (state: EditorState, { type, payload }: { type: string, payload: unknown}) => { if (type === "ADD_TO_OBJECT_DETAILS_STORAGE") { const { key, details } = payload as { key: string; details: ObjectDetails }; const objectDetailsStorage = { @@ -229,9 +216,6 @@ export const useEditorContext = () => { currentAgents, currentPid, activeDatastream, - isDatastreamModalOpen, - isParentsModalOpen, - isStateModalOpen, datastreamModalState, parentsModalActivePid, stateModalActivePid, @@ -243,7 +227,6 @@ export const useEditorContext = () => { vufindUrl, licensesCatalog, modelsCatalog, - snackbarState, objectDetailsStorage, parentDetailsStorage, childListStorage, @@ -432,27 +415,6 @@ export const useEditorContext = () => { }); }; - const toggleDatastreamModal = () => { - dispatch({ - type: "SET_IS_DATASTREAM_MODAL_OPEN", - payload: !isDatastreamModalOpen - }); - }; - - const toggleParentsModal = () => { - dispatch({ - type: "SET_IS_PARENTS_MODAL_OPEN", - payload: !isParentsModalOpen - }); - }; - - const toggleStateModal = () => { - dispatch({ - type: "SET_IS_STATE_MODAL_OPEN", - payload: !isStateModalOpen - }); - }; - const setDatastreamModalState = (datastreamModalState: boolean) => { dispatch({ type: "SET_DATASTREAM_MODAL_STATE", @@ -481,13 +443,6 @@ export const useEditorContext = () => { }) }; - const setSnackbarState = (snackbarState: SnackbarState) => { - dispatch({ - type: "SET_SNACKBAR_STATE", - payload: snackbarState - }); - }; - const datastreamsCatalog = Object.values(modelsCatalog).reduce((acc: Record, model) => { return { ...acc, @@ -527,9 +482,6 @@ export const useEditorContext = () => { currentPid, currentDatastreams, activeDatastream, - isDatastreamModalOpen, - isParentsModalOpen, - isStateModalOpen, datastreamModalState, parentsModalActivePid, stateModalActivePid, @@ -543,7 +495,6 @@ export const useEditorContext = () => { vufindUrl, modelsCatalog, licensesCatalog, - snackbarState, objectDetailsStorage, parentDetailsStorage, childListStorage, @@ -558,10 +509,6 @@ export const useEditorContext = () => { setDatastreamModalState, setParentsModalActivePid, setStateModalActivePid, - toggleDatastreamModal, - toggleParentsModal, - toggleStateModal, - setSnackbarState, extractFirstMetadataValue, getChildListStorageKey, loadObjectDetailsIntoStorage, diff --git a/client/context/GlobalContext.test.tsx b/client/context/GlobalContext.test.tsx new file mode 100644 index 000000000..f4711e8c9 --- /dev/null +++ b/client/context/GlobalContext.test.tsx @@ -0,0 +1,57 @@ +import { describe, expect, it } from "@jest/globals"; +import { renderHook, act } from "@testing-library/react"; +import { GlobalContextProvider, useGlobalContext } from "./GlobalContext"; + +describe("useGlobalContext", () => { + describe("setSnackbarState", () => { + it("sets the snackbar state with text and severity", async () => { + const { result } = await renderHook(() => useGlobalContext(), { wrapper: GlobalContextProvider }); + + expect(result.current.state.snackbarState).toEqual({ + open: false, + message: "", + severity: "info" + }); + + await act(async () => { + await result.current.action.setSnackbarState({ + open: true, + message: "oh no!", + severity: "error" + }); + }); + + expect(result.current.state.snackbarState).toEqual({ + open: true, + message: "oh no!", + severity: "error" + }); + }); + }); + + describe("toggleModal", () => { + it("toggles the modal", async () => { + const { result } = await renderHook(() => useGlobalContext(), { wrapper: GlobalContextProvider }); + + expect(result.current.action.isModalOpen("foo")).toEqual(false); + + await act(async () => { + await result.current.action.toggleModal("foo"); + }); + + expect(result.current.action.isModalOpen("foo")).toEqual(true); + + await act(async () => { + await result.current.action.toggleModal("foo"); + }); + + expect(result.current.action.isModalOpen("foo")).toEqual(false); + + await act(async () => { + await result.current.action.toggleModal("foo"); + }); + + expect(result.current.action.isModalOpen("foo")).toEqual(true); + }); + }); +}); diff --git a/client/context/GlobalContext.tsx b/client/context/GlobalContext.tsx new file mode 100644 index 000000000..3c47df455 --- /dev/null +++ b/client/context/GlobalContext.tsx @@ -0,0 +1,151 @@ +import React, { createContext, useContext, useReducer, Dispatch, ReactNode } from "react"; + +interface SnackbarState { + open: boolean, + message: string, + severity: string +} + +interface GlobalState { + // Modal control + modalOpenStates: Record; + // Snackbar + snackbarState: SnackbarState; +} + +/** + * Pass a shared entity to react components, + * specifically a way to make api requests. + */ +const initalGlobalState: GlobalState = { + // Modal control + modalOpenStates: {}, + // Snackbar + snackbarState: { + open: false, + message: "", + severity: "info", + }, +}; + +type Action = + | { type: 'OPEN_MODAL'; payload: string } + | { type: 'CLOSE_MODAL'; payload: string } + | { type: 'SET_SNACKBAR_STATE'; payload: SnackbarState }; + +/** + * Update the shared states of react components. + */ +const globalReducer = (state: GlobalState, action: Action): GlobalState => { + switch (action.type) { + case 'OPEN_MODAL': + return { + ...state, + modalOpenStates: { + ...state.modalOpenStates, + [action.payload]: true, + }, + }; + case 'CLOSE_MODAL': + return { + ...state, + modalOpenStates: { + ...state.modalOpenStates, + [action.payload]: false, + }, + }; + case 'SET_SNACKBAR_STATE': + return { + ...state, + snackbarState: action.payload, + }; + default: + return state; + } +}; + +/** + * Context to provide global state and dispatch function. + */ +interface GlobalContextProps { + state: GlobalState; + dispatch: Dispatch; +} + +const GlobalContext = createContext(undefined); + +/** + * GlobalContextProvider to wrap around the application. + */ +export const GlobalContextProvider = ({ children }: { children: ReactNode }) => { + const [state, dispatch] = useReducer(globalReducer, initalGlobalState); + return ( + + {children} + + ); +}; + +export const useGlobalContext = () => { + const context = useContext(GlobalContext); + + if (!context) { + throw new Error("useGlobalContext must be used within a GlobalContextProvider"); + } + + const { state, dispatch } = context; + + // Modal control + + const isModalOpen = (modal: string) => state.modalOpenStates[modal] ?? false; + const openModal = (modal: string) => { + dispatch({ + type: "OPEN_MODAL", + payload: modal + }); + }; + const closeModal = (modal: string) => { + dispatch({ + type: "CLOSE_MODAL", + payload: modal + }); + }; + const toggleModal = (modal: string) => { + if (isModalOpen(modal)) { + closeModal(modal); + } else { + openModal(modal); + } + }; + + // Snackbar + + const setSnackbarState = (snackbarState: SnackbarState) => { + dispatch({ + type: "SET_SNACKBAR_STATE", + payload: snackbarState + }); + }; + + return { + state: { + // only return limited state + // to create "private" attributes + snackbarState: state.snackbarState, + }, + action: { + // Modal control + isModalOpen, + openModal, + closeModal, + toggleModal, + // Snackbar + setSnackbarState, + }, + }; +} + +export default { + GlobalContextProvider, + useGlobalContext +} diff --git a/client/hooks/useDatastreamOperation.test.ts b/client/hooks/useDatastreamOperation.test.ts index 75f7459ec..2f7fb74fe 100644 --- a/client/hooks/useDatastreamOperation.test.ts +++ b/client/hooks/useDatastreamOperation.test.ts @@ -1,6 +1,12 @@ import { beforeEach, describe, expect, it, jest } from "@jest/globals"; import useDatastreamOperation from "./useDatastreamOperation"; +const mockUseGlobalContext = jest.fn(); +jest.mock("../context/GlobalContext", () => ({ + useGlobalContext: () => { + return mockUseGlobalContext(); + }, +})); const mockUseFetchContext = jest.fn(); jest.mock("../context/FetchContext", () => ({ useFetchContext: () => { @@ -15,6 +21,7 @@ jest.mock("../context/EditorContext", () => ({ })); describe("useDatastreamOperation", () => { + let globalValues; let fetchValues; let editorValues; let currentPid; @@ -33,6 +40,12 @@ describe("useDatastreamOperation", () => { }, }, }; + globalValues = { + action: { + setSnackbarState: jest.fn(), + closeModal: jest.fn() + }, + }; fetchValues = { action: { fetchBlob: jest.fn(), @@ -48,11 +61,10 @@ describe("useDatastreamOperation", () => { datastreamsCatalog }, action: { - setSnackbarState: jest.fn(), - toggleDatastreamModal: jest.fn(), loadCurrentObjectDetails: jest.fn() }, }; + mockUseGlobalContext.mockReturnValue(globalValues); mockUseFetchContext.mockReturnValue(fetchValues); mockUseEditorContext.mockReturnValue(editorValues); }); @@ -75,7 +87,7 @@ describe("useDatastreamOperation", () => { }) ); expect(editorValues.action.loadCurrentObjectDetails).toHaveBeenCalled(); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: true, message: "upload worked", severity: "success", @@ -87,12 +99,12 @@ describe("useDatastreamOperation", () => { await uploadFile({ type: "image/illegaltype", }); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: true, message: expect.stringContaining("Illegal mime type"), severity: "error", }); - expect(editorValues.action.toggleDatastreamModal).toHaveBeenCalled(); + expect(globalValues.action.closeModal).toHaveBeenCalled(); }); it("returns illegal mime type when catalog cannot find datastream", async () => { @@ -103,12 +115,12 @@ describe("useDatastreamOperation", () => { type: "image/png", }); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: true, message: expect.stringContaining("Illegal mime type"), severity: "error", }); - expect(editorValues.action.toggleDatastreamModal).toHaveBeenCalled(); + expect(globalValues.action.closeModal).toHaveBeenCalled(); }); }); @@ -139,7 +151,7 @@ describe("useDatastreamOperation", () => { }), { "Content-Type": "application/json"} ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: true, message: "upload agents worked", severity: "success", @@ -160,7 +172,7 @@ describe("useDatastreamOperation", () => { }), { "Content-Type": "application/json"} ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith( + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith( expect.objectContaining({ open: true, message: "Upload failure!", @@ -194,7 +206,7 @@ describe("useDatastreamOperation", () => { }), { "Content-Type": "application/json"} ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: true, message: "upload DC works", severity: "success", @@ -215,7 +227,7 @@ describe("useDatastreamOperation", () => { }), { "Content-Type": "application/json"} ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith( + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith( expect.objectContaining({ open: true, message: "Upload failure!", @@ -245,7 +257,7 @@ describe("useDatastreamOperation", () => { }), { "Content-Type": "application/json"} ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: true, message: "upload license works", severity: "success", @@ -266,7 +278,7 @@ describe("useDatastreamOperation", () => { }), { "Content-Type": "application/json"} ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith( + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith( expect.objectContaining({ open: true, message: "Upload failure!", @@ -297,7 +309,7 @@ describe("useDatastreamOperation", () => { }), { "Content-Type": "application/json"} ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: true, message: "upload works", severity: "success", @@ -319,7 +331,7 @@ describe("useDatastreamOperation", () => { }), { "Content-Type": "application/json"} ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith( + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith( expect.objectContaining({ open: true, message: "Upload failure!", @@ -343,7 +355,7 @@ describe("useDatastreamOperation", () => { }) ); expect(editorValues.action.loadCurrentObjectDetails).toHaveBeenCalled(); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith( + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith( expect.objectContaining({ open: true, message: "Delete success!", @@ -365,7 +377,7 @@ describe("useDatastreamOperation", () => { }) ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith( + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith( expect.objectContaining({ open: true, message: "Delete failure!", @@ -432,7 +444,7 @@ describe("useDatastreamOperation", () => { "http://localhost:9000/api/edit/object/vudl%3A123/datastream/test1/download" ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith( + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith( expect.objectContaining({ open: true, message: "Incorrect file format", @@ -453,7 +465,7 @@ describe("useDatastreamOperation", () => { "http://localhost:9000/api/edit/object/vudl%3A123/datastream/test1/download" ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith( + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith( expect.objectContaining({ open: true, message: "Incorrect file format", @@ -472,7 +484,7 @@ describe("useDatastreamOperation", () => { "http://localhost:9000/api/edit/object/vudl%3A123/datastream/test1/download" ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith( + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith( expect.objectContaining({ open: true, message: "Download failure!", @@ -535,7 +547,7 @@ describe("useDatastreamOperation", () => { await viewDatastream(); expect(fetchValues.action.fetchBlob).toHaveBeenCalled(); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith( + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith( expect.objectContaining({ open: true, severity: "error", @@ -573,7 +585,7 @@ describe("useDatastreamOperation", () => { expect(fetchValues.action.fetchText).toHaveBeenCalledWith( "http://localhost:9000/api/edit/object/vudl%3A123/datastream/LICENSE/license" ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: true, message: "fetch license failed", severity: "error" @@ -616,7 +628,7 @@ describe("useDatastreamOperation", () => { expect(fetchValues.action.fetchJSON).toHaveBeenCalledWith( "http://localhost:9000/api/edit/object/vudl%3A123/datastream/AGENTS/agents" ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: true, message: "fetch agents failed", severity: "error" @@ -654,7 +666,7 @@ describe("useDatastreamOperation", () => { expect(fetchValues.action.fetchJSON).toHaveBeenCalledWith( "http://localhost:9000/api/edit/object/vudl%3A123/datastream/PROCESS-MD/processMetadata" ); - expect(editorValues.action.setSnackbarState).toHaveBeenCalledWith({ + expect(globalValues.action.setSnackbarState).toHaveBeenCalledWith({ open: true, message: "fetch process metadata failed", severity: "error" diff --git a/client/hooks/useDatastreamOperation.ts b/client/hooks/useDatastreamOperation.ts index befc966a0..c8db809cc 100644 --- a/client/hooks/useDatastreamOperation.ts +++ b/client/hooks/useDatastreamOperation.ts @@ -1,5 +1,6 @@ import { useFetchContext } from "../context/FetchContext"; import { useEditorContext } from "../context/EditorContext"; +import { useGlobalContext } from "../context/GlobalContext"; import { deleteObjectDatastreamUrl, downloadObjectDatastreamUrl, @@ -14,12 +15,15 @@ import { } from "../util/routes"; const useDatastreamOperation = () => { + const { + action: { setSnackbarState, closeModal }, + } = useGlobalContext(); const { action: { fetchBlob, fetchJSON, fetchText }, } = useFetchContext(); const { state: { currentPid, activeDatastream, datastreamsCatalog, currentDatastreams, processMetadataDefaults }, - action: { setSnackbarState, toggleDatastreamModal, loadCurrentObjectDetails }, + action: { loadCurrentObjectDetails }, } = useEditorContext(); const isAllowedMimeType = (mimeType) => { @@ -51,15 +55,14 @@ const useDatastreamOperation = () => { message: text, severity: "success", }); - toggleDatastreamModal(); } catch (err) { setSnackbarState({ open: true, message: err.message, severity: "error", }); - toggleDatastreamModal(); } + closeModal("datastream"); }; const uploadAgents = async (agents) => { @@ -106,7 +109,7 @@ const useDatastreamOperation = () => { severity: "error", }); } - toggleDatastreamModal(); + closeModal("datastream"); }; const uploadLicense = async (licenseKey) => { @@ -130,7 +133,7 @@ const useDatastreamOperation = () => { severity: "error", }); } - toggleDatastreamModal(); + closeModal("datastream"); }; const uploadProcessMetadata = async (processMetadata) => { @@ -154,7 +157,7 @@ const useDatastreamOperation = () => { severity: "error", }); } - toggleDatastreamModal(); + closeModal("datastream"); }; const deleteDatastream = async () => { @@ -175,7 +178,7 @@ const useDatastreamOperation = () => { severity: "error", }); } - toggleDatastreamModal(); + closeModal("datastream"); }; const downloadDatastream = async (datastream) => { diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index 444d31784..b15a3e316 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -1,5 +1,6 @@ /* eslint react/prop-types: 0 */ import React from "react"; +import { GlobalContextProvider } from "../context/GlobalContext"; import { PaginatorContextProvider } from "../context/PaginatorContext"; import { FetchContextProvider } from "../context/FetchContext"; @@ -12,14 +13,16 @@ import LogoutButton from "../components/LogoutButton"; function MyApp({ Component, pageProps }: { Component: React.ReactNode }): React.ReactElement { return ( - -
- -
- - - -
+ + +
+ +
+ + + +
+
); } export default MyApp;