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
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { screen } from "@testing-library/react";
import { createMockStandaloneEvent } from "@core/util/test/ccal.event.factory";
import {
createMockBaseEvent,
createMockInstance,
createMockStandaloneEvent,
} from "@core/util/test/ccal.event.factory";
import { render } from "@web/__tests__/__mocks__/mock.render";
import { createInitialState } from "@web/__tests__/utils/state/store.test.util";
import { type Schema_GridEvent } from "@web/common/types/web.event.types";
Expand Down Expand Up @@ -234,4 +238,149 @@ describe("GridEvent", () => {
// but here we're testing that the selector only checks event1's pending state
expect(renderSpy).toHaveBeenCalledTimes(2); // Rerender was called, but memo should prevent actual re-render
});

describe("repeat icon for recurring events", () => {
it("should render repeat icon for base recurring event with rule", () => {
const baseEvent = createMockBaseEvent({
_id: "base-event-1",
title: "Weekly Meeting",
recurrence: { rule: ["RRULE:FREQ=WEEKLY"] },
});

const event = createMockGridEvent(baseEvent);

const initialState = createInitialState({
events: {
pendingEvents: {
eventIds: [],
},
},
});

render(
<GridEventMemo
event={event}
isDraft={false}
isDragging={false}
isPlaceholder={false}
isResizing={false}
measurements={mockMeasurements}
onEventMouseDown={mockOnEventMouseDown}
onScalerMouseDown={mockOnScalerMouseDown}
weekProps={mockWeekProps}
/>,
{ state: initialState },
);

const icon = screen.getByLabelText("Recurring event");
expect(icon).toBeInTheDocument();
expect(screen.getByText("Weekly Meeting")).toBeInTheDocument();
});

it("should render repeat icon for recurring instance with eventId", () => {
const baseEventId = "base-123";
const gBaseId = "gbase-123";
const instance = createMockInstance(baseEventId, gBaseId, {
_id: "instance-1",
title: "Weekly Meeting Instance",
});

const event = createMockGridEvent(instance);

const initialState = createInitialState({
events: {
pendingEvents: {
eventIds: [],
},
},
});

render(
<GridEventMemo
event={event}
isDraft={false}
isDragging={false}
isPlaceholder={false}
isResizing={false}
measurements={mockMeasurements}
onEventMouseDown={mockOnEventMouseDown}
onScalerMouseDown={mockOnScalerMouseDown}
weekProps={mockWeekProps}
/>,
{ state: initialState },
);

const icon = screen.getByLabelText("Recurring event");
expect(icon).toBeInTheDocument();
});

it("should not render repeat icon for non-recurring event", () => {
const event = createMockGridEvent({
_id: "standalone-1",
title: "One-time Meeting",
recurrence: undefined,
});

const initialState = createInitialState({
events: {
pendingEvents: {
eventIds: [],
},
},
});

render(
<GridEventMemo
event={event}
isDraft={false}
isDragging={false}
isPlaceholder={false}
isResizing={false}
measurements={mockMeasurements}
onEventMouseDown={mockOnEventMouseDown}
onScalerMouseDown={mockOnScalerMouseDown}
weekProps={mockWeekProps}
/>,
{ state: initialState },
);

const icon = screen.queryByLabelText("Recurring event");
expect(icon).not.toBeInTheDocument();
expect(screen.getByText("One-time Meeting")).toBeInTheDocument();
});

it("should not render repeat icon when recurrence is disabled (rule is null)", () => {
const event = createMockGridEvent({
_id: "disabled-recurring-1",
title: "Disabled Recurring Meeting",
recurrence: { rule: null },
});

const initialState = createInitialState({
events: {
pendingEvents: {
eventIds: [],
},
},
});

render(
<GridEventMemo
event={event}
isDraft={false}
isDragging={false}
isPlaceholder={false}
isResizing={false}
measurements={mockMeasurements}
onEventMouseDown={mockOnEventMouseDown}
onScalerMouseDown={mockOnScalerMouseDown}
weekProps={mockWeekProps}
/>,
{ state: initialState },
);

const icon = screen.queryByLabelText("Recurring event");
expect(icon).not.toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
FlexDirections,
FlexWrap,
} from "@web/components/Flex/styled";
import { RepeatIcon } from "@web/components/Icons/Repeat";
import { Text } from "@web/components/Text";
import { selectIsEventPending } from "@web/ducks/events/selectors/pending.selectors";
import { useAppSelector } from "@web/store/store.hooks";
Expand Down Expand Up @@ -74,7 +75,10 @@ const _GridEvent = (
const isPending = useAppSelector((state) =>
event._id ? selectIsEventPending(state, event._id) : false,
);
const isRecurring = event.recurrence && event.recurrence?.eventId !== null;
const rule = event.recurrence?.rule;
const recurrenceEventId = event.recurrence?.eventId;
const isRecurring =
Array.isArray(rule) || typeof recurrenceEventId === "string";

const position = getEventPosition(
event,
Expand Down Expand Up @@ -140,6 +144,13 @@ const _GridEvent = (
flexWrap={FlexWrap.WRAP}
>
<StyledEventTitle eventHeight={position.height} role="textbox">
{isRecurring && (
<RepeatIcon
aria-label="Recurring event"
size={12}
style={{ marginRight: "4px", verticalAlign: "middle" }}
/>
)}
{event.title}
</StyledEventTitle>
{!event.isAllDay && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { screen } from "@testing-library/react";
import {
createMockBaseEvent,
createMockInstance,
createMockStandaloneEvent,
} from "@core/util/test/ccal.event.factory";
import { render } from "@web/__tests__/__mocks__/mock.render";
import { createInitialState } from "@web/__tests__/utils/state/store.test.util";
import { type Schema_GridEvent } from "@web/common/types/web.event.types";
import { gridEventDefaultPosition } from "@web/common/utils/event/event.util";
import { type Measurements_Grid } from "@web/views/Calendar/hooks/grid/useGridLayout";
import { AllDayEventMemo } from "./AllDayEvent";

const createMockGridEvent = (
overrides: Partial<Schema_GridEvent> = {},
): Schema_GridEvent => {
const standaloneEvent = createMockStandaloneEvent();
return {
...standaloneEvent,
position: gridEventDefaultPosition,
isAllDay: true,
...overrides,
} as Schema_GridEvent;
};

const mockMeasurements: Measurements_Grid = {
mainGrid: {
top: 0,
left: 0,
height: 1000,
width: 1000,
x: 0,
y: 0,
bottom: 1000,
right: 1000,
toJSON: () => ({}),
},
hourHeight: 60,
colWidths: Array(7).fill(100),
allDayRow: null,
remeasure: jest.fn(),
};

const mockOnMouseDown = jest.fn();
const mockOnScalerMouseDown = jest.fn();

describe("AllDayEvent - Repeat Icon", () => {
beforeEach(() => {
jest.clearAllMocks();
});

describe("recurring event rendering", () => {
it("should render repeat icon for base recurring event with rule", () => {
const baseEvent = createMockBaseEvent({
_id: "base-123",
title: "Weekly Team Standup",
});

const event = createMockGridEvent(baseEvent);

const initialState = createInitialState({
events: {
pendingEvents: {
eventIds: [],
},
},
});

render(
<AllDayEventMemo
endOfView="2024-01-21T23:59:59Z"
event={event}
isPlaceholder={false}
measurements={mockMeasurements}
onMouseDown={mockOnMouseDown}
onScalerMouseDown={mockOnScalerMouseDown}
startOfView="2024-01-15T00:00:00Z"
/>,
{ state: initialState },
);

const icon = screen.getByLabelText("Recurring event");
expect(icon).toBeInTheDocument();
expect(screen.getByText("Weekly Team Standup")).toBeInTheDocument();
});

it("should render repeat icon for recurring instance with eventId", () => {
const baseEventId = "base-456";
const gBaseId = "gbase-456";
const instance = createMockInstance(baseEventId, gBaseId, {
_id: "instance-1",
title: "Weekly Team Standup Instance",
isAllDay: true,
});

const event = createMockGridEvent(instance);

const initialState = createInitialState({
events: {
pendingEvents: {
eventIds: [],
},
},
});

render(
<AllDayEventMemo
endOfView="2024-01-21T23:59:59Z"
event={event}
isPlaceholder={false}
measurements={mockMeasurements}
onMouseDown={mockOnMouseDown}
onScalerMouseDown={mockOnScalerMouseDown}
startOfView="2024-01-15T00:00:00Z"
/>,
{ state: initialState },
);

const icon = screen.getByLabelText("Recurring event");
expect(icon).toBeInTheDocument();
});

it("should not render repeat icon for non-recurring all-day event", () => {
const event = createMockGridEvent({
_id: "standalone-1",
title: "Company Holiday",
recurrence: undefined,
});

const initialState = createInitialState({
events: {
pendingEvents: {
eventIds: [],
},
},
});

render(
<AllDayEventMemo
endOfView="2024-01-21T23:59:59Z"
event={event}
isPlaceholder={false}
measurements={mockMeasurements}
onMouseDown={mockOnMouseDown}
onScalerMouseDown={mockOnScalerMouseDown}
startOfView="2024-01-15T00:00:00Z"
/>,
{ state: initialState },
);

expect(
screen.queryByLabelText("Recurring event"),
).not.toBeInTheDocument();
expect(screen.getByText("Company Holiday")).toBeInTheDocument();
});

it("should not render repeat icon when recurrence is explicitly disabled", () => {
const event = createMockGridEvent({
_id: "standalone-2",
title: "One-time Event",
recurrence: {
rule: null,
eventId: undefined,
},
});

const initialState = createInitialState({
events: {
pendingEvents: {
eventIds: [],
},
},
});

render(
<AllDayEventMemo
endOfView="2024-01-21T23:59:59Z"
event={event}
isPlaceholder={false}
measurements={mockMeasurements}
onMouseDown={mockOnMouseDown}
onScalerMouseDown={mockOnScalerMouseDown}
startOfView="2024-01-15T00:00:00Z"
/>,
{ state: initialState },
);

expect(
screen.queryByLabelText("Recurring event"),
).not.toBeInTheDocument();
});
});
});
Loading