feat: redesign event modal with responsive dialog and improved layout
Implemented a new ResponsiveDialog component and redesigned EventModal with better UX for both normal and extended modes. New Features: - Created reusable ResponsiveDialog component (src/components/Dialog/) * Normal mode: 685px centered popup * Extended mode: fullscreen with 90px header preservation * Auto spacing via MUI Stack (16px normal, 24px extended) * Back arrow navigation in extended mode * No backdrop/shadow in extended mode for seamless integration * Configurable props for all dimensions and behaviors EventModal Improvements: - Replaced native checkbox with MUI Checkbox component - Migrated from Popover to ResponsiveDialog - Reorganized field layout => Comprehensive documentation in Dialog/README.md Note: RepeatEvent integration tests need additional refactoring (tracked separately)
This commit is contained in:
@@ -99,7 +99,7 @@ async function setupEventPopover(
|
||||
preloadedState
|
||||
);
|
||||
act(() => {
|
||||
fireEvent.change(screen.getByLabelText("Title"), {
|
||||
fireEvent.change(screen.getByRole("textbox", { name: /title/i }), {
|
||||
target: { value: "Meeting" },
|
||||
});
|
||||
fireEvent.click(screen.getByLabelText("All day"));
|
||||
@@ -113,7 +113,10 @@ async function setupEventPopover(
|
||||
value: (overrides?.end ?? "2025-07-19T00:00:00.000Z").split("T")[0],
|
||||
},
|
||||
});
|
||||
fireEvent.click(screen.getByText("Show More"));
|
||||
const showMoreButton = screen.queryByText("Show More");
|
||||
if (showMoreButton) {
|
||||
fireEvent.click(showMoreButton);
|
||||
}
|
||||
});
|
||||
const select = screen.getByLabelText(/repetition/i);
|
||||
userEvent.click(select);
|
||||
@@ -124,7 +127,8 @@ async function setupEventPopover(
|
||||
async function expectRRule(expected: any) {
|
||||
const spyAPi = jest.spyOn(apiUtils, "api");
|
||||
|
||||
act(() => fireEvent.click(screen.getByText("Save")));
|
||||
const saveButton = screen.getByRole("button", { name: /save/i });
|
||||
act(() => fireEvent.click(saveButton));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(spyAPi).toHaveBeenCalled();
|
||||
@@ -214,7 +218,8 @@ describe("Repeat Event API calls", () => {
|
||||
const spy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => () => Promise.resolve(payload) as any);
|
||||
act(() => fireEvent.click(screen.getByText("Save")));
|
||||
const saveButton = screen.getByRole("button", { name: /save/i });
|
||||
act(() => fireEvent.click(saveButton));
|
||||
await waitFor(() => expect(spy).toHaveBeenCalled());
|
||||
|
||||
const received = spy.mock.calls[0][0];
|
||||
@@ -223,12 +228,14 @@ describe("Repeat Event API calls", () => {
|
||||
);
|
||||
expect(received.newEvent.title).toBe("Meeting");
|
||||
expect(
|
||||
formatDateToYYYYMMDDTHHMMSS(received.newEvent.start).split("T")[0]
|
||||
).toBe("20250718");
|
||||
expect(
|
||||
formatDateToYYYYMMDDTHHMMSS(received.newEvent.end || new Date()).split(
|
||||
formatDateToYYYYMMDDTHHMMSS(new Date(received.newEvent.start)).split(
|
||||
"T"
|
||||
)[0]
|
||||
).toBe("20250718");
|
||||
expect(
|
||||
formatDateToYYYYMMDDTHHMMSS(
|
||||
new Date(received.newEvent.end || new Date())
|
||||
).split("T")[0]
|
||||
).toBe("20250719");
|
||||
expect(received.newEvent.organizer).toEqual(
|
||||
preloadedState.user.organiserData
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { ResponsiveDialog } from "../../src/components/Dialog";
|
||||
import { Button, TextField } from "@mui/material";
|
||||
|
||||
describe("ResponsiveDialog", () => {
|
||||
const mockOnClose = jest.fn();
|
||||
const mockOnExpandToggle = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnClose.mockClear();
|
||||
mockOnExpandToggle.mockClear();
|
||||
});
|
||||
|
||||
it("renders in normal mode by default", () => {
|
||||
render(
|
||||
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test Dialog">
|
||||
<TextField label="Name" />
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Test Dialog")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/name/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders title in normal mode", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="My Title"
|
||||
isExpanded={false}
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("My Title")).toBeInTheDocument();
|
||||
expect(screen.queryByLabelText("show less")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders back arrow in extended mode", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="My Title"
|
||||
isExpanded={true}
|
||||
onExpandToggle={mockOnExpandToggle}
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.queryByText("My Title")).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText("show less")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls onExpandToggle when back arrow is clicked", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
isExpanded={true}
|
||||
onExpandToggle={mockOnExpandToggle}
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
const backButton = screen.getByLabelText("show less");
|
||||
fireEvent.click(backButton);
|
||||
|
||||
expect(mockOnExpandToggle).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renders actions when provided", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
actions={<Button>Custom Action</Button>}
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Custom Action")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render actions when not provided", () => {
|
||||
const { container } = render(
|
||||
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test">
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
const dialogActions = container.querySelector(".MuiDialogActions-root");
|
||||
expect(dialogActions).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls onClose when backdrop is clicked", () => {
|
||||
render(
|
||||
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test">
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
const backdrop = document.querySelector(".MuiBackdrop-root");
|
||||
if (backdrop) {
|
||||
fireEvent.click(backdrop);
|
||||
}
|
||||
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("applies custom normalMaxWidth", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
normalMaxWidth="800px"
|
||||
>
|
||||
<div>Normal Width Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Normal Width Content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("wraps children in Stack component", () => {
|
||||
render(
|
||||
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test">
|
||||
<TextField label="Field 1" />
|
||||
<TextField label="Field 2" />
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText("Field 1")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("Field 2")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("uses correct spacing in normal mode", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
isExpanded={false}
|
||||
normalSpacing={2}
|
||||
>
|
||||
<div>Normal Spacing Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Normal Spacing Content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("uses correct spacing in extended mode", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
isExpanded={true}
|
||||
expandedSpacing={3}
|
||||
>
|
||||
<div>Extended Spacing Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Extended Spacing Content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies contentSx custom styles", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
contentSx={{ padding: 4 }}
|
||||
>
|
||||
<div>Custom Styled Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Custom Styled Content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies titleSx custom styles", () => {
|
||||
const { container } = render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
titleSx={{ color: "red" }}
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
const title = screen.getByText("Test");
|
||||
expect(title).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows dividers when dividers prop is true", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
dividers={true}
|
||||
>
|
||||
<div>Content with Dividers</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Content with Dividers")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show back arrow when onExpandToggle is not provided", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test Title"
|
||||
isExpanded={true}
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText("show less")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("accepts custom headerHeight", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
isExpanded={true}
|
||||
headerHeight="100px"
|
||||
>
|
||||
<div>Custom Header Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Custom Header Content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders with custom expandedContentMaxWidth", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
isExpanded={true}
|
||||
expandedContentMaxWidth="1200px"
|
||||
>
|
||||
<div>Wide Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Wide Content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render dialog content when open is false", () => {
|
||||
render(
|
||||
<ResponsiveDialog open={false} onClose={mockOnClose} title="Test">
|
||||
<div>Test Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.queryByText("Test Content")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders correctly in extended mode", () => {
|
||||
render(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
isExpanded={true}
|
||||
onExpandToggle={mockOnExpandToggle}
|
||||
>
|
||||
<div>Extended Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Extended Content")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("show less")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user