Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/actions/sponsor-forms-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,9 @@ export const cloneGlobalTemplate =
})
);
})
.catch(() => {}); // need to catch promise reject
.finally(() => {
dispatch(stopLoading());
});
};

export const saveFormTemplate = (entity) => async (dispatch, getState) => {
Expand Down Expand Up @@ -403,7 +405,6 @@ export const saveFormTemplate = (entity) => async (dispatch, getState) => {
})
);
})
.catch(() => {}) // need to catch promise reject
.finally(() => {
dispatch(stopLoading());
});
Expand Down Expand Up @@ -443,7 +444,6 @@ export const updateFormTemplate = (entity) => async (dispatch, getState) => {
})
);
})
.catch(() => {}) // need to catch promise reject
.finally(() => {
dispatch(stopLoading());
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React from "react";
import { act, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithRedux } from "utils/test-utils";
import { saveFormTemplate } from "actions/sponsor-forms-actions";
import FormTemplatePopup from "../form-template-popup";

jest.mock("actions/sponsor-forms-actions", () => ({
saveFormTemplate: jest.fn(),
updateFormTemplate: jest.fn(),
getSponsorships: jest.fn(() => ({ type: "GET_SPONSORSHIPS" })),
resetFormTemplate: jest.fn(() => ({ type: "RESET_TEMPLATE_FORM" }))
}));

jest.mock("../form-template-form", () => ({
__esModule: true,
default: ({ onSubmit, isSaving }) => (
<button
type="button"
disabled={isSaving}
onClick={() => onSubmit({ id: null, code: "111" })}
>
submit-form-template
</button>
)
}));

describe("FormTemplatePopup", () => {
const initialState = {
sponsorFormsListState: {
sponsorships: { items: [] },
formTemplate: {}
},
currentSummitState: {
currentSummit: {
time_zone_id: "UTC"
}
}
};

beforeEach(() => {
jest.clearAllMocks();
});

it("keeps modal open when save fails", async () => {
const onClose = jest.fn();
saveFormTemplate.mockReturnValue(() => Promise.reject(new Error("dup")));

renderWithRedux(<FormTemplatePopup open onClose={onClose} edit={false} />, {
initialState
});

await act(async () => {
await userEvent.click(
screen.getByRole("button", { name: "submit-form-template" })
);
await Promise.resolve();
});

expect(saveFormTemplate).toHaveBeenCalledTimes(1);
expect(onClose).not.toHaveBeenCalled();
});

it("prevents duplicate save requests while saving", async () => {
const onClose = jest.fn();
let resolveSave;
const pendingPromise = new Promise((resolve) => {
resolveSave = resolve;
});

saveFormTemplate.mockReturnValue(() => pendingPromise);

renderWithRedux(<FormTemplatePopup open onClose={onClose} edit={false} />, {
initialState
});

const button = screen.getByRole("button", { name: "submit-form-template" });

await act(async () => {
await userEvent.click(button);
await userEvent.click(button);
});

expect(saveFormTemplate).toHaveBeenCalledTimes(1);

await act(async () => {
resolveSave();
await Promise.resolve();
});
});

it("closes modal after successful save", async () => {
const onClose = jest.fn();
saveFormTemplate.mockReturnValue(() => Promise.resolve());

renderWithRedux(<FormTemplatePopup open onClose={onClose} edit={false} />, {
initialState
});

await act(async () => {
await userEvent.click(
screen.getByRole("button", { name: "submit-form-template" })
);
await Promise.resolve();
});

expect(saveFormTemplate).toHaveBeenCalledTimes(1);
expect(onClose).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const FormTemplateForm = ({
initialValues,
sponsorships,
summitTZ,
isSaving = false,
onSubmit
}) => {
const formik = useFormik({
Expand Down Expand Up @@ -150,7 +151,12 @@ const FormTemplateForm = ({
</DialogContent>
<Divider />
<DialogActions>
<Button type="submit" fullWidth variant="contained">
<Button
type="submit"
disabled={isSaving}
fullWidth
variant="contained"
>
{T.translate("sponsor_forms.form_template_popup.save")}
</Button>
</DialogActions>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import T from "i18n-react/dist/i18n-react";
import { connect } from "react-redux";
Expand Down Expand Up @@ -31,33 +31,52 @@ const FormTemplatePopup = ({
updateFormTemplate,
edit
}) => {
const [isSaving, setIsSaving] = useState(false);

useEffect(() => {
getSponsorships(1, MAX_PER_PAGE);
}, []);

const handleClose = () => {
// clear form from reducer
const closePopup = () => {
resetFormTemplate();
onClose();
};

const handleClose = () => {
if (isSaving) return;
closePopup();
};

const handleOnSave = (values) => {
if (isSaving) return;

const save = values.id ? updateFormTemplate : saveFormTemplate;
setIsSaving(true);

save(values).finally(() => {
handleClose();
});
save(values)
.then(() => {
closePopup();
})
.catch(() => {
// keep dialog open on save error to preserve user input
})
.finally(() => {
setIsSaving(false);
});
};

return (
<Dialog
open={open}
onClose={handleClose}
onClose={() => {
handleClose();
}}
maxWidth="md"
fullWidth
disableEnforceFocus
disableAutoFocus
disableRestoreFocus
disableEscapeKeyDown={isSaving}
>
<DialogTitle
sx={{ display: "flex", justifyContent: "space-between" }}
Expand All @@ -70,7 +89,12 @@ const FormTemplatePopup = ({
: "sponsor_forms.form_template_popup.title.new"
)}
</Typography>
<IconButton size="large" sx={{ p: 0 }} onClick={handleClose}>
<IconButton
size="large"
sx={{ p: 0 }}
onClick={handleClose}
disabled={isSaving}
>
<CloseIcon fontSize="large" />
</IconButton>
</DialogTitle>
Expand All @@ -79,6 +103,7 @@ const FormTemplatePopup = ({
initialValues={formTemplate}
sponsorships={sponsorships}
summitTZ={summitTZ}
isSaving={isSaving}
onSubmit={handleOnSave}
/>
</Dialog>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React from "react";
import { act, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithRedux } from "utils/test-utils";
import { cloneGlobalTemplate } from "actions/sponsor-forms-actions";
import GlobalTemplatePopup from "../global-template-popup";

jest.mock("@mui/material", () => {
const originalModule = jest.requireActual("@mui/material");
return {
...originalModule,
Dialog: ({ open, onClose, children }) =>
open ? (
<div>
<button type="button" onClick={() => onClose({}, "backdropClick")}>
dialog-onclose
</button>
{children}
</div>
) : null
};
});

jest.mock("actions/sponsor-forms-actions", () => ({
cloneGlobalTemplate: jest.fn()
}));

jest.mock("../select-templates-dialog", () => ({
__esModule: true,
default: ({ onSave }) => (
<div>
<button type="button" onClick={() => onSave([1])}>
go-sponsorships
</button>
</div>
)
}));

jest.mock("../select-sponsorships-dialog", () => ({
__esModule: true,
default: ({ onSave, isSaving }) => (
<div>
<span>sponsorships-step</span>
<button
type="button"
onClick={() => onSave([10], false)}
disabled={isSaving}
>
apply-sponsorships
</button>
</div>
)
}));

describe("GlobalTemplatePopup", () => {
beforeEach(() => {
jest.clearAllMocks();
});

it("keeps popup open on clone error", async () => {
const onClose = jest.fn();
cloneGlobalTemplate.mockReturnValue(() => Promise.reject(new Error("dup")));

renderWithRedux(<GlobalTemplatePopup open onClose={onClose} />, {
initialState: {}
});

await act(async () => {
await userEvent.click(
screen.getByRole("button", { name: "go-sponsorships" })
);
await userEvent.click(
screen.getByRole("button", { name: "apply-sponsorships" })
);
await Promise.resolve();
});

expect(cloneGlobalTemplate).toHaveBeenCalledTimes(1);
expect(onClose).not.toHaveBeenCalled();
expect(screen.getByText("sponsorships-step")).toBeTruthy();
});

it("resets stage to templates on dialog onClose", async () => {
const onClose = jest.fn();

renderWithRedux(<GlobalTemplatePopup open onClose={onClose} />, {
initialState: {}
});

await act(async () => {
await userEvent.click(
screen.getByRole("button", { name: "go-sponsorships" })
);
});
expect(screen.getByText("sponsorships-step")).toBeTruthy();

await act(async () => {
await userEvent.click(
screen.getByRole("button", { name: "dialog-onclose" })
);
await Promise.resolve();
});

expect(onClose).toHaveBeenCalledTimes(1);
expect(
screen.queryByRole("button", { name: "go-sponsorships" })
).not.toBeInTheDocument();
});

it("prevents duplicate clone requests while saving", async () => {
const onClose = jest.fn();
let resolveClone;
const pendingPromise = new Promise((resolve) => {
resolveClone = resolve;
});
cloneGlobalTemplate.mockReturnValue(() => pendingPromise);

renderWithRedux(<GlobalTemplatePopup open onClose={onClose} />, {
initialState: {}
});

await act(async () => {
await userEvent.click(
screen.getByRole("button", { name: "go-sponsorships" })
);
});

const applyButton = screen.getByRole("button", {
name: "apply-sponsorships"
});
await act(async () => {
await userEvent.click(applyButton);
await userEvent.click(applyButton);
});

expect(cloneGlobalTemplate).toHaveBeenCalledTimes(1);

await act(async () => {
resolveClone();
await Promise.resolve();
});
});
});
Loading
Loading