* #708 apply strictier linting rules and fix simple eslint bugs * #708 fix eslint errors relate to promise * #708 fix eslint import/no-extraneous-dependencies * #708 fix eslint errors of react-hook * #708 enable eslint check for typescript --------- Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
This commit is contained in:
@@ -1,350 +1,340 @@
|
||||
import * as appHooks from "@/app/hooks";
|
||||
import { AppDispatch } from "@/app/store";
|
||||
import CalendarApp from "@/components/Calendar/Calendar";
|
||||
import * as appHooks from '@/app/hooks'
|
||||
import { AppDispatch } from '@/app/store'
|
||||
import CalendarApp from '@/components/Calendar/Calendar'
|
||||
import {
|
||||
createEventHandlers,
|
||||
EventHandlersProps,
|
||||
} from "@/components/Calendar/handlers/eventHandlers";
|
||||
import * as eventThunks from "@/features/Calendars/services";
|
||||
import EventUpdateModal from "@/features/Events/EventUpdateModal";
|
||||
import { CalendarApi } from "@fullcalendar/core";
|
||||
import { jest } from "@jest/globals";
|
||||
import "@testing-library/jest-dom";
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
screen,
|
||||
waitFor,
|
||||
within,
|
||||
} from "@testing-library/react";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
EventHandlersProps
|
||||
} from '@/components/Calendar/handlers/eventHandlers'
|
||||
import * as eventThunks from '@/features/Calendars/services'
|
||||
import EventUpdateModal from '@/features/Events/EventUpdateModal'
|
||||
import { CalendarApi } from '@fullcalendar/core'
|
||||
import { jest } from '@jest/globals'
|
||||
import '@testing-library/jest-dom'
|
||||
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
describe("CalendarApp integration", () => {
|
||||
const today = new Date();
|
||||
const start = new Date(today);
|
||||
start.setHours(10, 0, 0, 0);
|
||||
const end = new Date(today);
|
||||
end.setHours(11, 0, 0, 0);
|
||||
describe('CalendarApp integration', () => {
|
||||
const today = new Date()
|
||||
const start = new Date(today)
|
||||
start.setHours(10, 0, 0, 0)
|
||||
const end = new Date(today)
|
||||
end.setHours(11, 0, 0, 0)
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
const renderCalendar = () => {
|
||||
const preloadedState = {
|
||||
user: {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "mockSid",
|
||||
openpaasId: "667037022b752d0026472254",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: '667037022b752d0026472254'
|
||||
},
|
||||
tokens: { accessToken: "token" }, // required to avoid redirect
|
||||
tokens: { accessToken: 'token' } // required to avoid redirect
|
||||
},
|
||||
calendars: {
|
||||
list: {
|
||||
"667037022b752d0026472254/cal1": {
|
||||
name: "Calendar 1",
|
||||
id: "667037022b752d0026472254/cal1",
|
||||
color: { light: "#FFFFFF", dark: "#000000" },
|
||||
owner: { emails: ["alice@example.com"] },
|
||||
'667037022b752d0026472254/cal1': {
|
||||
name: 'Calendar 1',
|
||||
id: '667037022b752d0026472254/cal1',
|
||||
color: { light: '#FFFFFF', dark: '#000000' },
|
||||
owner: { emails: ['alice@example.com'] },
|
||||
events: {
|
||||
event1: {
|
||||
id: "event1",
|
||||
calId: "667037022b752d0026472254/cal1",
|
||||
uid: "event1",
|
||||
title: "Test Event",
|
||||
id: 'event1',
|
||||
calId: '667037022b752d0026472254/cal1',
|
||||
uid: 'event1',
|
||||
title: 'Test Event',
|
||||
start: start.toISOString(),
|
||||
end: end.toISOString(),
|
||||
partstat: "ACCEPTED",
|
||||
partstat: 'ACCEPTED',
|
||||
organizer: {
|
||||
cn: "Alice",
|
||||
cal_address: "alice@example.com",
|
||||
cn: 'Alice',
|
||||
cal_address: 'alice@example.com'
|
||||
},
|
||||
attendee: [
|
||||
{
|
||||
cn: "Alice",
|
||||
partstat: "ACCEPTED",
|
||||
rsvp: "TRUE",
|
||||
role: "REQ-PARTICIPANT",
|
||||
cutype: "INDIVIDUAL",
|
||||
cal_address: "alice@example.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
cn: 'Alice',
|
||||
partstat: 'ACCEPTED',
|
||||
rsvp: 'TRUE',
|
||||
role: 'REQ-PARTICIPANT',
|
||||
cutype: 'INDIVIDUAL',
|
||||
cal_address: 'alice@example.com'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
};
|
||||
pending: false
|
||||
}
|
||||
}
|
||||
|
||||
const mockCalendarRef = { current: null };
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
preloadedState
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
it("renders the event on the calendar and calendarRef works", async () => {
|
||||
it('renders the event on the calendar and calendarRef works', async () => {
|
||||
const dispatch = jest.fn().mockReturnValue(
|
||||
Object.assign(Promise.resolve({}), {
|
||||
unwrap: () => Promise.resolve({}),
|
||||
unwrap: () => Promise.resolve({})
|
||||
})
|
||||
) as unknown as AppDispatch;
|
||||
jest.spyOn(appHooks, "useAppDispatch").mockReturnValue(dispatch);
|
||||
) as unknown as AppDispatch
|
||||
jest.spyOn(appHooks, 'useAppDispatch').mockReturnValue(dispatch)
|
||||
|
||||
renderCalendar();
|
||||
renderCalendar()
|
||||
const calendarRef: React.RefObject<CalendarApi | null> = (window as any)
|
||||
.__calendarRef;
|
||||
.__calendarRef
|
||||
|
||||
const calendarApi = calendarRef.current;
|
||||
const calendarApi = calendarRef.current
|
||||
|
||||
// Wait for the FullCalendar DOM to populate
|
||||
const eventEl = await screen.findByText(
|
||||
"Test Event",
|
||||
{},
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
expect(eventEl).toBeInTheDocument();
|
||||
const eventEl = await screen.findByText('Test Event', {}, { timeout: 3000 })
|
||||
expect(eventEl).toBeInTheDocument()
|
||||
act(() => {
|
||||
if (calendarApi) {
|
||||
const fcEvent = calendarApi.getEventById("event1");
|
||||
expect(fcEvent?.title).toBe("Test Event");
|
||||
const oldEnd = new Date(today.getTime() + 3600000); // +1 hour
|
||||
const newEnd = new Date(oldEnd.getTime() + 1800000); // +30 min
|
||||
const fcEvent = calendarApi.getEventById('event1')
|
||||
expect(fcEvent?.title).toBe('Test Event')
|
||||
const oldEnd = new Date(today.getTime() + 3600000) // +1 hour
|
||||
const newEnd = new Date(oldEnd.getTime() + 1800000) // +30 min
|
||||
|
||||
fcEvent?.setEnd(newEnd);
|
||||
fcEvent?.setEnd(newEnd)
|
||||
|
||||
waitFor(() => expect(dispatch).toHaveBeenCalled());
|
||||
waitFor(() => expect(dispatch).toHaveBeenCalled())
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
const createPreloadedState = (eventProps = {}) => ({
|
||||
user: {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "mockSid",
|
||||
openpaasId: "667037022b752d0026472254",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: '667037022b752d0026472254'
|
||||
},
|
||||
tokens: {
|
||||
accessToken: "token",
|
||||
},
|
||||
accessToken: 'token'
|
||||
}
|
||||
},
|
||||
calendars: {
|
||||
list: {
|
||||
"667037022b752d0026472254/cal1": {
|
||||
name: "Calendar 1",
|
||||
id: "667037022b752d0026472254/cal1",
|
||||
color: { light: "#FFFFFF", dark: "#000000" },
|
||||
owner: { emails: ["alice@example.com"] },
|
||||
'667037022b752d0026472254/cal1': {
|
||||
name: 'Calendar 1',
|
||||
id: '667037022b752d0026472254/cal1',
|
||||
color: { light: '#FFFFFF', dark: '#000000' },
|
||||
owner: { emails: ['alice@example.com'] },
|
||||
events: {
|
||||
event1: {
|
||||
id: "event1",
|
||||
calId: "667037022b752d0026472254/cal1",
|
||||
uid: "event1",
|
||||
id: 'event1',
|
||||
calId: '667037022b752d0026472254/cal1',
|
||||
uid: 'event1',
|
||||
start: new Date().toISOString(),
|
||||
end: new Date(Date.now() + 3600000).toISOString(),
|
||||
partstat: "ACCEPTED",
|
||||
partstat: 'ACCEPTED',
|
||||
organizer: {
|
||||
cn: "Alice",
|
||||
cal_address: "alice@example.com",
|
||||
cn: 'Alice',
|
||||
cal_address: 'alice@example.com'
|
||||
},
|
||||
attendee: [
|
||||
{
|
||||
cn: "Alice",
|
||||
partstat: "ACCEPTED",
|
||||
rsvp: "TRUE",
|
||||
role: "REQ-PARTICIPANT",
|
||||
cutype: "INDIVIDUAL",
|
||||
cal_address: "alice@example.com",
|
||||
},
|
||||
cn: 'Alice',
|
||||
partstat: 'ACCEPTED',
|
||||
rsvp: 'TRUE',
|
||||
role: 'REQ-PARTICIPANT',
|
||||
cutype: 'INDIVIDUAL',
|
||||
cal_address: 'alice@example.com'
|
||||
}
|
||||
],
|
||||
...eventProps,
|
||||
},
|
||||
},
|
||||
},
|
||||
...eventProps
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
});
|
||||
pending: false
|
||||
}
|
||||
})
|
||||
|
||||
it("renders lock icon for private events on the calendar", async () => {
|
||||
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
|
||||
it('renders lock icon for private events on the calendar', async () => {
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
|
||||
configurable: true,
|
||||
value: 120,
|
||||
});
|
||||
value: 120
|
||||
})
|
||||
const preloadedState = createPreloadedState({
|
||||
class: "PRIVATE",
|
||||
title: "Private Event",
|
||||
});
|
||||
const mockCalendarRef = { current: null };
|
||||
class: 'PRIVATE',
|
||||
title: 'Private Event'
|
||||
})
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
preloadedState
|
||||
);
|
||||
const card = screen.getByTestId("event-card-event1");
|
||||
const lockIcon = within(card).getByTestId("LockOutlineIcon");
|
||||
expect(lockIcon).toBeInTheDocument();
|
||||
});
|
||||
)
|
||||
const card = screen.getByTestId('event-card-event1')
|
||||
const lockIcon = within(card).getByTestId('LockOutlineIcon')
|
||||
expect(lockIcon).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders lock icon for confidential events on the calendar", async () => {
|
||||
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
|
||||
it('renders lock icon for confidential events on the calendar', async () => {
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
|
||||
configurable: true,
|
||||
value: 120,
|
||||
});
|
||||
value: 120
|
||||
})
|
||||
const preloadedState = createPreloadedState({
|
||||
class: "CONFIDENTIAL",
|
||||
title: "Confidential Event",
|
||||
});
|
||||
const mockCalendarRef = { current: null };
|
||||
class: 'CONFIDENTIAL',
|
||||
title: 'Confidential Event'
|
||||
})
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
preloadedState
|
||||
);
|
||||
)
|
||||
|
||||
const card = screen.getByTestId("event-card-event1");
|
||||
const lockIcon = within(card).getByTestId("LockOutlineIcon");
|
||||
expect(lockIcon).toBeInTheDocument();
|
||||
});
|
||||
const card = screen.getByTestId('event-card-event1')
|
||||
const lockIcon = within(card).getByTestId('LockOutlineIcon')
|
||||
expect(lockIcon).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("does NOT render a lock icon for public events on the calendar", async () => {
|
||||
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
|
||||
it('does NOT render a lock icon for public events on the calendar', async () => {
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
|
||||
configurable: true,
|
||||
value: 120,
|
||||
});
|
||||
value: 120
|
||||
})
|
||||
const preloadedState = createPreloadedState({
|
||||
class: "PUBLIC",
|
||||
title: "Public Event",
|
||||
});
|
||||
const mockCalendarRef = { current: null };
|
||||
class: 'PUBLIC',
|
||||
title: 'Public Event'
|
||||
})
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
preloadedState
|
||||
);
|
||||
)
|
||||
|
||||
const card = screen.getByTestId("event-card-event1");
|
||||
const lockIcon = within(card).queryByTestId("LockOutlineIcon");
|
||||
expect(lockIcon).not.toBeInTheDocument();
|
||||
});
|
||||
const card = screen.getByTestId('event-card-event1')
|
||||
const lockIcon = within(card).queryByTestId('LockOutlineIcon')
|
||||
expect(lockIcon).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("does render a title for events without any attendees or user as organizer", async () => {
|
||||
const mockCalendarRef = { current: null };
|
||||
it('does render a title for events without any attendees or user as organizer', async () => {
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(<CalendarApp calendarRef={mockCalendarRef} />, {
|
||||
user: {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "mockSid",
|
||||
openpaasId: "667037022b752d0026472254",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: '667037022b752d0026472254'
|
||||
},
|
||||
tokens: {
|
||||
accessToken: "token",
|
||||
},
|
||||
accessToken: 'token'
|
||||
}
|
||||
},
|
||||
calendars: {
|
||||
list: {
|
||||
"667037022b752d0026472254/cal1": {
|
||||
name: "Calendar 1",
|
||||
id: "667037022b752d0026472254/cal1",
|
||||
color: { light: "#FF0000", dark: "#000" },
|
||||
owner: { emails: ["alice@example.com"] },
|
||||
'667037022b752d0026472254/cal1': {
|
||||
name: 'Calendar 1',
|
||||
id: '667037022b752d0026472254/cal1',
|
||||
color: { light: '#FF0000', dark: '#000' },
|
||||
owner: { emails: ['alice@example.com'] },
|
||||
events: {
|
||||
event1: {
|
||||
id: "event1",
|
||||
calId: "667037022b752d0026472254/cal1",
|
||||
uid: "event1",
|
||||
id: 'event1',
|
||||
calId: '667037022b752d0026472254/cal1',
|
||||
uid: 'event1',
|
||||
start: new Date().toISOString(),
|
||||
end: new Date(Date.now() + 3600000).toISOString(),
|
||||
partstat: "ACCEPTED",
|
||||
partstat: 'ACCEPTED',
|
||||
organizer: {
|
||||
cn: "Alice",
|
||||
cal_address: "alice@example.com",
|
||||
cn: 'Alice',
|
||||
cal_address: 'alice@example.com'
|
||||
},
|
||||
class: "PUBLIC",
|
||||
title: "Public Event",
|
||||
},
|
||||
},
|
||||
},
|
||||
class: 'PUBLIC',
|
||||
title: 'Public Event'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
});
|
||||
pending: false
|
||||
}
|
||||
})
|
||||
|
||||
expect(screen.getByText("Public Event")).toBeInTheDocument();
|
||||
});
|
||||
describe("BUGFIX", () => {
|
||||
expect(screen.getByText('Public Event')).toBeInTheDocument()
|
||||
})
|
||||
describe('BUGFIX', () => {
|
||||
const preloadedState = {
|
||||
user: {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "mockSid",
|
||||
openpaasId: "667037022b752d0026472254",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: '667037022b752d0026472254'
|
||||
},
|
||||
tokens: {
|
||||
accessToken: "token",
|
||||
},
|
||||
accessToken: 'token'
|
||||
}
|
||||
},
|
||||
calendars: {
|
||||
list: {
|
||||
"667037022b752d0026472254/cal1": {
|
||||
name: "Calendar 1",
|
||||
id: "667037022b752d0026472254/cal1",
|
||||
color: { light: "#FFFFFF", dark: "#000000" },
|
||||
owner: { emails: ["alice@example.com"] },
|
||||
'667037022b752d0026472254/cal1': {
|
||||
name: 'Calendar 1',
|
||||
id: '667037022b752d0026472254/cal1',
|
||||
color: { light: '#FFFFFF', dark: '#000000' },
|
||||
owner: { emails: ['alice@example.com'] },
|
||||
events: {
|
||||
event1: {
|
||||
id: "event1",
|
||||
calId: "667037022b752d0026472254/cal1",
|
||||
uid: "event1",
|
||||
title: "Original Event",
|
||||
start: new Date("2025-11-14T10:31:00.000Z").toISOString(),
|
||||
end: new Date("2025-11-14T11:31:00.000Z").toISOString(),
|
||||
class: "PUBLIC",
|
||||
partstat: "ACCEPTED",
|
||||
id: 'event1',
|
||||
calId: '667037022b752d0026472254/cal1',
|
||||
uid: 'event1',
|
||||
title: 'Original Event',
|
||||
start: new Date('2025-11-14T10:31:00.000Z').toISOString(),
|
||||
end: new Date('2025-11-14T11:31:00.000Z').toISOString(),
|
||||
class: 'PUBLIC',
|
||||
partstat: 'ACCEPTED',
|
||||
sequence: 2,
|
||||
organizer: {
|
||||
cn: "Alice",
|
||||
cal_address: "alice@example.com",
|
||||
cn: 'Alice',
|
||||
cal_address: 'alice@example.com'
|
||||
},
|
||||
attendee: [
|
||||
{
|
||||
cn: "Alice",
|
||||
partstat: "ACCEPTED",
|
||||
rsvp: "TRUE",
|
||||
role: "CHAIR",
|
||||
cutype: "INDIVIDUAL",
|
||||
cal_address: "alice@example.com",
|
||||
cn: 'Alice',
|
||||
partstat: 'ACCEPTED',
|
||||
rsvp: 'TRUE',
|
||||
role: 'CHAIR',
|
||||
cutype: 'INDIVIDUAL',
|
||||
cal_address: 'alice@example.com'
|
||||
},
|
||||
{
|
||||
cn: "Bob",
|
||||
partstat: "ACCEPTED",
|
||||
rsvp: "TRUE",
|
||||
role: "REQ-PARTICIPANT",
|
||||
cutype: "INDIVIDUAL",
|
||||
cal_address: "bob@example.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
cn: 'Bob',
|
||||
partstat: 'ACCEPTED',
|
||||
rsvp: 'TRUE',
|
||||
role: 'REQ-PARTICIPANT',
|
||||
cutype: 'INDIVIDUAL',
|
||||
cal_address: 'bob@example.com'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
};
|
||||
pending: false
|
||||
}
|
||||
}
|
||||
|
||||
it("keeps all attendees event participation on title update", async () => {
|
||||
it('keeps all attendees event participation on title update', async () => {
|
||||
const updateSpy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
const onClose = jest.fn();
|
||||
.spyOn(eventThunks, 'putEventAsync')
|
||||
.mockImplementation(payload => {
|
||||
const promise = Promise.resolve(payload)
|
||||
;(promise as any).unwrap = () => promise
|
||||
return () => promise as any
|
||||
})
|
||||
const onClose = jest.fn()
|
||||
renderWithProviders(
|
||||
<EventUpdateModal
|
||||
eventId="event1"
|
||||
@@ -354,56 +344,56 @@ describe("CalendarApp integration", () => {
|
||||
typeOfAction="solo"
|
||||
/>,
|
||||
preloadedState
|
||||
);
|
||||
)
|
||||
|
||||
const titleInput = await screen.findByDisplayValue("Original Event");
|
||||
const titleInput = await screen.findByDisplayValue('Original Event')
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(titleInput, "Updated Event");
|
||||
});
|
||||
fireEvent.change(titleInput, 'Updated Event')
|
||||
})
|
||||
|
||||
const saveButton = screen.getByRole("button", { name: /save/i });
|
||||
const saveButton = screen.getByRole('button', { name: /save/i })
|
||||
await act(async () => {
|
||||
saveButton.click();
|
||||
});
|
||||
saveButton.click()
|
||||
})
|
||||
|
||||
await waitFor(() => expect(updateSpy).toHaveBeenCalled());
|
||||
await waitFor(() => expect(updateSpy).toHaveBeenCalled())
|
||||
|
||||
const dispatchedCalls = updateSpy.mock.calls;
|
||||
expect(dispatchedCalls.length).toBeGreaterThan(0);
|
||||
const updatedEvent = dispatchedCalls[0][0].newEvent;
|
||||
const dispatchedCalls = updateSpy.mock.calls
|
||||
expect(dispatchedCalls.length).toBeGreaterThan(0)
|
||||
const updatedEvent = dispatchedCalls[0][0].newEvent
|
||||
|
||||
// Ensure organizer attendee info is preserved
|
||||
const organizerAttendee = updatedEvent?.attendee?.find(
|
||||
(a: any) => a.cal_address === "alice@example.com"
|
||||
);
|
||||
(a: any) => a.cal_address === 'alice@example.com'
|
||||
)
|
||||
|
||||
expect(organizerAttendee).toBeTruthy();
|
||||
expect(organizerAttendee?.partstat).toBe("ACCEPTED");
|
||||
expect(organizerAttendee?.role).toBe("CHAIR");
|
||||
expect(organizerAttendee).toBeTruthy()
|
||||
expect(organizerAttendee?.partstat).toBe('ACCEPTED')
|
||||
expect(organizerAttendee?.role).toBe('CHAIR')
|
||||
|
||||
// Ensure normal attendee info is preserved too
|
||||
const normalAttendee = updatedEvent?.attendee?.find(
|
||||
(a: any) => a.cal_address === "bob@example.com"
|
||||
);
|
||||
(a: any) => a.cal_address === 'bob@example.com'
|
||||
)
|
||||
|
||||
expect(normalAttendee).toBeTruthy();
|
||||
expect(normalAttendee?.partstat).toBe("ACCEPTED");
|
||||
expect(normalAttendee?.role).toBe("REQ-PARTICIPANT");
|
||||
expect(normalAttendee).toBeTruthy()
|
||||
expect(normalAttendee?.partstat).toBe('ACCEPTED')
|
||||
expect(normalAttendee?.role).toBe('REQ-PARTICIPANT')
|
||||
|
||||
// Verify SEQUENCE is incremented
|
||||
expect(updatedEvent?.sequence).toBe(3); // 2 + 1
|
||||
});
|
||||
expect(updatedEvent?.sequence).toBe(3) // 2 + 1
|
||||
})
|
||||
|
||||
it("changes normal attendee to need action on time update and no organizer changes", async () => {
|
||||
it('changes normal attendee to need action on time update and no organizer changes', async () => {
|
||||
const updateSpy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
const onClose = jest.fn();
|
||||
.spyOn(eventThunks, 'putEventAsync')
|
||||
.mockImplementation(payload => {
|
||||
const promise = Promise.resolve(payload)
|
||||
;(promise as any).unwrap = () => promise
|
||||
return () => promise as any
|
||||
})
|
||||
const onClose = jest.fn()
|
||||
renderWithProviders(
|
||||
<EventUpdateModal
|
||||
eventId="event1"
|
||||
@@ -413,78 +403,78 @@ describe("CalendarApp integration", () => {
|
||||
typeOfAction="solo"
|
||||
/>,
|
||||
preloadedState
|
||||
);
|
||||
)
|
||||
|
||||
// Expand to show date/time inputs (normal mode shows DateTimeSummary)
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "common.moreOptions" })
|
||||
);
|
||||
const startDateInput = await screen.findByTestId("start-time-input");
|
||||
screen.getByRole('button', { name: 'common.moreOptions' })
|
||||
)
|
||||
const startDateInput = await screen.findByTestId('start-time-input')
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(startDateInput, {
|
||||
target: { value: "08:00" },
|
||||
});
|
||||
fireEvent.blur(startDateInput);
|
||||
});
|
||||
target: { value: '08:00' }
|
||||
})
|
||||
fireEvent.blur(startDateInput)
|
||||
})
|
||||
|
||||
// Wait for blur handler to complete
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
});
|
||||
await new Promise(resolve => setTimeout(resolve, 150))
|
||||
})
|
||||
|
||||
const saveButton = screen.getByRole("button", { name: /save/i });
|
||||
const saveButton = screen.getByRole('button', { name: /save/i })
|
||||
await act(async () => {
|
||||
saveButton.click();
|
||||
});
|
||||
saveButton.click()
|
||||
})
|
||||
|
||||
await waitFor(() => expect(updateSpy).toHaveBeenCalled());
|
||||
await waitFor(() => expect(updateSpy).toHaveBeenCalled())
|
||||
|
||||
const dispatchedCalls = updateSpy.mock.calls;
|
||||
expect(dispatchedCalls.length).toBeGreaterThan(0);
|
||||
const updatedEvent = dispatchedCalls[0][0].newEvent;
|
||||
const dispatchedCalls = updateSpy.mock.calls
|
||||
expect(dispatchedCalls.length).toBeGreaterThan(0)
|
||||
const updatedEvent = dispatchedCalls[0][0].newEvent
|
||||
|
||||
// Ensure organizer attendee info is preserved
|
||||
const organizerAttendee = updatedEvent?.attendee?.find(
|
||||
(a: any) => a.cal_address === "alice@example.com"
|
||||
);
|
||||
(a: any) => a.cal_address === 'alice@example.com'
|
||||
)
|
||||
|
||||
expect(organizerAttendee).toBeTruthy();
|
||||
expect(organizerAttendee?.partstat).toBe("ACCEPTED");
|
||||
expect(organizerAttendee?.role).toBe("CHAIR");
|
||||
expect(organizerAttendee).toBeTruthy()
|
||||
expect(organizerAttendee?.partstat).toBe('ACCEPTED')
|
||||
expect(organizerAttendee?.role).toBe('CHAIR')
|
||||
|
||||
// Ensure normal attendee partstat is updated
|
||||
const normalAttendee = updatedEvent?.attendee?.find(
|
||||
(a: any) => a.cal_address === "bob@example.com"
|
||||
);
|
||||
(a: any) => a.cal_address === 'bob@example.com'
|
||||
)
|
||||
|
||||
expect(normalAttendee).toBeTruthy();
|
||||
expect(normalAttendee?.partstat).toBe("NEEDS-ACTION");
|
||||
expect(normalAttendee?.role).toBe("REQ-PARTICIPANT");
|
||||
expect(normalAttendee).toBeTruthy()
|
||||
expect(normalAttendee?.partstat).toBe('NEEDS-ACTION')
|
||||
expect(normalAttendee?.role).toBe('REQ-PARTICIPANT')
|
||||
|
||||
// Verify SEQUENCE is incremented
|
||||
expect(updatedEvent?.sequence).toBe(3); // 2 + 1
|
||||
});
|
||||
expect(updatedEvent?.sequence).toBe(3) // 2 + 1
|
||||
})
|
||||
|
||||
it("update event attendees on drag", async () => {
|
||||
it('update event attendees on drag', async () => {
|
||||
// Mock dispatch locally — this test calls createEventHandlers directly
|
||||
// and does not go through the Redux store or useAppDispatch.
|
||||
const mockDispatch = jest.fn().mockReturnValue(
|
||||
Object.assign(Promise.resolve({}), {
|
||||
unwrap: () => Promise.resolve({}),
|
||||
unwrap: () => Promise.resolve({})
|
||||
})
|
||||
) as unknown as AppDispatch;
|
||||
) as unknown as AppDispatch
|
||||
jest
|
||||
.spyOn(appHooks, "useAppDispatch")
|
||||
.mockReturnValue(mockDispatch as unknown as AppDispatch);
|
||||
.spyOn(appHooks, 'useAppDispatch')
|
||||
.mockReturnValue(mockDispatch as unknown as AppDispatch)
|
||||
|
||||
const updateSpy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
.spyOn(eventThunks, 'putEventAsync')
|
||||
.mockImplementation(payload => {
|
||||
const promise = Promise.resolve(payload)
|
||||
;(promise as any).unwrap = () => promise
|
||||
return () => promise as any
|
||||
})
|
||||
|
||||
const eventHandlers = createEventHandlers({
|
||||
setSelectedRange: jest.fn(),
|
||||
@@ -499,70 +489,70 @@ describe("CalendarApp integration", () => {
|
||||
calendars: preloadedState.calendars.list,
|
||||
setSelectedEvent: jest.fn(),
|
||||
setAfterChoiceFunc: jest.fn(),
|
||||
setOpenEditModePopup: jest.fn(),
|
||||
} as unknown as EventHandlersProps);
|
||||
setOpenEditModePopup: jest.fn()
|
||||
} as unknown as EventHandlersProps)
|
||||
|
||||
const mockArg = {
|
||||
event: {
|
||||
_def: {
|
||||
extendedProps: {
|
||||
uid: "event1",
|
||||
calId: "667037022b752d0026472254/cal1",
|
||||
},
|
||||
},
|
||||
uid: 'event1',
|
||||
calId: '667037022b752d0026472254/cal1'
|
||||
}
|
||||
}
|
||||
},
|
||||
// drag event → move by 1 day
|
||||
delta: { years: 0, months: 0, days: 1, milliseconds: 0 },
|
||||
};
|
||||
delta: { years: 0, months: 0, days: 1, milliseconds: 0 }
|
||||
}
|
||||
|
||||
renderCalendar();
|
||||
eventHandlers.handleEventDrop(mockArg);
|
||||
renderCalendar()
|
||||
eventHandlers.handleEventDrop(mockArg)
|
||||
|
||||
expect(updateSpy).toHaveBeenCalled();
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
|
||||
// Extract the dispatched update event
|
||||
const dispatched = updateSpy.mock.calls[0];
|
||||
const dispatched = updateSpy.mock.calls[0]
|
||||
|
||||
expect(dispatched).toBeTruthy();
|
||||
expect(dispatched).toBeTruthy()
|
||||
|
||||
const updatePayload = dispatched[0];
|
||||
const updatedEvent = updatePayload.newEvent;
|
||||
const updatePayload = dispatched[0]
|
||||
const updatedEvent = updatePayload.newEvent
|
||||
|
||||
// Organizer should remain unchanged
|
||||
const organizer = updatedEvent.attendee.find(
|
||||
(a: any) => a.cal_address === "alice@example.com"
|
||||
);
|
||||
expect(organizer?.partstat).toBe("ACCEPTED");
|
||||
(a: any) => a.cal_address === 'alice@example.com'
|
||||
)
|
||||
expect(organizer?.partstat).toBe('ACCEPTED')
|
||||
|
||||
// Normal attendee must become NEEDS-ACTION
|
||||
const normal = updatedEvent.attendee.find(
|
||||
(a: any) => a.cal_address === "bob@example.com"
|
||||
);
|
||||
expect(normal?.partstat).toBe("NEEDS-ACTION");
|
||||
(a: any) => a.cal_address === 'bob@example.com'
|
||||
)
|
||||
expect(normal?.partstat).toBe('NEEDS-ACTION')
|
||||
|
||||
// Verify SEQUENCE is incremented
|
||||
expect(updatedEvent?.sequence).toBe(3); // 2 + 1
|
||||
});
|
||||
expect(updatedEvent?.sequence).toBe(3) // 2 + 1
|
||||
})
|
||||
|
||||
it("update event attendees on resize", async () => {
|
||||
it('update event attendees on resize', async () => {
|
||||
// Mock dispatch locally — this test calls createEventHandlers directly
|
||||
// and does not go through the Redux store or useAppDispatch.
|
||||
const mockDispatch = jest.fn().mockReturnValue(
|
||||
Object.assign(Promise.resolve({}), {
|
||||
unwrap: () => Promise.resolve({}),
|
||||
unwrap: () => Promise.resolve({})
|
||||
})
|
||||
) as unknown as AppDispatch;
|
||||
) as unknown as AppDispatch
|
||||
jest
|
||||
.spyOn(appHooks, "useAppDispatch")
|
||||
.mockReturnValue(mockDispatch as unknown as AppDispatch);
|
||||
.spyOn(appHooks, 'useAppDispatch')
|
||||
.mockReturnValue(mockDispatch as unknown as AppDispatch)
|
||||
|
||||
const updateSpy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
.spyOn(eventThunks, 'putEventAsync')
|
||||
.mockImplementation(payload => {
|
||||
const promise = Promise.resolve(payload)
|
||||
;(promise as any).unwrap = () => promise
|
||||
return () => promise as any
|
||||
})
|
||||
|
||||
const eventHandlers = createEventHandlers({
|
||||
setSelectedRange: jest.fn(),
|
||||
@@ -577,49 +567,49 @@ describe("CalendarApp integration", () => {
|
||||
calendars: preloadedState.calendars.list,
|
||||
setSelectedEvent: jest.fn(),
|
||||
setAfterChoiceFunc: jest.fn(),
|
||||
setOpenEditModePopup: jest.fn(),
|
||||
} as unknown as EventHandlersProps);
|
||||
setOpenEditModePopup: jest.fn()
|
||||
} as unknown as EventHandlersProps)
|
||||
|
||||
const mockArg = {
|
||||
event: {
|
||||
_def: {
|
||||
extendedProps: {
|
||||
uid: "event1",
|
||||
calId: "667037022b752d0026472254/cal1",
|
||||
},
|
||||
},
|
||||
uid: 'event1',
|
||||
calId: '667037022b752d0026472254/cal1'
|
||||
}
|
||||
}
|
||||
},
|
||||
startDelta: { years: 0, months: 0, days: 0, milliseconds: 0 },
|
||||
endDelta: { years: 0, months: 0, days: 0, milliseconds: 3600000 }, // 1 hour
|
||||
};
|
||||
endDelta: { years: 0, months: 0, days: 0, milliseconds: 3600000 } // 1 hour
|
||||
}
|
||||
|
||||
renderCalendar();
|
||||
eventHandlers.handleEventResize(mockArg);
|
||||
renderCalendar()
|
||||
eventHandlers.handleEventResize(mockArg)
|
||||
|
||||
expect(updateSpy).toHaveBeenCalled();
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
|
||||
// Extract the dispatched update event
|
||||
const dispatched = updateSpy.mock.calls[0];
|
||||
const dispatched = updateSpy.mock.calls[0]
|
||||
|
||||
expect(dispatched).toBeTruthy();
|
||||
expect(dispatched).toBeTruthy()
|
||||
|
||||
const updatePayload = dispatched[0];
|
||||
const updatedEvent = updatePayload.newEvent;
|
||||
const updatePayload = dispatched[0]
|
||||
const updatedEvent = updatePayload.newEvent
|
||||
|
||||
// Organizer should remain unchanged
|
||||
const organizer = updatedEvent.attendee.find(
|
||||
(a: any) => a.cal_address === "alice@example.com"
|
||||
);
|
||||
expect(organizer?.partstat).toBe("ACCEPTED");
|
||||
(a: any) => a.cal_address === 'alice@example.com'
|
||||
)
|
||||
expect(organizer?.partstat).toBe('ACCEPTED')
|
||||
|
||||
// Normal attendee must become NEEDS-ACTION
|
||||
const normal = updatedEvent.attendee.find(
|
||||
(a: any) => a.cal_address === "bob@example.com"
|
||||
);
|
||||
expect(normal?.partstat).toBe("NEEDS-ACTION");
|
||||
(a: any) => a.cal_address === 'bob@example.com'
|
||||
)
|
||||
expect(normal?.partstat).toBe('NEEDS-ACTION')
|
||||
|
||||
// Verify SEQUENCE is incremented
|
||||
expect(updatedEvent?.sequence).toBe(3); // 2 + 1
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(updatedEvent?.sequence).toBe(3) // 2 + 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user