* #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:
File diff suppressed because it is too large
Load Diff
@@ -1,106 +1,106 @@
|
||||
import CalendarLayout from "@/components/Calendar/CalendarLayout";
|
||||
import { act, waitFor } from "@testing-library/react";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
import CalendarLayout from '@/components/Calendar/CalendarLayout'
|
||||
import { act, waitFor } from '@testing-library/react'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
const PIVOT_UTC = new Date("2026-03-16T17:00:00Z");
|
||||
const PIVOT_UTC = new Date('2026-03-16T17:00:00Z')
|
||||
|
||||
const makeState = (timezone: string) => ({
|
||||
user: {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "mockSid",
|
||||
openpaasId: "user1",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: 'user1'
|
||||
},
|
||||
tokens: { accessToken: "token" },
|
||||
tokens: { accessToken: 'token' }
|
||||
},
|
||||
settings: { view: "calendar", timeZone: timezone },
|
||||
settings: { view: 'calendar', timeZone: timezone },
|
||||
calendars: {
|
||||
list: {
|
||||
"user1/cal1": {
|
||||
name: "Calendar personal",
|
||||
id: "user1/cal1",
|
||||
color: { light: "#FF0000", dark: "#000" },
|
||||
owner: { emails: ["test@test.com"] },
|
||||
events: {},
|
||||
},
|
||||
'user1/cal1': {
|
||||
name: 'Calendar personal',
|
||||
id: 'user1/cal1',
|
||||
color: { light: '#FF0000', dark: '#000' },
|
||||
owner: { emails: ['test@test.com'] },
|
||||
events: {}
|
||||
}
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
});
|
||||
pending: false
|
||||
}
|
||||
})
|
||||
|
||||
describe("Calendar – dayHeaderContent respects selected timezone", () => {
|
||||
describe('Calendar – dayHeaderContent respects selected timezone', () => {
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
async function renderAtPivotTime(timezone: string): Promise<string[]> {
|
||||
// Set fake timers the same way the working test does — single chained call
|
||||
jest.useFakeTimers().setSystemTime(PIVOT_UTC);
|
||||
jest.useFakeTimers().setSystemTime(PIVOT_UTC)
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(<CalendarLayout />, makeState(timezone));
|
||||
});
|
||||
renderWithProviders(<CalendarLayout />, makeState(timezone))
|
||||
})
|
||||
|
||||
// Advance past the debounce (300ms) like the working navigation test does
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
const calendarRef = window.__calendarRef;
|
||||
await waitFor(() => expect(calendarRef?.current).not.toBeNull());
|
||||
const calendarRef = window.__calendarRef
|
||||
await waitFor(() => expect(calendarRef?.current).not.toBeNull())
|
||||
|
||||
await act(async () => {
|
||||
calendarRef.current?.changeView("timeGridWeek");
|
||||
});
|
||||
calendarRef.current?.changeView('timeGridWeek')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(document.querySelectorAll(".fc-col-header-cell").length).toBe(7);
|
||||
});
|
||||
expect(document.querySelectorAll('.fc-col-header-cell').length).toBe(7)
|
||||
})
|
||||
|
||||
return Array.from(document.querySelectorAll(".fc-col-header-cell")).map(
|
||||
(cell) => cell.textContent ?? ""
|
||||
);
|
||||
return Array.from(document.querySelectorAll('.fc-col-header-cell')).map(
|
||||
cell => cell.textContent ?? ''
|
||||
)
|
||||
}
|
||||
|
||||
it("UTC: Monday March 16 column exists", async () => {
|
||||
const cells = await renderAtPivotTime("UTC");
|
||||
const monday = cells.find((t) => t.includes("16"));
|
||||
expect(monday).toBeDefined();
|
||||
expect(monday).toMatch(/MON/i);
|
||||
});
|
||||
it('UTC: Monday March 16 column exists', async () => {
|
||||
const cells = await renderAtPivotTime('UTC')
|
||||
const monday = cells.find(t => t.includes('16'))
|
||||
expect(monday).toBeDefined()
|
||||
expect(monday).toMatch(/MON/i)
|
||||
})
|
||||
|
||||
it("Europe/Paris: Monday March 16 column exists (UTC+1, no day shift)", async () => {
|
||||
const cells = await renderAtPivotTime("Europe/Paris");
|
||||
const monday = cells.find((t) => t.includes("16"));
|
||||
expect(monday).toBeDefined();
|
||||
expect(monday).toMatch(/MON/i);
|
||||
});
|
||||
it('Europe/Paris: Monday March 16 column exists (UTC+1, no day shift)', async () => {
|
||||
const cells = await renderAtPivotTime('Europe/Paris')
|
||||
const monday = cells.find(t => t.includes('16'))
|
||||
expect(monday).toBeDefined()
|
||||
expect(monday).toMatch(/MON/i)
|
||||
})
|
||||
|
||||
it("Asia/Jakarta: Tuesday March 17 column exists (UTC+7, day shifts forward)", async () => {
|
||||
const cells = await renderAtPivotTime("Asia/Jakarta");
|
||||
const tuesday = cells.find((t) => t.includes("17"));
|
||||
expect(tuesday).toBeDefined();
|
||||
expect(tuesday).toMatch(/TUE/i);
|
||||
});
|
||||
it('Asia/Jakarta: Tuesday March 17 column exists (UTC+7, day shifts forward)', async () => {
|
||||
const cells = await renderAtPivotTime('Asia/Jakarta')
|
||||
const tuesday = cells.find(t => t.includes('17'))
|
||||
expect(tuesday).toBeDefined()
|
||||
expect(tuesday).toMatch(/TUE/i)
|
||||
})
|
||||
|
||||
it("Asia/Jakarta: no column incorrectly labeled MON 17 – regression", async () => {
|
||||
const cells = await renderAtPivotTime("Asia/Jakarta");
|
||||
const wrongCell = cells.find((t) => t.includes("17") && /MON/i.test(t));
|
||||
expect(wrongCell).toBeUndefined();
|
||||
});
|
||||
it('Asia/Jakarta: no column incorrectly labeled MON 17 – regression', async () => {
|
||||
const cells = await renderAtPivotTime('Asia/Jakarta')
|
||||
const wrongCell = cells.find(t => t.includes('17') && /MON/i.test(t))
|
||||
expect(wrongCell).toBeUndefined()
|
||||
})
|
||||
|
||||
it("Asia/Tokyo: Tuesday March 17 column exists (UTC+9)", async () => {
|
||||
const cells = await renderAtPivotTime("Asia/Tokyo");
|
||||
const tuesday = cells.find((t) => t.includes("17"));
|
||||
expect(tuesday).toBeDefined();
|
||||
expect(tuesday).toMatch(/TUE/i);
|
||||
});
|
||||
it('Asia/Tokyo: Tuesday March 17 column exists (UTC+9)', async () => {
|
||||
const cells = await renderAtPivotTime('Asia/Tokyo')
|
||||
const tuesday = cells.find(t => t.includes('17'))
|
||||
expect(tuesday).toBeDefined()
|
||||
expect(tuesday).toMatch(/TUE/i)
|
||||
})
|
||||
|
||||
it("America/New_York: Monday March 16 column exists (UTC-4, no day shift)", async () => {
|
||||
const cells = await renderAtPivotTime("America/New_York");
|
||||
const monday = cells.find((t) => t.includes("16"));
|
||||
expect(monday).toBeDefined();
|
||||
expect(monday).toMatch(/MON/i);
|
||||
});
|
||||
});
|
||||
it('America/New_York: Monday March 16 column exists (UTC-4, no day shift)', async () => {
|
||||
const cells = await renderAtPivotTime('America/New_York')
|
||||
const monday = cells.find(t => t.includes('16'))
|
||||
expect(monday).toBeDefined()
|
||||
expect(monday).toMatch(/MON/i)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,413 +1,411 @@
|
||||
import CalendarSearch from "@/components/Calendar/CalendarSearch";
|
||||
import * as CalendarApi from "@/features/Calendars/CalendarApi";
|
||||
import * as CalendarSlice from "@/features/Calendars/services";
|
||||
import { searchUsers } from "@/features/User/userAPI";
|
||||
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
import CalendarSearch from '@/components/Calendar/CalendarSearch'
|
||||
import * as CalendarApi from '@/features/Calendars/CalendarApi'
|
||||
import * as CalendarSlice from '@/features/Calendars/services'
|
||||
import { searchUsers } from '@/features/User/userAPI'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
jest.mock("@/features/User/userAPI");
|
||||
jest.mock("@/features/Calendars/CalendarApi");
|
||||
jest.mock('@/features/User/userAPI')
|
||||
jest.mock('@/features/Calendars/CalendarApi')
|
||||
|
||||
const mockedSearchUsers = searchUsers as jest.MockedFunction<
|
||||
typeof searchUsers
|
||||
>;
|
||||
const mockedSearchUsers = searchUsers as jest.MockedFunction<typeof searchUsers>
|
||||
const mockedGetCalendars = CalendarApi.getCalendars as jest.MockedFunction<
|
||||
typeof CalendarApi.getCalendars
|
||||
>;
|
||||
>
|
||||
|
||||
describe("CalendarSearch", () => {
|
||||
const mockOnClose = jest.fn();
|
||||
describe('CalendarSearch', () => {
|
||||
const mockOnClose = jest.fn()
|
||||
const mockUser = {
|
||||
email: "user@example.com",
|
||||
displayName: "Test User",
|
||||
avatarUrl: "https://example.com/avatar.jpg",
|
||||
openpaasId: "user123",
|
||||
};
|
||||
email: 'user@example.com',
|
||||
displayName: 'Test User',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
openpaasId: 'user123'
|
||||
}
|
||||
|
||||
const mockCalendar = {
|
||||
"dav:name": "Test Calendar",
|
||||
"apple:color": "#FF0000",
|
||||
'dav:name': 'Test Calendar',
|
||||
'apple:color': '#FF0000',
|
||||
_links: {
|
||||
self: {
|
||||
href: "/calendars/user123/cal1.json",
|
||||
},
|
||||
},
|
||||
};
|
||||
href: '/calendars/user123/cal1.json'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const preloadedState = {
|
||||
user: {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "mockSid",
|
||||
openpaasId: "user1",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: 'user1'
|
||||
},
|
||||
tokens: { accessToken: "token" },
|
||||
tokens: { accessToken: 'token' }
|
||||
},
|
||||
calendars: {
|
||||
list: {
|
||||
"user1/cal1": {
|
||||
name: "My Calendar",
|
||||
id: "user1/cal1",
|
||||
color: "#0000FF",
|
||||
owner: { emails: ["test@test.com"] },
|
||||
events: {},
|
||||
},
|
||||
'user1/cal1': {
|
||||
name: 'My Calendar',
|
||||
id: 'user1/cal1',
|
||||
color: '#0000FF',
|
||||
owner: { emails: ['test@test.com'] },
|
||||
events: {}
|
||||
}
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
};
|
||||
pending: false
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("searches for users and displays their calendars", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser]);
|
||||
it('searches for users and displays their calendars', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser])
|
||||
mockedGetCalendars.mockResolvedValueOnce({
|
||||
_embedded: {
|
||||
"dav:calendar": [mockCalendar],
|
||||
},
|
||||
});
|
||||
'dav:calendar': [mockCalendar]
|
||||
}
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarSearch open={true} onClose={mockOnClose} />,
|
||||
preloadedState
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
const input = screen.getByRole('combobox')
|
||||
await act(async () => {
|
||||
userEvent.type(input, "Test");
|
||||
});
|
||||
userEvent.type(input, 'Test')
|
||||
})
|
||||
|
||||
const option = await screen.findByText("Test User");
|
||||
const option = await screen.findByText('Test User')
|
||||
await act(async () => {
|
||||
fireEvent.click(option);
|
||||
});
|
||||
fireEvent.click(option)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedGetCalendars).toHaveBeenCalledWith(
|
||||
"user123",
|
||||
"sharedPublic=true&"
|
||||
);
|
||||
});
|
||||
'user123',
|
||||
'sharedPublic=true&'
|
||||
)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument();
|
||||
expect(screen.getByText("user@example.com")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument()
|
||||
expect(screen.getByText('user@example.com')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it("adds selected calendars on save", async () => {
|
||||
it('adds selected calendars on save', async () => {
|
||||
const addSharedCalendarSpy = jest
|
||||
.spyOn(CalendarSlice, "addSharedCalendarAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
});
|
||||
.spyOn(CalendarSlice, 'addSharedCalendarAsync')
|
||||
.mockImplementation(payload => {
|
||||
return () => Promise.resolve(payload) as any
|
||||
})
|
||||
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser]);
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser])
|
||||
mockedGetCalendars.mockResolvedValueOnce({
|
||||
_embedded: {
|
||||
"dav:calendar": [mockCalendar],
|
||||
},
|
||||
});
|
||||
'dav:calendar': [mockCalendar]
|
||||
}
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarSearch open={true} onClose={mockOnClose} />,
|
||||
preloadedState
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
const input = screen.getByRole('combobox')
|
||||
await act(async () => {
|
||||
userEvent.type(input, "Test");
|
||||
});
|
||||
userEvent.type(input, 'Test')
|
||||
})
|
||||
|
||||
const option = await screen.findByText("Test User");
|
||||
const option = await screen.findByText('Test User')
|
||||
await act(async () => {
|
||||
fireEvent.click(option);
|
||||
});
|
||||
fireEvent.click(option)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const addButton = screen.getByRole("button", { name: /add/i });
|
||||
const addButton = screen.getByRole('button', { name: /add/i })
|
||||
await act(async () => {
|
||||
fireEvent.click(addButton);
|
||||
});
|
||||
fireEvent.click(addButton)
|
||||
})
|
||||
|
||||
expect(addSharedCalendarSpy).toHaveBeenCalled();
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
});
|
||||
expect(addSharedCalendarSpy).toHaveBeenCalled()
|
||||
expect(mockOnClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("does not add calendars that already exist", async () => {
|
||||
it('does not add calendars that already exist', async () => {
|
||||
const addSharedCalendarSpy = jest
|
||||
.spyOn(CalendarSlice, "addSharedCalendarAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
});
|
||||
.spyOn(CalendarSlice, 'addSharedCalendarAsync')
|
||||
.mockImplementation(payload => {
|
||||
return () => Promise.resolve(payload) as any
|
||||
})
|
||||
|
||||
const existingCalendar = {
|
||||
...mockCalendar,
|
||||
_links: {
|
||||
self: {
|
||||
href: "/calendars/user1/cal1.json",
|
||||
},
|
||||
},
|
||||
};
|
||||
href: '/calendars/user1/cal1.json'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser]);
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser])
|
||||
mockedGetCalendars.mockResolvedValueOnce({
|
||||
_embedded: {
|
||||
"dav:calendar": [existingCalendar],
|
||||
},
|
||||
});
|
||||
'dav:calendar': [existingCalendar]
|
||||
}
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarSearch open={true} onClose={mockOnClose} />,
|
||||
preloadedState
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
const input = screen.getByRole('combobox')
|
||||
await act(async () => {
|
||||
userEvent.type(input, "Test");
|
||||
});
|
||||
userEvent.type(input, 'Test')
|
||||
})
|
||||
|
||||
const option = await screen.findByText("Test User");
|
||||
const option = await screen.findByText('Test User')
|
||||
await act(async () => {
|
||||
fireEvent.click(option);
|
||||
});
|
||||
fireEvent.click(option)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("calendar.noMoreCalendarsFor(name=Test User)")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
screen.getByText('calendar.noMoreCalendarsFor(name=Test User)')
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const addButton = screen.getByRole("button", { name: /add/i });
|
||||
const addButton = screen.getByRole('button', { name: /add/i })
|
||||
await act(async () => {
|
||||
fireEvent.click(addButton);
|
||||
});
|
||||
fireEvent.click(addButton)
|
||||
})
|
||||
|
||||
expect(addSharedCalendarSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(addSharedCalendarSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("displays message when user has no publicly available calendars", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser]);
|
||||
mockedGetCalendars.mockResolvedValueOnce({});
|
||||
it('displays message when user has no publicly available calendars', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser])
|
||||
mockedGetCalendars.mockResolvedValueOnce({})
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarSearch open={true} onClose={mockOnClose} />,
|
||||
preloadedState
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
const input = screen.getByRole('combobox')
|
||||
await act(async () => {
|
||||
userEvent.type(input, "Test");
|
||||
});
|
||||
userEvent.type(input, 'Test')
|
||||
})
|
||||
|
||||
const option = await screen.findByText("Test User");
|
||||
const option = await screen.findByText('Test User')
|
||||
await act(async () => {
|
||||
fireEvent.click(option);
|
||||
});
|
||||
fireEvent.click(option)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("calendar.noPublicCalendarsFor(name=Test User)")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
screen.getByText('calendar.noPublicCalendarsFor(name=Test User)')
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it("changes calendar color", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser]);
|
||||
it('changes calendar color', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser])
|
||||
mockedGetCalendars.mockResolvedValueOnce({
|
||||
_embedded: {
|
||||
"dav:calendar": [mockCalendar],
|
||||
},
|
||||
});
|
||||
'dav:calendar': [mockCalendar]
|
||||
}
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarSearch open={true} onClose={mockOnClose} />,
|
||||
preloadedState
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
const input = screen.getByRole('combobox')
|
||||
await act(async () => {
|
||||
userEvent.type(input, "Test");
|
||||
});
|
||||
userEvent.type(input, 'Test')
|
||||
})
|
||||
|
||||
const option = await screen.findByText("Test User");
|
||||
const option = await screen.findByText('Test User')
|
||||
await act(async () => {
|
||||
fireEvent.click(option);
|
||||
});
|
||||
fireEvent.click(option)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// ColorPicker would need to be interacted with based on its implementation
|
||||
// This is a placeholder for color change interaction
|
||||
const colorPicker = document.querySelector('[data-testid="color-picker"]');
|
||||
const colorPicker = document.querySelector('[data-testid="color-picker"]')
|
||||
if (colorPicker) {
|
||||
await act(async () => {
|
||||
fireEvent.click(colorPicker);
|
||||
});
|
||||
fireEvent.click(colorPicker)
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it("handles multiple calendars from the same user", async () => {
|
||||
it('handles multiple calendars from the same user', async () => {
|
||||
const secondCalendar = {
|
||||
"dav:name": "Second Calendar",
|
||||
"apple:color": "#00FF00",
|
||||
'dav:name': 'Second Calendar',
|
||||
'apple:color': '#00FF00',
|
||||
_links: {
|
||||
self: {
|
||||
href: "/calendars/user123/cal2.json",
|
||||
},
|
||||
},
|
||||
};
|
||||
href: '/calendars/user123/cal2.json'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser]);
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser])
|
||||
mockedGetCalendars.mockResolvedValueOnce({
|
||||
_embedded: {
|
||||
"dav:calendar": [mockCalendar, secondCalendar],
|
||||
},
|
||||
});
|
||||
'dav:calendar': [mockCalendar, secondCalendar]
|
||||
}
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarSearch open={true} onClose={mockOnClose} />,
|
||||
preloadedState
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
const input = screen.getByRole('combobox')
|
||||
await act(async () => {
|
||||
userEvent.type(input, "Test");
|
||||
});
|
||||
userEvent.type(input, 'Test')
|
||||
})
|
||||
|
||||
const option = await screen.findByText("Test User");
|
||||
const option = await screen.findByText('Test User')
|
||||
await act(async () => {
|
||||
fireEvent.click(option);
|
||||
});
|
||||
fireEvent.click(option)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Second Calendar/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Second Calendar/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it("does not call addSharedCalendarAsync when no calendars are selected", async () => {
|
||||
it('does not call addSharedCalendarAsync when no calendars are selected', async () => {
|
||||
const addSharedCalendarSpy = jest
|
||||
.spyOn(CalendarSlice, "addSharedCalendarAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
});
|
||||
.spyOn(CalendarSlice, 'addSharedCalendarAsync')
|
||||
.mockImplementation(payload => {
|
||||
return () => Promise.resolve(payload) as any
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarSearch open={true} onClose={mockOnClose} />,
|
||||
preloadedState
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
const addButton = screen.getByRole("button", { name: /add/i });
|
||||
const addButton = screen.getByRole('button', { name: /add/i })
|
||||
await act(async () => {
|
||||
fireEvent.click(addButton);
|
||||
});
|
||||
fireEvent.click(addButton)
|
||||
})
|
||||
|
||||
expect(addSharedCalendarSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
it("BUGFIX : handles calendar with no apple:color", async () => {
|
||||
expect(addSharedCalendarSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
it('BUGFIX : handles calendar with no apple:color', async () => {
|
||||
const addSharedCalendarSpy = jest
|
||||
.spyOn(CalendarSlice, "addSharedCalendarAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
});
|
||||
.spyOn(CalendarSlice, 'addSharedCalendarAsync')
|
||||
.mockImplementation(payload => {
|
||||
return () => Promise.resolve(payload) as any
|
||||
})
|
||||
const mockCalendarNoColor = {
|
||||
"dav:name": "Test Calendar",
|
||||
'dav:name': 'Test Calendar',
|
||||
_links: {
|
||||
self: {
|
||||
href: "/calendars/user123/cal2.json",
|
||||
},
|
||||
},
|
||||
};
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser]);
|
||||
href: '/calendars/user123/cal2.json'
|
||||
}
|
||||
}
|
||||
}
|
||||
mockedSearchUsers.mockResolvedValueOnce([mockUser])
|
||||
mockedGetCalendars.mockResolvedValueOnce({
|
||||
_embedded: {
|
||||
"dav:calendar": [mockCalendarNoColor],
|
||||
},
|
||||
});
|
||||
'dav:calendar': [mockCalendarNoColor]
|
||||
}
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarSearch open={true} onClose={mockOnClose} />,
|
||||
preloadedState
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
const input = screen.getByRole('combobox')
|
||||
await act(async () => {
|
||||
userEvent.type(input, "Test");
|
||||
});
|
||||
userEvent.type(input, 'Test')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedSearchUsers).toHaveBeenCalledWith("Test", expect.anything());
|
||||
});
|
||||
expect(mockedSearchUsers).toHaveBeenCalledWith('Test', expect.anything())
|
||||
})
|
||||
|
||||
const option = await screen.findByText("Test User");
|
||||
const option = await screen.findByText('Test User')
|
||||
await act(async () => {
|
||||
fireEvent.click(option);
|
||||
});
|
||||
fireEvent.click(option)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedGetCalendars).toHaveBeenCalledWith(
|
||||
"user123",
|
||||
"sharedPublic=true&"
|
||||
);
|
||||
});
|
||||
'user123',
|
||||
'sharedPublic=true&'
|
||||
)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument();
|
||||
expect(screen.getByText("user@example.com")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(/Test Calendar/i)).toBeInTheDocument()
|
||||
expect(screen.getByText('user@example.com')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const addButton = screen.getByRole("button", { name: /add/i });
|
||||
const addButton = screen.getByRole('button', { name: /add/i })
|
||||
await act(async () => {
|
||||
fireEvent.click(addButton);
|
||||
});
|
||||
fireEvent.click(addButton)
|
||||
})
|
||||
await waitFor(() =>
|
||||
expect(addSharedCalendarSpy).toHaveBeenCalledWith({
|
||||
cal: {
|
||||
cal: {
|
||||
_links: { self: { href: "/calendars/user123/cal2.json" } },
|
||||
"dav:name": "Test Calendar",
|
||||
_links: { self: { href: '/calendars/user123/cal2.json' } },
|
||||
'dav:name': 'Test Calendar'
|
||||
},
|
||||
color: { dark: "#329655", light: "#D0ECDA" },
|
||||
color: { dark: '#329655', light: '#D0ECDA' },
|
||||
owner: {
|
||||
avatarUrl: "https://example.com/avatar.jpg",
|
||||
displayName: "Test User",
|
||||
email: "user@example.com",
|
||||
openpaasId: "user123",
|
||||
},
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
displayName: 'Test User',
|
||||
email: 'user@example.com',
|
||||
openpaasId: 'user123'
|
||||
}
|
||||
},
|
||||
calId: expect.any(String),
|
||||
userId: "user1",
|
||||
userId: 'user1'
|
||||
})
|
||||
);
|
||||
)
|
||||
|
||||
expect(mockOnClose).toHaveBeenCalledWith(
|
||||
expect.arrayContaining(["user123/cal2"])
|
||||
);
|
||||
});
|
||||
});
|
||||
expect.arrayContaining(['user123/cal2'])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,188 +1,188 @@
|
||||
import CalendarResources from "@/components/Calendar/CalendarResources";
|
||||
import { getCalendars } from "@/features/Calendars/CalendarApi";
|
||||
import { addCalendarResourceAsync } from "@/features/Calendars/api/addCalendarResourceAsync";
|
||||
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
import CalendarResources from '@/components/Calendar/CalendarResources'
|
||||
import { getCalendars } from '@/features/Calendars/CalendarApi'
|
||||
import { addCalendarResourceAsync } from '@/features/Calendars/api/addCalendarResourceAsync'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
jest.mock("@/features/Calendars/CalendarApi");
|
||||
jest.mock("@/features/Calendars/api/addCalendarResourceAsync");
|
||||
jest.mock("@/components/Attendees/ResourceSearch", () => ({
|
||||
jest.mock('@/features/Calendars/CalendarApi')
|
||||
jest.mock('@/features/Calendars/api/addCalendarResourceAsync')
|
||||
jest.mock('@/components/Attendees/ResourceSearch', () => ({
|
||||
ResourceSearch: ({
|
||||
onChange,
|
||||
onChange
|
||||
}: {
|
||||
onChange: (
|
||||
event: null,
|
||||
value: { displayName: string; openpaasId: string }[]
|
||||
) => void;
|
||||
) => void
|
||||
}) => (
|
||||
<div data-testid="resource-search">
|
||||
<button
|
||||
data-testid="mock-resource-search-select"
|
||||
onClick={() =>
|
||||
onChange(null, [
|
||||
{ displayName: "Room A", openpaasId: "room-a-id" },
|
||||
{ displayName: "Room B", openpaasId: "room-b-id" },
|
||||
{ displayName: 'Room A', openpaasId: 'room-a-id' },
|
||||
{ displayName: 'Room B', openpaasId: 'room-b-id' }
|
||||
])
|
||||
}
|
||||
>
|
||||
Select Resources
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
)
|
||||
}))
|
||||
|
||||
const mockedGetCalendars = getCalendars as jest.Mock;
|
||||
const mockedGetCalendars = getCalendars as jest.Mock
|
||||
const mockedAddCalendarResourceAsync =
|
||||
addCalendarResourceAsync as unknown as jest.Mock;
|
||||
addCalendarResourceAsync as unknown as jest.Mock
|
||||
|
||||
describe("CalendarResources", () => {
|
||||
describe('CalendarResources', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
const baseUser = {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "mockSid",
|
||||
openpaasId: "user1",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: 'user1'
|
||||
},
|
||||
tokens: { accessToken: "token" },
|
||||
};
|
||||
tokens: { accessToken: 'token' }
|
||||
}
|
||||
|
||||
const setup = (isOpen = true) => {
|
||||
const onClose = jest.fn();
|
||||
const onClose = jest.fn()
|
||||
renderWithProviders(<CalendarResources onClose={onClose} open={isOpen} />, {
|
||||
user: baseUser,
|
||||
});
|
||||
return { onClose };
|
||||
};
|
||||
user: baseUser
|
||||
})
|
||||
return { onClose }
|
||||
}
|
||||
|
||||
it("renders correctly and closes on cancel", () => {
|
||||
const { onClose } = setup();
|
||||
it('renders correctly and closes on cancel', () => {
|
||||
const { onClose } = setup()
|
||||
|
||||
expect(screen.getByText("calendar.browseResources")).toBeInTheDocument();
|
||||
expect(screen.getByText('calendar.browseResources')).toBeInTheDocument()
|
||||
|
||||
const cancelButton = screen.getByRole("button", {
|
||||
name: "common.cancel",
|
||||
});
|
||||
fireEvent.click(cancelButton);
|
||||
const cancelButton = screen.getByRole('button', {
|
||||
name: 'common.cancel'
|
||||
})
|
||||
fireEvent.click(cancelButton)
|
||||
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("does not render when isOpen is false", () => {
|
||||
setup(false);
|
||||
it('does not render when isOpen is false', () => {
|
||||
setup(false)
|
||||
expect(
|
||||
screen.queryByText("calendar.browseResources")
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
screen.queryByText('calendar.browseResources')
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("fetches resource calendars and adds them using Promise.allSettled logic", async () => {
|
||||
it('fetches resource calendars and adds them using Promise.allSettled logic', async () => {
|
||||
const mockCalendarDataA = {
|
||||
_embedded: {
|
||||
"dav:calendar": [
|
||||
'dav:calendar': [
|
||||
{
|
||||
_links: { self: { href: "/calendars/room-a-id/cal-a.json" } },
|
||||
"dav:name": "Room A Calendar",
|
||||
"caldav:description": "Main room A calendar",
|
||||
color: "red",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
_links: { self: { href: '/calendars/room-a-id/cal-a.json' } },
|
||||
'dav:name': 'Room A Calendar',
|
||||
'caldav:description': 'Main room A calendar',
|
||||
color: 'red'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
mockedGetCalendars.mockImplementation((userId) => {
|
||||
if (userId === "room-a-id") return Promise.resolve(mockCalendarDataA);
|
||||
mockedGetCalendars.mockImplementation(userId => {
|
||||
if (userId === 'room-a-id') return Promise.resolve(mockCalendarDataA)
|
||||
// Simulate failure for room-b
|
||||
if (userId === "room-b-id")
|
||||
return Promise.reject(new Error("Failed fetching Room B"));
|
||||
return Promise.resolve([]);
|
||||
});
|
||||
if (userId === 'room-b-id')
|
||||
return Promise.reject(new Error('Failed fetching Room B'))
|
||||
return Promise.resolve([])
|
||||
})
|
||||
|
||||
const mockDispatchResult = {
|
||||
unwrap: () => Promise.resolve({ type: "success" }),
|
||||
};
|
||||
mockedAddCalendarResourceAsync.mockReturnValue(() => mockDispatchResult);
|
||||
unwrap: () => Promise.resolve({ type: 'success' })
|
||||
}
|
||||
mockedAddCalendarResourceAsync.mockReturnValue(() => mockDispatchResult)
|
||||
|
||||
const { onClose } = setup();
|
||||
const { onClose } = setup()
|
||||
|
||||
// Trigger resource selection
|
||||
const mockSelectBtn = screen.getByTestId("mock-resource-search-select");
|
||||
const mockSelectBtn = screen.getByTestId('mock-resource-search-select')
|
||||
await act(async () => {
|
||||
fireEvent.click(mockSelectBtn);
|
||||
});
|
||||
fireEvent.click(mockSelectBtn)
|
||||
})
|
||||
|
||||
// After selection, the button should be enabled and say Add
|
||||
const addButton = await screen.findByRole("button", {
|
||||
name: "actions.add",
|
||||
});
|
||||
expect(addButton).not.toBeDisabled();
|
||||
const addButton = await screen.findByRole('button', {
|
||||
name: 'actions.add'
|
||||
})
|
||||
expect(addButton).not.toBeDisabled()
|
||||
|
||||
// Click add to trigger addCalendarResourceAsync
|
||||
await act(async () => {
|
||||
fireEvent.click(addButton);
|
||||
});
|
||||
fireEvent.click(addButton)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
// It should only have been called for Room A because Room B failed (Promise.allSettled)
|
||||
expect(mockedAddCalendarResourceAsync).toHaveBeenCalledTimes(1);
|
||||
expect(mockedAddCalendarResourceAsync).toHaveBeenCalledTimes(1)
|
||||
|
||||
const payload = mockedAddCalendarResourceAsync.mock.calls[0][0];
|
||||
const payload = mockedAddCalendarResourceAsync.mock.calls[0][0]
|
||||
expect(payload).toEqual(
|
||||
expect.objectContaining({
|
||||
userId: "user1",
|
||||
userId: 'user1',
|
||||
calId: expect.any(String),
|
||||
cal: expect.objectContaining({
|
||||
cal: mockCalendarDataA._embedded["dav:calendar"][0],
|
||||
}),
|
||||
cal: mockCalendarDataA._embedded['dav:calendar'][0]
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("adds existing user details fallback logic within submit handler", async () => {
|
||||
it('adds existing user details fallback logic within submit handler', async () => {
|
||||
const mockCalendarDataA = {
|
||||
_embedded: {
|
||||
"dav:calendar": [
|
||||
'dav:calendar': [
|
||||
{
|
||||
_links: { self: { href: "/calendars/room-a-id/cal-a.json" } },
|
||||
"dav:name": "Room A Calendar",
|
||||
"caldav:description": "Main room A calendar",
|
||||
color: "red",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
_links: { self: { href: '/calendars/room-a-id/cal-a.json' } },
|
||||
'dav:name': 'Room A Calendar',
|
||||
'caldav:description': 'Main room A calendar',
|
||||
color: 'red'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
mockedGetCalendars.mockResolvedValueOnce(mockCalendarDataA);
|
||||
mockedGetCalendars.mockResolvedValueOnce([]); // Mock array of size 2 (for both A and B but second resolves empty)
|
||||
mockedGetCalendars.mockResolvedValueOnce(mockCalendarDataA)
|
||||
mockedGetCalendars.mockResolvedValueOnce([]) // Mock array of size 2 (for both A and B but second resolves empty)
|
||||
|
||||
// Simulate successful API response
|
||||
const mockDispatchResult = {
|
||||
unwrap: () => Promise.resolve(),
|
||||
};
|
||||
mockedAddCalendarResourceAsync.mockReturnValue(() => mockDispatchResult);
|
||||
unwrap: () => Promise.resolve()
|
||||
}
|
||||
mockedAddCalendarResourceAsync.mockReturnValue(() => mockDispatchResult)
|
||||
|
||||
const { onClose } = setup();
|
||||
const { onClose } = setup()
|
||||
|
||||
// Trigger resource selection
|
||||
const mockSelectBtn = screen.getByTestId("mock-resource-search-select");
|
||||
const mockSelectBtn = screen.getByTestId('mock-resource-search-select')
|
||||
await act(async () => {
|
||||
fireEvent.click(mockSelectBtn);
|
||||
});
|
||||
fireEvent.click(mockSelectBtn)
|
||||
})
|
||||
|
||||
const addButton = await screen.findByRole("button", {
|
||||
name: "actions.add",
|
||||
});
|
||||
const addButton = await screen.findByRole('button', {
|
||||
name: 'actions.add'
|
||||
})
|
||||
await act(async () => {
|
||||
fireEvent.click(addButton);
|
||||
});
|
||||
fireEvent.click(addButton)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
import CalendarSelection from "@/components/Calendar/CalendarSelection";
|
||||
import * as calendarThunks from "@/features/Calendars/services";
|
||||
import "@testing-library/jest-dom";
|
||||
import { cleanup, fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
import CalendarSelection from '@/components/Calendar/CalendarSelection'
|
||||
import * as calendarThunks from '@/features/Calendars/services'
|
||||
import '@testing-library/jest-dom'
|
||||
import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
describe("CalendarSelection", () => {
|
||||
describe('CalendarSelection', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
localStorage.clear()
|
||||
})
|
||||
const baseUser = {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "mockSid",
|
||||
openpaasId: "user1",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: 'user1'
|
||||
},
|
||||
tokens: { accessToken: "token" },
|
||||
};
|
||||
tokens: { accessToken: 'token' }
|
||||
}
|
||||
|
||||
const calendarsMock = {
|
||||
"user1/cal1": {
|
||||
name: "Calendar personal",
|
||||
id: "user1/cal1",
|
||||
color: "#FF0000",
|
||||
owner: { emails: ["alice@example.com"], lastname: "alice" },
|
||||
'user1/cal1': {
|
||||
name: 'Calendar personal',
|
||||
id: 'user1/cal1',
|
||||
color: '#FF0000',
|
||||
owner: { emails: ['alice@example.com'], lastname: 'alice' }
|
||||
},
|
||||
"user2/cal1": {
|
||||
name: "Calendar delegated",
|
||||
'user2/cal1': {
|
||||
name: 'Calendar delegated',
|
||||
delegated: true,
|
||||
id: "user2/cal1",
|
||||
color: "#00FF00",
|
||||
id: 'user2/cal1',
|
||||
color: '#00FF00',
|
||||
owner: {
|
||||
firstname: "Bob",
|
||||
lastname: "Builder",
|
||||
emails: ["bob@example.com"],
|
||||
},
|
||||
firstname: 'Bob',
|
||||
lastname: 'Builder',
|
||||
emails: ['bob@example.com']
|
||||
}
|
||||
},
|
||||
"user3/cal1": {
|
||||
name: "Calendar shared",
|
||||
id: "user3/cal1",
|
||||
color: "#0000FF",
|
||||
'user3/cal1': {
|
||||
name: 'Calendar shared',
|
||||
id: 'user3/cal1',
|
||||
color: '#0000FF',
|
||||
owner: {
|
||||
firstname: "Charlie",
|
||||
lastname: "Chaplin",
|
||||
emails: ["charlie@example.com"],
|
||||
},
|
||||
},
|
||||
};
|
||||
firstname: 'Charlie',
|
||||
lastname: 'Chaplin',
|
||||
emails: ['charlie@example.com']
|
||||
}
|
||||
}
|
||||
}
|
||||
beforeAll(() => {
|
||||
jest.clearAllMocks();
|
||||
cleanup();
|
||||
});
|
||||
it("renders personal, delegated and calendar.other", () => {
|
||||
jest.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
it('renders personal, delegated and calendar.other', () => {
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
selectedCalendars={["user1/cal1"]}
|
||||
selectedCalendars={['user1/cal1']}
|
||||
setSelectedCalendars={jest.fn()}
|
||||
/>,
|
||||
{
|
||||
user: baseUser,
|
||||
calendars: { list: calendarsMock, pending: false },
|
||||
calendars: { list: calendarsMock, pending: false }
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("calendar.personal")).toBeInTheDocument();
|
||||
expect(screen.getByText("calendar.delegated")).toBeInTheDocument();
|
||||
expect(screen.getByText("calendar.other")).toBeInTheDocument();
|
||||
expect(screen.getByText('calendar.personal')).toBeInTheDocument()
|
||||
expect(screen.getByText('calendar.delegated')).toBeInTheDocument()
|
||||
expect(screen.getByText('calendar.other')).toBeInTheDocument()
|
||||
|
||||
expect(screen.getByLabelText("Calendar personal")).toBeChecked();
|
||||
expect(screen.getByLabelText("Calendar delegated")).not.toBeChecked();
|
||||
expect(screen.getByLabelText("Calendar shared")).not.toBeChecked();
|
||||
});
|
||||
expect(screen.getByLabelText('Calendar personal')).toBeChecked()
|
||||
expect(screen.getByLabelText('Calendar delegated')).not.toBeChecked()
|
||||
expect(screen.getByLabelText('Calendar shared')).not.toBeChecked()
|
||||
})
|
||||
|
||||
it("toggles a calendar selection on click", () => {
|
||||
const setSelectedCalendars = jest.fn();
|
||||
it('toggles a calendar selection on click', () => {
|
||||
const setSelectedCalendars = jest.fn()
|
||||
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
@@ -83,41 +83,41 @@ describe("CalendarSelection", () => {
|
||||
/>,
|
||||
{
|
||||
user: baseUser,
|
||||
calendars: { list: calendarsMock, pending: false },
|
||||
calendars: { list: calendarsMock, pending: false }
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const checkbox = screen.getByLabelText("Calendar personal");
|
||||
fireEvent.click(checkbox);
|
||||
const checkbox = screen.getByLabelText('Calendar personal')
|
||||
fireEvent.click(checkbox)
|
||||
|
||||
expect(setSelectedCalendars).toHaveBeenCalledWith(expect.any(Function));
|
||||
expect(setSelectedCalendars).toHaveBeenCalledWith(expect.any(Function))
|
||||
|
||||
const updater = setSelectedCalendars.mock.calls[0][0];
|
||||
expect(updater([])).toEqual(["user1/cal1"]);
|
||||
});
|
||||
const updater = setSelectedCalendars.mock.calls[0][0]
|
||||
expect(updater([])).toEqual(['user1/cal1'])
|
||||
})
|
||||
|
||||
it("removes calendar from selection if already selected", () => {
|
||||
const setSelectedCalendars = jest.fn();
|
||||
it('removes calendar from selection if already selected', () => {
|
||||
const setSelectedCalendars = jest.fn()
|
||||
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
selectedCalendars={["user1/cal1"]}
|
||||
selectedCalendars={['user1/cal1']}
|
||||
setSelectedCalendars={setSelectedCalendars}
|
||||
/>,
|
||||
{
|
||||
user: baseUser,
|
||||
calendars: { list: calendarsMock, pending: false },
|
||||
calendars: { list: calendarsMock, pending: false }
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const checkbox = screen.getByLabelText("Calendar personal");
|
||||
fireEvent.click(checkbox);
|
||||
const checkbox = screen.getByLabelText('Calendar personal')
|
||||
fireEvent.click(checkbox)
|
||||
|
||||
const updater = setSelectedCalendars.mock.calls[0][0];
|
||||
expect(updater(["user1/cal1"])).toEqual([]);
|
||||
});
|
||||
const updater = setSelectedCalendars.mock.calls[0][0]
|
||||
expect(updater(['user1/cal1'])).toEqual([])
|
||||
})
|
||||
|
||||
it("opens CalendarPopover modal when personal Add button is clicked", async () => {
|
||||
it('opens CalendarPopover modal when personal Add button is clicked', async () => {
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
selectedCalendars={[]}
|
||||
@@ -125,26 +125,26 @@ describe("CalendarSelection", () => {
|
||||
/>,
|
||||
{
|
||||
user: baseUser,
|
||||
calendars: { list: calendarsMock, pending: false },
|
||||
calendars: { list: calendarsMock, pending: false }
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const addButtons = screen.getAllByRole("button");
|
||||
fireEvent.click(addButtons[1]);
|
||||
const addButtons = screen.getAllByRole('button')
|
||||
fireEvent.click(addButtons[1])
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText("calendarPopover.tabs.addNew")
|
||||
screen.getByText('calendarPopover.tabs.addNew')
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("Navigates to deletion dialog and deletes personal cal", async () => {
|
||||
it('Navigates to deletion dialog and deletes personal cal', async () => {
|
||||
const spy = jest
|
||||
.spyOn(calendarThunks, "removeCalendarAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
});
|
||||
.spyOn(calendarThunks, 'removeCalendarAsync')
|
||||
.mockImplementation(payload => {
|
||||
return () => Promise.resolve(payload) as any
|
||||
})
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
selectedCalendars={[]}
|
||||
@@ -152,31 +152,31 @@ describe("CalendarSelection", () => {
|
||||
/>,
|
||||
{
|
||||
user: baseUser,
|
||||
calendars: { list: calendarsMock, pending: false },
|
||||
calendars: { list: calendarsMock, pending: false }
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const addButtons = screen.getAllByTestId("MoreHorizIcon");
|
||||
fireEvent.click(addButtons[0]);
|
||||
const addButtons = screen.getAllByTestId('MoreHorizIcon')
|
||||
fireEvent.click(addButtons[0])
|
||||
|
||||
userEvent.click(screen.getByText(/delete/i));
|
||||
userEvent.click(screen.getByText(/delete/i))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText("calendar.delete.title(name=Calendar personal)")
|
||||
screen.getByText('calendar.delete.title(name=Calendar personal)')
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /delete/i }));
|
||||
)
|
||||
fireEvent.click(screen.getByRole('button', { name: /delete/i }))
|
||||
|
||||
await waitFor(() => expect(spy).toHaveBeenCalled());
|
||||
});
|
||||
await waitFor(() => expect(spy).toHaveBeenCalled())
|
||||
})
|
||||
|
||||
it("Navigates to deletion dialog and deletes other cal", async () => {
|
||||
it('Navigates to deletion dialog and deletes other cal', async () => {
|
||||
const spy = jest
|
||||
.spyOn(calendarThunks, "removeCalendarAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
});
|
||||
.spyOn(calendarThunks, 'removeCalendarAsync')
|
||||
.mockImplementation(payload => {
|
||||
return () => Promise.resolve(payload) as any
|
||||
})
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
selectedCalendars={[]}
|
||||
@@ -184,26 +184,26 @@ describe("CalendarSelection", () => {
|
||||
/>,
|
||||
{
|
||||
user: baseUser,
|
||||
calendars: { list: calendarsMock, pending: false },
|
||||
calendars: { list: calendarsMock, pending: false }
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const addButtons = screen.getAllByTestId("MoreHorizIcon");
|
||||
fireEvent.click(addButtons[1]);
|
||||
const addButtons = screen.getAllByTestId('MoreHorizIcon')
|
||||
fireEvent.click(addButtons[1])
|
||||
|
||||
userEvent.click(screen.getByText(/remove/i));
|
||||
userEvent.click(screen.getByText(/remove/i))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText("calendar.delete.title(name=Calendar delegated)")
|
||||
screen.getByText('calendar.delete.title(name=Calendar delegated)')
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /remove/i }));
|
||||
)
|
||||
fireEvent.click(screen.getByRole('button', { name: /remove/i }))
|
||||
|
||||
await waitFor(() => expect(spy).toHaveBeenCalled());
|
||||
});
|
||||
await waitFor(() => expect(spy).toHaveBeenCalled())
|
||||
})
|
||||
|
||||
it("opens CalendarSearch modal when Other Add button is clicked", () => {
|
||||
it('opens CalendarSearch modal when Other Add button is clicked', () => {
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
selectedCalendars={[]}
|
||||
@@ -211,19 +211,19 @@ describe("CalendarSelection", () => {
|
||||
/>,
|
||||
{
|
||||
user: baseUser,
|
||||
calendars: { list: calendarsMock, pending: false },
|
||||
calendars: { list: calendarsMock, pending: false }
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const addButtons = screen.getAllByTestId("AddIcon");
|
||||
fireEvent.click(addButtons[1]); // seccond Add button (other)
|
||||
const addButtons = screen.getAllByTestId('AddIcon')
|
||||
fireEvent.click(addButtons[1]) // seccond Add button (other)
|
||||
|
||||
expect(
|
||||
screen.getByText("calendar.browseOtherCalendars")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
screen.getByText('calendar.browseOtherCalendars')
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("when only calendar.personal are in the state, only calendar.personal and the title for other to be added are shown", () => {
|
||||
it('when only calendar.personal are in the state, only calendar.personal and the title for other to be added are shown', () => {
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
selectedCalendars={[]}
|
||||
@@ -233,19 +233,19 @@ describe("CalendarSelection", () => {
|
||||
user: baseUser,
|
||||
calendars: {
|
||||
list: {
|
||||
"user1/cal1": calendarsMock["user1/cal1"],
|
||||
'user1/cal1': calendarsMock['user1/cal1']
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
pending: false
|
||||
}
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("calendar.personal")).toBeInTheDocument();
|
||||
expect(screen.queryByText("calendar.delegated")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("calendar.other")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('calendar.personal')).toBeInTheDocument()
|
||||
expect(screen.queryByText('calendar.delegated')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('calendar.other')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders nothing when no calendars are present", () => {
|
||||
it('renders nothing when no calendars are present', () => {
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
selectedCalendars={[]}
|
||||
@@ -253,14 +253,14 @@ describe("CalendarSelection", () => {
|
||||
/>,
|
||||
{
|
||||
user: baseUser,
|
||||
calendars: { list: {}, pending: false },
|
||||
calendars: { list: {}, pending: false }
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.queryByLabelText(/Calendar/)).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByLabelText(/Calendar/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("expands and collapses accordions when clicked", () => {
|
||||
it('expands and collapses accordions when clicked', () => {
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
selectedCalendars={[]}
|
||||
@@ -268,31 +268,31 @@ describe("CalendarSelection", () => {
|
||||
/>,
|
||||
{
|
||||
user: baseUser,
|
||||
calendars: { list: calendarsMock, pending: false },
|
||||
calendars: { list: calendarsMock, pending: false }
|
||||
}
|
||||
);
|
||||
)
|
||||
const delegatedAccordionSummary = screen
|
||||
.getByText("calendar.delegated")
|
||||
.closest(".MuiAccordionSummary-root");
|
||||
.getByText('calendar.delegated')
|
||||
.closest('.MuiAccordionSummary-root')
|
||||
|
||||
fireEvent.click(delegatedAccordionSummary!);
|
||||
expect(delegatedAccordionSummary).toHaveAttribute("aria-expanded", "false");
|
||||
fireEvent.click(delegatedAccordionSummary!)
|
||||
expect(delegatedAccordionSummary).toHaveAttribute('aria-expanded', 'false')
|
||||
|
||||
fireEvent.click(delegatedAccordionSummary!);
|
||||
expect(delegatedAccordionSummary).toHaveAttribute("aria-expanded", "true");
|
||||
});
|
||||
fireEvent.click(delegatedAccordionSummary!)
|
||||
expect(delegatedAccordionSummary).toHaveAttribute('aria-expanded', 'true')
|
||||
})
|
||||
|
||||
it("renders owner name caption for non-personal, non-default calendars", () => {
|
||||
it('renders owner name caption for non-personal, non-default calendars', () => {
|
||||
renderWithProviders(
|
||||
<CalendarSelection
|
||||
selectedCalendars={[]}
|
||||
setSelectedCalendars={jest.fn()}
|
||||
/>,
|
||||
{ user: baseUser, calendars: { list: calendarsMock, pending: false } }
|
||||
);
|
||||
expect(screen.getByText("Bob Builder")).toBeInTheDocument();
|
||||
expect(screen.getByText("Charlie Chaplin")).toBeInTheDocument();
|
||||
)
|
||||
expect(screen.getByText('Bob Builder')).toBeInTheDocument()
|
||||
expect(screen.getByText('Charlie Chaplin')).toBeInTheDocument()
|
||||
// personal calendar should NOT show a caption
|
||||
expect(screen.queryByText("alice")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
expect(screen.queryByText('alice')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,343 +1,331 @@
|
||||
import {
|
||||
DateTimeFields,
|
||||
DateTimeFieldsProps,
|
||||
} from "@/components/Event/components/DateTimeFields";
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
DateTimeFieldsProps
|
||||
} from '@/components/Event/components/DateTimeFields'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
|
||||
jest.mock("twake-i18n", () => ({
|
||||
jest.mock('twake-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key: string) => key,
|
||||
lang: "en",
|
||||
}),
|
||||
}));
|
||||
lang: 'en'
|
||||
})
|
||||
}))
|
||||
|
||||
describe("DateTimeFields", () => {
|
||||
describe('DateTimeFields', () => {
|
||||
const mockHandlers = {
|
||||
onStartDateChange: jest.fn(),
|
||||
onStartTimeChange: jest.fn(),
|
||||
onEndDateChange: jest.fn(),
|
||||
onEndTimeChange: jest.fn(),
|
||||
onToggleEndDate: jest.fn(),
|
||||
};
|
||||
onToggleEndDate: jest.fn()
|
||||
}
|
||||
|
||||
const defaultProps: DateTimeFieldsProps = {
|
||||
startDate: "2025-07-18",
|
||||
startTime: "09:00",
|
||||
endDate: "2025-07-18",
|
||||
endTime: "10:00",
|
||||
startDate: '2025-07-18',
|
||||
startTime: '09:00',
|
||||
endDate: '2025-07-18',
|
||||
endTime: '10:00',
|
||||
allday: false,
|
||||
showMore: true,
|
||||
hasEndDateChanged: false,
|
||||
showEndDate: false,
|
||||
validation: {
|
||||
errors: {
|
||||
dateTime: "",
|
||||
},
|
||||
dateTime: ''
|
||||
}
|
||||
},
|
||||
...mockHandlers,
|
||||
};
|
||||
...mockHandlers
|
||||
}
|
||||
|
||||
const renderField = async (props: Partial<DateTimeFieldsProps> = {}) => {
|
||||
await act(async () =>
|
||||
render(<DateTimeFields {...defaultProps} {...props} />)
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("moves END forward when START moves after END (normal mode)", async () => {
|
||||
it('moves END forward when START moves after END (normal mode)', async () => {
|
||||
await renderField({
|
||||
startDate: "2025-01-01",
|
||||
startTime: "10:00",
|
||||
endDate: "2025-01-01",
|
||||
endTime: "11:00",
|
||||
showMore: true,
|
||||
});
|
||||
startDate: '2025-01-01',
|
||||
startTime: '10:00',
|
||||
endDate: '2025-01-01',
|
||||
endTime: '11:00',
|
||||
showMore: true
|
||||
})
|
||||
|
||||
const startTimeInput = screen.getByTestId("start-time-input");
|
||||
const startTimeInput = screen.getByTestId('start-time-input')
|
||||
|
||||
fireEvent.change(startTimeInput, { target: { value: "12:00" } });
|
||||
fireEvent.blur(startTimeInput);
|
||||
fireEvent.change(startTimeInput, { target: { value: '12:00' } })
|
||||
fireEvent.blur(startTimeInput)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onStartTimeChange).toHaveBeenCalledWith("12:00")
|
||||
);
|
||||
expect(mockHandlers.onStartTimeChange).toHaveBeenCalledWith('12:00')
|
||||
)
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onEndTimeChange).toHaveBeenCalledWith("13:00")
|
||||
);
|
||||
});
|
||||
expect(mockHandlers.onEndTimeChange).toHaveBeenCalledWith('13:00')
|
||||
)
|
||||
})
|
||||
|
||||
it("moves END forward by full duration when START date jumps after END date", async () => {
|
||||
it('moves END forward by full duration when START date jumps after END date', async () => {
|
||||
await renderField({
|
||||
startDate: "2025-01-01",
|
||||
startTime: "09:00",
|
||||
endDate: "2025-01-01",
|
||||
endTime: "10:00",
|
||||
showMore: true,
|
||||
});
|
||||
startDate: '2025-01-01',
|
||||
startTime: '09:00',
|
||||
endDate: '2025-01-01',
|
||||
endTime: '10:00',
|
||||
showMore: true
|
||||
})
|
||||
|
||||
await userEvent.click(screen.getByTestId("start-date-input"));
|
||||
const dayButton = screen.getByRole("gridcell", { name: "3" });
|
||||
await userEvent.click(dayButton);
|
||||
await userEvent.click(screen.getByTestId('start-date-input'))
|
||||
const dayButton = screen.getByRole('gridcell', { name: '3' })
|
||||
await userEvent.click(dayButton)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onStartDateChange).toHaveBeenCalledWith("2025-01-03")
|
||||
);
|
||||
expect(mockHandlers.onStartDateChange).toHaveBeenCalledWith('2025-01-03')
|
||||
)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onEndDateChange).toHaveBeenCalledWith("2025-01-03")
|
||||
);
|
||||
});
|
||||
expect(mockHandlers.onEndDateChange).toHaveBeenCalledWith('2025-01-03')
|
||||
)
|
||||
})
|
||||
|
||||
it("does NOT move START backward when END moves before START (normal mode)", async () => {
|
||||
it('does NOT move START backward when END moves before START (normal mode)', async () => {
|
||||
await renderField({
|
||||
startDate: "2025-01-01",
|
||||
startTime: "10:00",
|
||||
endDate: "2025-01-01",
|
||||
endTime: "11:00",
|
||||
showMore: true,
|
||||
});
|
||||
startDate: '2025-01-01',
|
||||
startTime: '10:00',
|
||||
endDate: '2025-01-01',
|
||||
endTime: '11:00',
|
||||
showMore: true
|
||||
})
|
||||
|
||||
const endTimeInput = screen.getByTestId("end-time-input");
|
||||
const endTimeInput = screen.getByTestId('end-time-input')
|
||||
|
||||
fireEvent.change(endTimeInput, { target: { value: "08:00" } });
|
||||
fireEvent.blur(endTimeInput);
|
||||
fireEvent.change(endTimeInput, { target: { value: '08:00' } })
|
||||
fireEvent.blur(endTimeInput)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onEndTimeChange).toHaveBeenCalledWith("08:00")
|
||||
);
|
||||
expect(mockHandlers.onEndTimeChange).toHaveBeenCalledWith('08:00')
|
||||
)
|
||||
|
||||
// Start time should NOT be automatically adjusted when end time changes
|
||||
expect(mockHandlers.onStartTimeChange).not.toHaveBeenCalled();
|
||||
expect(mockHandlers.onStartDateChange).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(mockHandlers.onStartTimeChange).not.toHaveBeenCalled()
|
||||
expect(mockHandlers.onStartDateChange).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("moves START backward properly when END date jumps before START date", async () => {
|
||||
it('moves START backward properly when END date jumps before START date', async () => {
|
||||
await renderField({
|
||||
startDate: "2025-01-05",
|
||||
startTime: "09:00",
|
||||
endDate: "2025-01-05",
|
||||
endTime: "10:00",
|
||||
showMore: true,
|
||||
});
|
||||
startDate: '2025-01-05',
|
||||
startTime: '09:00',
|
||||
endDate: '2025-01-05',
|
||||
endTime: '10:00',
|
||||
showMore: true
|
||||
})
|
||||
|
||||
await userEvent.click(screen.getByTestId("end-date-input"));
|
||||
const dayButton = screen.getByRole("gridcell", { name: "3" });
|
||||
await userEvent.click(dayButton);
|
||||
await userEvent.click(screen.getByTestId('end-date-input'))
|
||||
const dayButton = screen.getByRole('gridcell', { name: '3' })
|
||||
await userEvent.click(dayButton)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onEndDateChange).toHaveBeenCalledWith("2025-01-03")
|
||||
);
|
||||
expect(mockHandlers.onEndDateChange).toHaveBeenCalledWith('2025-01-03')
|
||||
)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onStartDateChange).toHaveBeenCalledWith("2025-01-03")
|
||||
);
|
||||
});
|
||||
expect(mockHandlers.onStartDateChange).toHaveBeenCalledWith('2025-01-03')
|
||||
)
|
||||
})
|
||||
|
||||
it("pushes END forward in whole days for allday events", async () => {
|
||||
it('pushes END forward in whole days for allday events', async () => {
|
||||
await renderField({
|
||||
allday: true,
|
||||
startDate: "2025-02-01",
|
||||
endDate: "2025-02-03",
|
||||
showEndDate: true,
|
||||
});
|
||||
startDate: '2025-02-01',
|
||||
endDate: '2025-02-03',
|
||||
showEndDate: true
|
||||
})
|
||||
|
||||
await userEvent.click(screen.getByTestId("start-date-input"));
|
||||
const dayButton = screen.getByRole("gridcell", { name: "10" });
|
||||
await userEvent.click(dayButton);
|
||||
await userEvent.click(screen.getByTestId('start-date-input'))
|
||||
const dayButton = screen.getByRole('gridcell', { name: '10' })
|
||||
await userEvent.click(dayButton)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onStartDateChange).toHaveBeenCalledWith("2025-02-10")
|
||||
);
|
||||
expect(mockHandlers.onStartDateChange).toHaveBeenCalledWith('2025-02-10')
|
||||
)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onEndDateChange).toHaveBeenCalledWith("2025-02-12")
|
||||
);
|
||||
});
|
||||
expect(mockHandlers.onEndDateChange).toHaveBeenCalledWith('2025-02-12')
|
||||
)
|
||||
})
|
||||
|
||||
it("moves START backward in whole days for allday when END moves earlier", async () => {
|
||||
it('moves START backward in whole days for allday when END moves earlier', async () => {
|
||||
await renderField({
|
||||
allday: true,
|
||||
startDate: "2025-05-10",
|
||||
endDate: "2025-05-15",
|
||||
showEndDate: true,
|
||||
});
|
||||
startDate: '2025-05-10',
|
||||
endDate: '2025-05-15',
|
||||
showEndDate: true
|
||||
})
|
||||
|
||||
await userEvent.click(screen.getByTestId("end-date-input"));
|
||||
const dayButton = screen.getByRole("gridcell", { name: "1" });
|
||||
await userEvent.click(dayButton);
|
||||
await userEvent.click(screen.getByTestId('end-date-input'))
|
||||
const dayButton = screen.getByRole('gridcell', { name: '1' })
|
||||
await userEvent.click(dayButton)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onEndDateChange).toHaveBeenCalledWith("2025-05-01")
|
||||
);
|
||||
expect(mockHandlers.onEndDateChange).toHaveBeenCalledWith('2025-05-01')
|
||||
)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onStartDateChange).toHaveBeenCalledWith("2025-04-26")
|
||||
);
|
||||
});
|
||||
expect(mockHandlers.onStartDateChange).toHaveBeenCalledWith('2025-04-26')
|
||||
)
|
||||
})
|
||||
|
||||
it("does not call handlers if invalid (null) date value", async () => {
|
||||
it('does not call handlers if invalid (null) date value', async () => {
|
||||
await renderField({
|
||||
showMore: true,
|
||||
});
|
||||
showMore: true
|
||||
})
|
||||
|
||||
const startDateInput = screen.getByTestId("start-date-input");
|
||||
const startDateInput = screen.getByTestId('start-date-input')
|
||||
|
||||
fireEvent.change(startDateInput, { target: { value: "" } });
|
||||
fireEvent.change(startDateInput, { target: { value: '' } })
|
||||
|
||||
expect(mockHandlers.onStartDateChange).not.toHaveBeenCalled();
|
||||
expect(mockHandlers.onEndDateChange).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(mockHandlers.onStartDateChange).not.toHaveBeenCalled()
|
||||
expect(mockHandlers.onEndDateChange).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("shift to preserve original duration (normal case)", async () => {
|
||||
it('shift to preserve original duration (normal case)', async () => {
|
||||
await renderField({
|
||||
startDate: "2025-01-01",
|
||||
startTime: "09:00",
|
||||
endDate: "2025-01-01",
|
||||
endTime: "10:00",
|
||||
showMore: true,
|
||||
});
|
||||
startDate: '2025-01-01',
|
||||
startTime: '09:00',
|
||||
endDate: '2025-01-01',
|
||||
endTime: '10:00',
|
||||
showMore: true
|
||||
})
|
||||
|
||||
const startTimeInput = screen.getByTestId("start-time-input");
|
||||
const startTimeInput = screen.getByTestId('start-time-input')
|
||||
|
||||
fireEvent.change(startTimeInput, { target: { value: "09:30" } });
|
||||
fireEvent.blur(startTimeInput);
|
||||
fireEvent.change(startTimeInput, { target: { value: '09:30' } })
|
||||
fireEvent.blur(startTimeInput)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onStartTimeChange).toHaveBeenCalledWith("09:30")
|
||||
);
|
||||
expect(mockHandlers.onStartTimeChange).toHaveBeenCalledWith('09:30')
|
||||
)
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onEndTimeChange).toHaveBeenCalledWith("10:30")
|
||||
);
|
||||
});
|
||||
expect(mockHandlers.onEndTimeChange).toHaveBeenCalledWith('10:30')
|
||||
)
|
||||
})
|
||||
|
||||
it("preserves 1-hour duration across midnight when changing start time from 22:30 to 23:45", async () => {
|
||||
it('preserves 1-hour duration across midnight when changing start time from 22:30 to 23:45', async () => {
|
||||
await renderField({
|
||||
startDate: "2025-01-15",
|
||||
startTime: "22:30",
|
||||
endDate: "2025-01-15",
|
||||
endTime: "23:30",
|
||||
showMore: true,
|
||||
});
|
||||
startDate: '2025-01-15',
|
||||
startTime: '22:30',
|
||||
endDate: '2025-01-15',
|
||||
endTime: '23:30',
|
||||
showMore: true
|
||||
})
|
||||
|
||||
const startTimeInput = screen.getByTestId("start-time-input");
|
||||
const startTimeInput = screen.getByTestId('start-time-input')
|
||||
|
||||
// Change start time from 22:30 to 23:45
|
||||
fireEvent.change(startTimeInput, { target: { value: "23:45" } });
|
||||
fireEvent.blur(startTimeInput);
|
||||
fireEvent.change(startTimeInput, { target: { value: '23:45' } })
|
||||
fireEvent.blur(startTimeInput)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onStartTimeChange).toHaveBeenCalledWith("23:45")
|
||||
);
|
||||
expect(mockHandlers.onStartTimeChange).toHaveBeenCalledWith('23:45')
|
||||
)
|
||||
|
||||
// End date should move to the next day
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onEndDateChange).toHaveBeenCalledWith(
|
||||
"2025-01-16",
|
||||
"00:45"
|
||||
'2025-01-16',
|
||||
'00:45'
|
||||
)
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("should have aria-label for accessibility and testing", async () => {
|
||||
it('should have aria-label for accessibility and testing', async () => {
|
||||
await renderField({
|
||||
startDate: "2025-01-01",
|
||||
startTime: "10:00",
|
||||
endDate: "2025-01-01",
|
||||
endTime: "11:00",
|
||||
showMore: true,
|
||||
});
|
||||
startDate: '2025-01-01',
|
||||
startTime: '10:00',
|
||||
endDate: '2025-01-01',
|
||||
endTime: '11:00',
|
||||
showMore: true
|
||||
})
|
||||
|
||||
const startDateInput = screen.getByTestId("start-date-input");
|
||||
const startTimeInput = screen.getByTestId("start-time-input");
|
||||
const endDateInput = screen.getByTestId("end-date-input");
|
||||
const endTimeInput = screen.getByTestId("end-time-input");
|
||||
const startDateInput = screen.getByTestId('start-date-input')
|
||||
const startTimeInput = screen.getByTestId('start-time-input')
|
||||
const endDateInput = screen.getByTestId('end-date-input')
|
||||
const endTimeInput = screen.getByTestId('end-time-input')
|
||||
|
||||
expect(startDateInput).toHaveAttribute(
|
||||
"aria-label",
|
||||
"dateTimeFields.startDate"
|
||||
);
|
||||
'aria-label',
|
||||
'dateTimeFields.startDate'
|
||||
)
|
||||
expect(startTimeInput).toHaveAttribute(
|
||||
"aria-label",
|
||||
"dateTimeFields.startTime"
|
||||
);
|
||||
expect(endDateInput).toHaveAttribute(
|
||||
"aria-label",
|
||||
"dateTimeFields.endDate"
|
||||
);
|
||||
expect(endTimeInput).toHaveAttribute(
|
||||
"aria-label",
|
||||
"dateTimeFields.endTime"
|
||||
);
|
||||
});
|
||||
'aria-label',
|
||||
'dateTimeFields.startTime'
|
||||
)
|
||||
expect(endDateInput).toHaveAttribute('aria-label', 'dateTimeFields.endDate')
|
||||
expect(endTimeInput).toHaveAttribute('aria-label', 'dateTimeFields.endTime')
|
||||
})
|
||||
|
||||
describe("DateTimeFields - Drag and Drop Display Logic", () => {
|
||||
describe('DateTimeFields - Drag and Drop Display Logic', () => {
|
||||
// Test 1.1: Display 2 fields when drag from allday slot (multiple days)
|
||||
it("displays only 2 date fields when allday=true and multiple days", async () => {
|
||||
it('displays only 2 date fields when allday=true and multiple days', async () => {
|
||||
await renderField({
|
||||
allday: true,
|
||||
hasEndDateChanged: false,
|
||||
startDate: "2025-07-18",
|
||||
endDate: "2025-07-20",
|
||||
startDate: '2025-07-18',
|
||||
endDate: '2025-07-20',
|
||||
showEndDate: true,
|
||||
showMore: false,
|
||||
});
|
||||
showMore: false
|
||||
})
|
||||
|
||||
expect(screen.getByTestId("start-date-input")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("end-date-input")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("start-time-input")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("end-time-input")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId('start-date-input')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('end-date-input')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('start-time-input')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('end-time-input')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Test 1.2: Display 4 fields when drag from week view (multiple days)
|
||||
it("displays 4 fields when allday=false, hasEndDateChanged=true, and multiple days", async () => {
|
||||
it('displays 4 fields when allday=false, hasEndDateChanged=true, and multiple days', async () => {
|
||||
await renderField({
|
||||
allday: false,
|
||||
hasEndDateChanged: true,
|
||||
startDate: "2025-07-18",
|
||||
endDate: "2025-07-20",
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
startDate: '2025-07-18',
|
||||
endDate: '2025-07-20',
|
||||
startTime: '09:00',
|
||||
endTime: '10:00',
|
||||
showEndDate: true,
|
||||
showMore: false,
|
||||
});
|
||||
showMore: false
|
||||
})
|
||||
|
||||
expect(screen.getByTestId("start-date-input")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("start-time-input")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("end-date-input")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("end-time-input")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId('start-date-input')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('start-time-input')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('end-date-input')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('end-time-input')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Test 1.3: Display single date + time fields
|
||||
it("displays single date field with time fields for single day event", async () => {
|
||||
it('displays single date field with time fields for single day event', async () => {
|
||||
await renderField({
|
||||
allday: false,
|
||||
hasEndDateChanged: false,
|
||||
startDate: "2025-07-18",
|
||||
endDate: "2025-07-18",
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
startDate: '2025-07-18',
|
||||
endDate: '2025-07-18',
|
||||
startTime: '09:00',
|
||||
endTime: '10:00',
|
||||
showEndDate: false,
|
||||
showMore: false,
|
||||
});
|
||||
showMore: false
|
||||
})
|
||||
|
||||
const startDateInput = screen.getByTestId("start-date-input");
|
||||
const startDateInput = screen.getByTestId('start-date-input')
|
||||
expect(startDateInput).toHaveAttribute(
|
||||
"aria-label",
|
||||
"dateTimeFields.date"
|
||||
);
|
||||
expect(screen.getByTestId("start-time-input")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("end-time-input")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("end-date-input")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
'aria-label',
|
||||
'dateTimeFields.date'
|
||||
)
|
||||
expect(screen.getByTestId('start-time-input')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('end-time-input')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('end-date-input')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
import { RootState } from "@/app/store";
|
||||
import EventDuplication from "@/components/Event/EventDuplicate";
|
||||
import EventPreviewModal from "@/features/Events/EventPreview";
|
||||
import EventPopover from "@/features/Events/EventModal";
|
||||
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
import { RootState } from '@/app/store'
|
||||
import EventDuplication from '@/components/Event/EventDuplicate'
|
||||
import EventPreviewModal from '@/features/Events/EventPreview'
|
||||
import EventPopover from '@/features/Events/EventModal'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
const day = new Date();
|
||||
const day = new Date()
|
||||
const preloadedState = {
|
||||
user: {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||
openpaasId: "667037022b752d0026472254",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro',
|
||||
openpaasId: '667037022b752d0026472254'
|
||||
},
|
||||
organiserData: {
|
||||
cn: "test",
|
||||
cal_address: "mailto:test@test.com",
|
||||
},
|
||||
cn: 'test',
|
||||
cal_address: 'mailto:test@test.com'
|
||||
}
|
||||
},
|
||||
calendars: {
|
||||
list: {
|
||||
"667037022b752d0026472254/cal1": {
|
||||
id: "667037022b752d0026472254/cal1",
|
||||
name: "Calendar",
|
||||
color: "#FF0000",
|
||||
owner: { emails: ["test@test.com"] },
|
||||
'667037022b752d0026472254/cal1': {
|
||||
id: '667037022b752d0026472254/cal1',
|
||||
name: 'Calendar',
|
||||
color: '#FF0000',
|
||||
owner: { emails: ['test@test.com'] },
|
||||
events: {
|
||||
event1: {
|
||||
uid: "event1",
|
||||
URL: "calendars/667037022b752d0026472254/cal1/event1.ics",
|
||||
title: "Test Event",
|
||||
calId: "667037022b752d0026472254/cal1",
|
||||
uid: 'event1',
|
||||
URL: 'calendars/667037022b752d0026472254/cal1/event1.ics',
|
||||
title: 'Test Event',
|
||||
calId: '667037022b752d0026472254/cal1',
|
||||
start: day.toISOString(),
|
||||
end: day.toISOString(),
|
||||
timezone: "UTC",
|
||||
organizer: { cn: "test", cal_address: "test@test.com" },
|
||||
timezone: 'UTC',
|
||||
organizer: { cn: 'test', cal_address: 'test@test.com' },
|
||||
attendee: [
|
||||
{
|
||||
cn: "test",
|
||||
cal_address: "test@test.com",
|
||||
partstat: "NEEDS-ACTION",
|
||||
rsvp: "TRUE",
|
||||
role: "REQ-PARTICIPANT",
|
||||
cutype: "INDIVIDUAL",
|
||||
cn: 'test',
|
||||
cal_address: 'test@test.com',
|
||||
partstat: 'NEEDS-ACTION',
|
||||
rsvp: 'TRUE',
|
||||
role: 'REQ-PARTICIPANT',
|
||||
cutype: 'INDIVIDUAL'
|
||||
},
|
||||
{
|
||||
cn: "John",
|
||||
cal_address: "john@test.com",
|
||||
partstat: "NEEDS-ACTION",
|
||||
rsvp: "TRUE",
|
||||
role: "REQ-PARTICIPANT",
|
||||
cutype: "INDIVIDUAL",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
cn: 'John',
|
||||
cal_address: 'john@test.com',
|
||||
partstat: 'NEEDS-ACTION',
|
||||
rsvp: 'TRUE',
|
||||
role: 'REQ-PARTICIPANT',
|
||||
cutype: 'INDIVIDUAL'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
} as unknown as RootState;
|
||||
pending: false
|
||||
}
|
||||
} as unknown as RootState
|
||||
|
||||
describe("EventDuplication", () => {
|
||||
it("calls onOpenDuplicate when button clicked", () => {
|
||||
const handleClose = jest.fn();
|
||||
const onOpenDuplicate = jest.fn();
|
||||
describe('EventDuplication', () => {
|
||||
it('calls onOpenDuplicate when button clicked', () => {
|
||||
const handleClose = jest.fn()
|
||||
const onOpenDuplicate = jest.fn()
|
||||
renderWithProviders(
|
||||
<EventDuplication
|
||||
event={
|
||||
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events
|
||||
preloadedState.calendars.list['667037022b752d0026472254/cal1'].events
|
||||
.event1
|
||||
}
|
||||
onClose={handleClose}
|
||||
onOpenDuplicate={onOpenDuplicate}
|
||||
/>,
|
||||
preloadedState
|
||||
);
|
||||
)
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole("menuitem", { name: "eventDuplication.duplicateEvent" })
|
||||
);
|
||||
screen.getByRole('menuitem', { name: 'eventDuplication.duplicateEvent' })
|
||||
)
|
||||
|
||||
expect(onOpenDuplicate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
expect(onOpenDuplicate).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("EventPopover", () => {
|
||||
it("renders with event data", () => {
|
||||
describe('EventPopover', () => {
|
||||
it('renders with event data', () => {
|
||||
renderWithProviders(
|
||||
<EventPopover
|
||||
anchorEl={null}
|
||||
@@ -97,18 +97,18 @@ describe("EventPopover", () => {
|
||||
setSelectedRange={jest.fn()}
|
||||
calendarRef={{ current: null }}
|
||||
event={
|
||||
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events
|
||||
preloadedState.calendars.list['667037022b752d0026472254/cal1'].events
|
||||
.event1
|
||||
}
|
||||
/>,
|
||||
preloadedState
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByDisplayValue(/Test Event/i)).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByDisplayValue(/Test Event/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("saves duplicated event when Save is clicked", () => {
|
||||
const onClose = jest.fn();
|
||||
it('saves duplicated event when Save is clicked', () => {
|
||||
const onClose = jest.fn()
|
||||
|
||||
renderWithProviders(
|
||||
<EventPopover
|
||||
@@ -119,24 +119,24 @@ describe("EventPopover", () => {
|
||||
setSelectedRange={jest.fn()}
|
||||
calendarRef={{ current: null }}
|
||||
event={
|
||||
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events
|
||||
preloadedState.calendars.list['667037022b752d0026472254/cal1'].events
|
||||
.event1
|
||||
}
|
||||
/>,
|
||||
preloadedState
|
||||
);
|
||||
)
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/Title/i), {
|
||||
target: { value: "Duplicated Event" },
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
|
||||
target: { value: 'Duplicated Event' }
|
||||
})
|
||||
fireEvent.click(screen.getByRole('button', { name: 'actions.save' }))
|
||||
|
||||
waitFor(() => expect(onClose).toHaveBeenCalled());
|
||||
});
|
||||
});
|
||||
waitFor(() => expect(onClose).toHaveBeenCalled())
|
||||
})
|
||||
})
|
||||
|
||||
describe("EventDisplayModal", () => {
|
||||
it("shows duplication button and opens duplication form", () => {
|
||||
describe('EventDisplayModal', () => {
|
||||
it('shows duplication button and opens duplication form', () => {
|
||||
renderWithProviders(
|
||||
<EventPreviewModal
|
||||
eventId="event1"
|
||||
@@ -145,20 +145,20 @@ describe("EventDisplayModal", () => {
|
||||
onClose={jest.fn()}
|
||||
/>,
|
||||
preloadedState
|
||||
);
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId("MoreVertIcon"));
|
||||
fireEvent.click(screen.getByTestId('MoreVertIcon'))
|
||||
fireEvent.click(
|
||||
screen.getByRole("menuitem", { name: "eventDuplication.duplicateEvent" })
|
||||
);
|
||||
screen.getByRole('menuitem', { name: 'eventDuplication.duplicateEvent' })
|
||||
)
|
||||
expect(
|
||||
screen.getAllByText("eventDuplication.duplicateEvent")[1]
|
||||
).toBeInTheDocument();
|
||||
screen.getAllByText('eventDuplication.duplicateEvent')[1]
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByDisplayValue(
|
||||
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events
|
||||
preloadedState.calendars.list['667037022b752d0026472254/cal1'].events
|
||||
.event1.title as string
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,176 +3,174 @@ import {
|
||||
convertFormDateTimeToISO,
|
||||
DATETIME_WITH_SECONDS_LENGTH,
|
||||
DATETIME_FORMAT_WITH_SECONDS,
|
||||
DATETIME_FORMAT_WITHOUT_SECONDS,
|
||||
} from "@/components/Event/utils/dateTimeHelpers";
|
||||
DATETIME_FORMAT_WITHOUT_SECONDS
|
||||
} from '@/components/Event/utils/dateTimeHelpers'
|
||||
|
||||
describe("dateTimeHelpers", () => {
|
||||
describe("Constants", () => {
|
||||
it("should have correct constant values", () => {
|
||||
expect(DATETIME_WITH_SECONDS_LENGTH).toBe(19);
|
||||
expect(DATETIME_FORMAT_WITH_SECONDS).toBe("YYYY-MM-DDTHH:mm:ss");
|
||||
expect(DATETIME_FORMAT_WITHOUT_SECONDS).toBe("YYYY-MM-DDTHH:mm");
|
||||
});
|
||||
});
|
||||
describe('dateTimeHelpers', () => {
|
||||
describe('Constants', () => {
|
||||
it('should have correct constant values', () => {
|
||||
expect(DATETIME_WITH_SECONDS_LENGTH).toBe(19)
|
||||
expect(DATETIME_FORMAT_WITH_SECONDS).toBe('YYYY-MM-DDTHH:mm:ss')
|
||||
expect(DATETIME_FORMAT_WITHOUT_SECONDS).toBe('YYYY-MM-DDTHH:mm')
|
||||
})
|
||||
})
|
||||
|
||||
describe("detectDateTimeFormat", () => {
|
||||
it("should return format with seconds for length >= 19", () => {
|
||||
expect(detectDateTimeFormat("2024-01-15T10:30:45")).toBe(
|
||||
describe('detectDateTimeFormat', () => {
|
||||
it('should return format with seconds for length >= 19', () => {
|
||||
expect(detectDateTimeFormat('2024-01-15T10:30:45')).toBe(
|
||||
DATETIME_FORMAT_WITH_SECONDS
|
||||
);
|
||||
expect(detectDateTimeFormat("2024-01-15T10:30:45")).toBe(
|
||||
"YYYY-MM-DDTHH:mm:ss"
|
||||
);
|
||||
});
|
||||
)
|
||||
expect(detectDateTimeFormat('2024-01-15T10:30:45')).toBe(
|
||||
'YYYY-MM-DDTHH:mm:ss'
|
||||
)
|
||||
})
|
||||
|
||||
it("should return format without seconds for length < 19", () => {
|
||||
expect(detectDateTimeFormat("2024-01-15T10:30")).toBe(
|
||||
it('should return format without seconds for length < 19', () => {
|
||||
expect(detectDateTimeFormat('2024-01-15T10:30')).toBe(
|
||||
DATETIME_FORMAT_WITHOUT_SECONDS
|
||||
);
|
||||
expect(detectDateTimeFormat("2024-01-15T10:30")).toBe("YYYY-MM-DDTHH:mm");
|
||||
});
|
||||
)
|
||||
expect(detectDateTimeFormat('2024-01-15T10:30')).toBe('YYYY-MM-DDTHH:mm')
|
||||
})
|
||||
|
||||
it("should return format with seconds for length exactly 19", () => {
|
||||
const datetime = "2024-01-15T10:30:45";
|
||||
expect(datetime.length).toBe(19);
|
||||
expect(detectDateTimeFormat(datetime)).toBe(DATETIME_FORMAT_WITH_SECONDS);
|
||||
});
|
||||
it('should return format with seconds for length exactly 19', () => {
|
||||
const datetime = '2024-01-15T10:30:45'
|
||||
expect(datetime.length).toBe(19)
|
||||
expect(detectDateTimeFormat(datetime)).toBe(DATETIME_FORMAT_WITH_SECONDS)
|
||||
})
|
||||
|
||||
it("should return format without seconds for length 16", () => {
|
||||
const datetime = "2024-01-15T10:30";
|
||||
expect(datetime.length).toBe(16);
|
||||
it('should return format without seconds for length 16', () => {
|
||||
const datetime = '2024-01-15T10:30'
|
||||
expect(datetime.length).toBe(16)
|
||||
expect(detectDateTimeFormat(datetime)).toBe(
|
||||
DATETIME_FORMAT_WITHOUT_SECONDS
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("should return format with seconds for length > 19", () => {
|
||||
const datetime = "2024-01-15T10:30:45.123";
|
||||
expect(datetime.length).toBeGreaterThan(19);
|
||||
expect(detectDateTimeFormat(datetime)).toBe(DATETIME_FORMAT_WITH_SECONDS);
|
||||
});
|
||||
it('should return format with seconds for length > 19', () => {
|
||||
const datetime = '2024-01-15T10:30:45.123'
|
||||
expect(datetime.length).toBeGreaterThan(19)
|
||||
expect(detectDateTimeFormat(datetime)).toBe(DATETIME_FORMAT_WITH_SECONDS)
|
||||
})
|
||||
|
||||
it("should handle empty string", () => {
|
||||
expect(detectDateTimeFormat("")).toBe(DATETIME_FORMAT_WITHOUT_SECONDS);
|
||||
});
|
||||
it('should handle empty string', () => {
|
||||
expect(detectDateTimeFormat('')).toBe(DATETIME_FORMAT_WITHOUT_SECONDS)
|
||||
})
|
||||
|
||||
it("should handle very short strings", () => {
|
||||
expect(detectDateTimeFormat("2024")).toBe(
|
||||
DATETIME_FORMAT_WITHOUT_SECONDS
|
||||
);
|
||||
});
|
||||
});
|
||||
it('should handle very short strings', () => {
|
||||
expect(detectDateTimeFormat('2024')).toBe(DATETIME_FORMAT_WITHOUT_SECONDS)
|
||||
})
|
||||
})
|
||||
|
||||
describe("convertFormDateTimeToISO", () => {
|
||||
const originalConsoleWarn = console.warn;
|
||||
let consoleWarnSpy: jest.SpyInstance;
|
||||
describe('convertFormDateTimeToISO', () => {
|
||||
const originalConsoleWarn = console.warn
|
||||
let consoleWarnSpy: jest.SpyInstance
|
||||
|
||||
beforeEach(() => {
|
||||
consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation();
|
||||
});
|
||||
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
consoleWarnSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("should convert valid datetime without seconds to ISO string", () => {
|
||||
it('should convert valid datetime without seconds to ISO string', () => {
|
||||
const result = convertFormDateTimeToISO(
|
||||
"2024-01-15T10:30",
|
||||
"America/New_York"
|
||||
);
|
||||
expect(result).toBeTruthy();
|
||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
'2024-01-15T10:30',
|
||||
'America/New_York'
|
||||
)
|
||||
expect(result).toBeTruthy()
|
||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should convert valid datetime with seconds to ISO string", () => {
|
||||
it('should convert valid datetime with seconds to ISO string', () => {
|
||||
const result = convertFormDateTimeToISO(
|
||||
"2024-01-15T10:30:45",
|
||||
"America/New_York"
|
||||
);
|
||||
expect(result).toBeTruthy();
|
||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
'2024-01-15T10:30:45',
|
||||
'America/New_York'
|
||||
)
|
||||
expect(result).toBeTruthy()
|
||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should use Etc/UTC as default timezone when timezone is empty", () => {
|
||||
const result = convertFormDateTimeToISO("2024-01-15T10:30", "");
|
||||
expect(result).toBeTruthy();
|
||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
||||
});
|
||||
it('should use Etc/UTC as default timezone when timezone is empty', () => {
|
||||
const result = convertFormDateTimeToISO('2024-01-15T10:30', '')
|
||||
expect(result).toBeTruthy()
|
||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
|
||||
})
|
||||
|
||||
it("should return empty string for empty datetime input", () => {
|
||||
const result = convertFormDateTimeToISO("", "America/New_York");
|
||||
expect(result).toBe("");
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
it('should return empty string for empty datetime input', () => {
|
||||
const result = convertFormDateTimeToISO('', 'America/New_York')
|
||||
expect(result).toBe('')
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should return empty string and log warning for invalid datetime", () => {
|
||||
it('should return empty string and log warning for invalid datetime', () => {
|
||||
const result = convertFormDateTimeToISO(
|
||||
"invalid-date",
|
||||
"America/New_York"
|
||||
);
|
||||
expect(result).toBe("");
|
||||
'invalid-date',
|
||||
'America/New_York'
|
||||
)
|
||||
expect(result).toBe('')
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("[convertFormDateTimeToISO] Invalid datetime:")
|
||||
);
|
||||
expect.stringContaining('[convertFormDateTimeToISO] Invalid datetime:')
|
||||
)
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('"invalid-date"')
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("should return empty string and log warning for invalid format", () => {
|
||||
it('should return empty string and log warning for invalid format', () => {
|
||||
const result = convertFormDateTimeToISO(
|
||||
"2024-13-45T25:99:99",
|
||||
"America/New_York"
|
||||
);
|
||||
expect(result).toBe("");
|
||||
expect(consoleWarnSpy).toHaveBeenCalled();
|
||||
});
|
||||
'2024-13-45T25:99:99',
|
||||
'America/New_York'
|
||||
)
|
||||
expect(result).toBe('')
|
||||
expect(consoleWarnSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle different timezones correctly", () => {
|
||||
it('should handle different timezones correctly', () => {
|
||||
const result1 = convertFormDateTimeToISO(
|
||||
"2024-01-15T10:30",
|
||||
"America/New_York"
|
||||
);
|
||||
'2024-01-15T10:30',
|
||||
'America/New_York'
|
||||
)
|
||||
const result2 = convertFormDateTimeToISO(
|
||||
"2024-01-15T10:30",
|
||||
"Europe/London"
|
||||
);
|
||||
expect(result1).toBeTruthy();
|
||||
expect(result2).toBeTruthy();
|
||||
expect(result1).not.toBe(result2);
|
||||
});
|
||||
'2024-01-15T10:30',
|
||||
'Europe/London'
|
||||
)
|
||||
expect(result1).toBeTruthy()
|
||||
expect(result2).toBeTruthy()
|
||||
expect(result1).not.toBe(result2)
|
||||
})
|
||||
|
||||
it("should handle edge case with null/undefined timezone", () => {
|
||||
it('should handle edge case with null/undefined timezone', () => {
|
||||
const result = convertFormDateTimeToISO(
|
||||
"2024-01-15T10:30",
|
||||
'2024-01-15T10:30',
|
||||
// @ts-ignore - testing edge case
|
||||
null
|
||||
);
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
)
|
||||
expect(result).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should convert correctly for UTC timezone", () => {
|
||||
const result = convertFormDateTimeToISO("2024-01-15T10:30", "Etc/UTC");
|
||||
expect(result).toBeTruthy();
|
||||
expect(result).toContain("T10:30:00");
|
||||
});
|
||||
it('should convert correctly for UTC timezone', () => {
|
||||
const result = convertFormDateTimeToISO('2024-01-15T10:30', 'Etc/UTC')
|
||||
expect(result).toBeTruthy()
|
||||
expect(result).toContain('T10:30:00')
|
||||
})
|
||||
|
||||
it("should handle datetime at boundary (exactly 19 characters)", () => {
|
||||
const datetime = "2024-01-15T10:30:45";
|
||||
expect(datetime.length).toBe(19);
|
||||
const result = convertFormDateTimeToISO(datetime, "Etc/UTC");
|
||||
expect(result).toBeTruthy();
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
it('should handle datetime at boundary (exactly 19 characters)', () => {
|
||||
const datetime = '2024-01-15T10:30:45'
|
||||
expect(datetime.length).toBe(19)
|
||||
const result = convertFormDateTimeToISO(datetime, 'Etc/UTC')
|
||||
expect(result).toBeTruthy()
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle datetime at boundary (exactly 16 characters)", () => {
|
||||
const datetime = "2024-01-15T10:30";
|
||||
expect(datetime.length).toBe(16);
|
||||
const result = convertFormDateTimeToISO(datetime, "Etc/UTC");
|
||||
expect(result).toBeTruthy();
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should handle datetime at boundary (exactly 16 characters)', () => {
|
||||
const datetime = '2024-01-15T10:30'
|
||||
expect(datetime.length).toBe(16)
|
||||
const result = convertFormDateTimeToISO(datetime, 'Etc/UTC')
|
||||
expect(result).toBeTruthy()
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,102 +1,100 @@
|
||||
import CalendarLayout from "@/components/Calendar/CalendarLayout";
|
||||
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
import CalendarLayout from '@/components/Calendar/CalendarLayout'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
describe("Event Error Handling", () => {
|
||||
describe('Event Error Handling', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
localStorage.clear()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
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);
|
||||
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)
|
||||
|
||||
const erroredState = {
|
||||
user: {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "mockSid",
|
||||
openpaasId: "user1",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: 'user1'
|
||||
},
|
||||
tokens: { accessToken: "token" },
|
||||
tokens: { accessToken: 'token' }
|
||||
},
|
||||
calendars: {
|
||||
list: {
|
||||
"user1/cal1": {
|
||||
name: "Calendar personal",
|
||||
id: "user1/cal1",
|
||||
color: { light: "#FF0000", dark: "#000" },
|
||||
owner: { emails: ["alice@example.com"] },
|
||||
'user1/cal1': {
|
||||
name: 'Calendar personal',
|
||||
id: 'user1/cal1',
|
||||
color: { light: '#FF0000', dark: '#000' },
|
||||
owner: { emails: ['alice@example.com'] },
|
||||
events: {
|
||||
event1: {
|
||||
id: "event1",
|
||||
calId: "user1/cal1",
|
||||
uid: "event1",
|
||||
title: "Test Event",
|
||||
id: 'event1',
|
||||
calId: 'user1/cal1',
|
||||
uid: 'event1',
|
||||
title: 'Test Event',
|
||||
start: start.toISOString(),
|
||||
end: start.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
|
||||
}
|
||||
}
|
||||
|
||||
it("BUGFIX: does not re-report errors after clearing error snackbar", async () => {
|
||||
const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation();
|
||||
it('BUGFIX: does not re-report errors after clearing error snackbar', async () => {
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation()
|
||||
|
||||
await act(async () =>
|
||||
renderWithProviders(<CalendarLayout />, erroredState)
|
||||
);
|
||||
await act(async () => renderWithProviders(<CalendarLayout />, erroredState))
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.getByText("Test Event"));
|
||||
expect(screen.getByRole("alert")).toBeInTheDocument();
|
||||
expect(screen.getByText('Test Event'))
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument()
|
||||
},
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
const closeButton = screen.queryByRole("button", { name: "common.ok" });
|
||||
)
|
||||
const closeButton = screen.queryByRole('button', { name: 'common.ok' })
|
||||
|
||||
if (closeButton) {
|
||||
const initialWarnCount = consoleWarnSpy.mock.calls.length;
|
||||
const initialWarnCount = consoleWarnSpy.mock.calls.length
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(closeButton);
|
||||
});
|
||||
fireEvent.click(closeButton)
|
||||
})
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
|
||||
},
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
)
|
||||
|
||||
const afterCloseWarnCount = consoleWarnSpy.mock.calls.length;
|
||||
const afterCloseWarnCount = consoleWarnSpy.mock.calls.length
|
||||
|
||||
expect(afterCloseWarnCount).toBe(initialWarnCount);
|
||||
expect(afterCloseWarnCount).toBe(initialWarnCount)
|
||||
}
|
||||
|
||||
consoleWarnSpy.mockRestore();
|
||||
}, 15000);
|
||||
});
|
||||
consoleWarnSpy.mockRestore()
|
||||
}, 15000)
|
||||
})
|
||||
|
||||
@@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,65 +1,65 @@
|
||||
import * as appHooks from "@/app/hooks";
|
||||
import { AppDispatch } from "@/app/store";
|
||||
import CalendarApp from "@/components/Calendar/Calendar";
|
||||
import { jest } from "@jest/globals";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
import * as appHooks from '@/app/hooks'
|
||||
import { AppDispatch } from '@/app/store'
|
||||
import CalendarApp from '@/components/Calendar/Calendar'
|
||||
import { jest } from '@jest/globals'
|
||||
import { screen } from '@testing-library/react'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
describe("MiniCalendar", () => {
|
||||
const day = new Date();
|
||||
describe('MiniCalendar', () => {
|
||||
const day = new Date()
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.clearAllMocks()
|
||||
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);
|
||||
jest.useFakeTimers().clearAllTimers();
|
||||
});
|
||||
) as unknown as AppDispatch
|
||||
jest.spyOn(appHooks, 'useAppDispatch').mockReturnValue(dispatch)
|
||||
jest.useFakeTimers().clearAllTimers()
|
||||
})
|
||||
|
||||
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'
|
||||
}
|
||||
},
|
||||
calendars: {
|
||||
list: {
|
||||
"667037022b752d0026472254/cal1": {
|
||||
name: "Calendar 1",
|
||||
color: { light: "#FF0000", dark: "#000" },
|
||||
owner: { emails: ["test@test.com"] },
|
||||
'667037022b752d0026472254/cal1': {
|
||||
name: 'Calendar 1',
|
||||
color: { light: '#FF0000', dark: '#000' },
|
||||
owner: { emails: ['test@test.com'] },
|
||||
events: {
|
||||
event1: {
|
||||
calId: "667037022b752d0026472254/cal1",
|
||||
id: "event1",
|
||||
title: "Test Event",
|
||||
start: day.toISOString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
calId: '667037022b752d0026472254/cal1',
|
||||
id: 'event1',
|
||||
title: 'Test Event',
|
||||
start: day.toISOString()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
};
|
||||
const mockCalendarRef = { current: null };
|
||||
pending: false
|
||||
}
|
||||
}
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
preloadedState
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
it("renders mini calendar with today in orange", async () => {
|
||||
renderCalendar();
|
||||
const today = new Date();
|
||||
const dateTestId = `date-${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`;
|
||||
it('renders mini calendar with today in orange', async () => {
|
||||
renderCalendar()
|
||||
const today = new Date()
|
||||
const dateTestId = `date-${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`
|
||||
|
||||
const todayTile = screen.getByTestId(dateTestId);
|
||||
expect(todayTile).toHaveClass("today");
|
||||
});
|
||||
});
|
||||
const todayTiles = screen.getAllByTestId(dateTestId)
|
||||
expect(todayTiles[0]).toHaveClass('today')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,412 +1,410 @@
|
||||
import { PeopleSearch, User } from "@/components/Attendees/PeopleSearch";
|
||||
import { searchUsers } from "@/features/User/userAPI";
|
||||
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
import { PeopleSearch, User } from '@/components/Attendees/PeopleSearch'
|
||||
import { searchUsers } from '@/features/User/userAPI'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
jest.mock("@/features/User/userAPI");
|
||||
const mockedSearchUsers = searchUsers as jest.MockedFunction<
|
||||
typeof searchUsers
|
||||
>;
|
||||
jest.mock('@/features/User/userAPI')
|
||||
const mockedSearchUsers = searchUsers as jest.MockedFunction<typeof searchUsers>
|
||||
|
||||
describe("PeopleSearch", () => {
|
||||
describe('PeopleSearch', () => {
|
||||
const baseUser: User = {
|
||||
email: "test@example.com",
|
||||
displayName: "Test User",
|
||||
avatarUrl: "https://example.com/avatar.png",
|
||||
openpaasId: "1234567890",
|
||||
};
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
avatarUrl: 'https://example.com/avatar.png',
|
||||
openpaasId: '1234567890'
|
||||
}
|
||||
|
||||
function setup(
|
||||
selectedUsers: User[] = [],
|
||||
props?: Partial<React.ComponentProps<typeof PeopleSearch>>
|
||||
) {
|
||||
const onChange = jest.fn();
|
||||
const onChange = jest.fn()
|
||||
renderWithProviders(
|
||||
<PeopleSearch
|
||||
objectTypes={["user"]}
|
||||
objectTypes={['user']}
|
||||
selectedUsers={selectedUsers}
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return { onChange };
|
||||
)
|
||||
return { onChange }
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
mockedSearchUsers.mockReset();
|
||||
});
|
||||
jest.useFakeTimers()
|
||||
mockedSearchUsers.mockReset()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it("calls searchUsers after debounce when typing", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser]);
|
||||
setup();
|
||||
it('calls searchUsers after debounce when typing', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser])
|
||||
setup()
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Test");
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Test')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedSearchUsers).toHaveBeenCalledWith("Test", ["user"]);
|
||||
});
|
||||
});
|
||||
expect(mockedSearchUsers).toHaveBeenCalledWith('Test', ['user'])
|
||||
})
|
||||
})
|
||||
|
||||
it("renders search results and allows selection", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser]);
|
||||
const { onChange } = setup();
|
||||
it('renders search results and allows selection', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser])
|
||||
const { onChange } = setup()
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Test");
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Test')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
const option = await screen.findByText("Test User");
|
||||
await userEvent.click(option);
|
||||
const option = await screen.findByText('Test User')
|
||||
await userEvent.click(option)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
expect(onChange).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it("does not show already selected users in options", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser]);
|
||||
setup([baseUser]);
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Test");
|
||||
it('does not show already selected users in options', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser])
|
||||
setup([baseUser])
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Test')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText("test@example.com")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
expect(screen.queryByText('test@example.com')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it("triggers onToggleEventPreview on Enter key press", () => {
|
||||
const onToggleEventPreview = jest.fn();
|
||||
setup([], { onToggleEventPreview });
|
||||
it('triggers onToggleEventPreview on Enter key press', () => {
|
||||
const onToggleEventPreview = jest.fn()
|
||||
setup([], { onToggleEventPreview })
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
fireEvent.keyDown(input, { key: "Enter" });
|
||||
const input = screen.getByRole('combobox')
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
|
||||
expect(onToggleEventPreview).toHaveBeenCalled();
|
||||
});
|
||||
expect(onToggleEventPreview).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("respects disabled state", () => {
|
||||
setup([], { disabled: true });
|
||||
expect(screen.getByRole("combobox")).toBeDisabled();
|
||||
});
|
||||
it('respects disabled state', () => {
|
||||
setup([], { disabled: true })
|
||||
expect(screen.getByRole('combobox')).toBeDisabled()
|
||||
})
|
||||
|
||||
it("no options doesn't show dropdown when input is empty", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser]);
|
||||
setup();
|
||||
const input = screen.getByRole("combobox");
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser])
|
||||
setup()
|
||||
const input = screen.getByRole('combobox')
|
||||
|
||||
userEvent.type(input, "Test");
|
||||
userEvent.type(input, 'Test')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByRole('listbox')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
userEvent.clear(input);
|
||||
userEvent.clear(input)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it("shows 'No results' when search succeeds but returns empty array", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([]);
|
||||
setup();
|
||||
mockedSearchUsers.mockResolvedValueOnce([])
|
||||
setup()
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Test");
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Test')
|
||||
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
const noResults = await screen.findByText(
|
||||
"peopleSearch.noResults",
|
||||
'peopleSearch.noResults',
|
||||
{},
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
expect(noResults).toBeInTheDocument();
|
||||
});
|
||||
)
|
||||
expect(noResults).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("does not clear options when search fails and shows error snackbar", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser]);
|
||||
setup();
|
||||
it('does not clear options when search fails and shows error snackbar', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser])
|
||||
setup()
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Test");
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Test')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
await Promise.resolve();
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Test User")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Test User')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
mockedSearchUsers.mockRejectedValueOnce(new Error("Network error"));
|
||||
await userEvent.clear(input);
|
||||
await userEvent.type(input, "Error");
|
||||
mockedSearchUsers.mockRejectedValueOnce(new Error('Network error'))
|
||||
await userEvent.clear(input)
|
||||
await userEvent.type(input, 'Error')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
await Promise.resolve();
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
const errorMessage = await screen.findByText("peopleSearch.searchError");
|
||||
expect(errorMessage).toBeInTheDocument();
|
||||
const errorMessage = await screen.findByText('peopleSearch.searchError')
|
||||
expect(errorMessage).toBeInTheDocument()
|
||||
|
||||
expect(screen.queryByText("Test User")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Test User')).not.toBeInTheDocument()
|
||||
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser]);
|
||||
await userEvent.clear(input);
|
||||
await userEvent.type(input, "Test");
|
||||
mockedSearchUsers.mockResolvedValueOnce([baseUser])
|
||||
await userEvent.clear(input)
|
||||
await userEvent.type(input, 'Test')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
await Promise.resolve();
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Test User")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
expect(screen.getByText('Test User')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it("shows loading text when searching", async () => {
|
||||
let resolveSearch: (value: User[]) => void;
|
||||
const searchPromise = new Promise<User[]>((resolve) => {
|
||||
resolveSearch = resolve;
|
||||
});
|
||||
mockedSearchUsers.mockReturnValueOnce(searchPromise);
|
||||
setup();
|
||||
it('shows loading text when searching', async () => {
|
||||
let resolveSearch: (value: User[]) => void
|
||||
const searchPromise = new Promise<User[]>(resolve => {
|
||||
resolveSearch = resolve
|
||||
})
|
||||
mockedSearchUsers.mockReturnValueOnce(searchPromise)
|
||||
setup()
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Test");
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Test')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
const loadingText = await screen.findByText(
|
||||
"peopleSearch.loading",
|
||||
'peopleSearch.loading',
|
||||
{},
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
expect(loadingText).toBeInTheDocument();
|
||||
)
|
||||
expect(loadingText).toBeInTheDocument()
|
||||
|
||||
await act(async () => {
|
||||
resolveSearch!([baseUser]);
|
||||
await searchPromise;
|
||||
});
|
||||
});
|
||||
resolveSearch!([baseUser])
|
||||
await searchPromise
|
||||
})
|
||||
})
|
||||
|
||||
describe("paste multiple attendees", () => {
|
||||
describe('paste multiple attendees', () => {
|
||||
function setupFreeSolo(selectedUsers: User[] = []) {
|
||||
const onChange = jest.fn();
|
||||
const onChange = jest.fn()
|
||||
renderWithProviders(
|
||||
<PeopleSearch
|
||||
objectTypes={["user"]}
|
||||
objectTypes={['user']}
|
||||
selectedUsers={selectedUsers}
|
||||
onChange={onChange}
|
||||
freeSolo
|
||||
/>
|
||||
);
|
||||
return { onChange };
|
||||
)
|
||||
return { onChange }
|
||||
}
|
||||
|
||||
it("splits pasted comma-separated emails into individual attendees", async () => {
|
||||
const { onChange } = setupFreeSolo();
|
||||
const input = screen.getByRole("combobox");
|
||||
it('splits pasted comma-separated emails into individual attendees', async () => {
|
||||
const { onChange } = setupFreeSolo()
|
||||
const input = screen.getByRole('combobox')
|
||||
|
||||
fireEvent.paste(input, {
|
||||
clipboardData: {
|
||||
getData: () => "alice@example.com, bob@example.com",
|
||||
},
|
||||
});
|
||||
getData: () => 'alice@example.com, bob@example.com'
|
||||
}
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ email: "alice@example.com" }),
|
||||
expect.objectContaining({ email: "bob@example.com" }),
|
||||
expect.objectContaining({ email: 'alice@example.com' }),
|
||||
expect.objectContaining({ email: 'bob@example.com' })
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("splits pasted semicolon-separated emails", async () => {
|
||||
const { onChange } = setupFreeSolo();
|
||||
const input = screen.getByRole("combobox");
|
||||
it('splits pasted semicolon-separated emails', async () => {
|
||||
const { onChange } = setupFreeSolo()
|
||||
const input = screen.getByRole('combobox')
|
||||
|
||||
fireEvent.paste(input, {
|
||||
clipboardData: {
|
||||
getData: () => "alice@example.com;bob@example.com",
|
||||
},
|
||||
});
|
||||
getData: () => 'alice@example.com;bob@example.com'
|
||||
}
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ email: "alice@example.com" }),
|
||||
expect.objectContaining({ email: "bob@example.com" }),
|
||||
expect.objectContaining({ email: 'alice@example.com' }),
|
||||
expect.objectContaining({ email: 'bob@example.com' })
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("splits pasted newline-separated emails", async () => {
|
||||
const { onChange } = setupFreeSolo();
|
||||
const input = screen.getByRole("combobox");
|
||||
it('splits pasted newline-separated emails', async () => {
|
||||
const { onChange } = setupFreeSolo()
|
||||
const input = screen.getByRole('combobox')
|
||||
|
||||
fireEvent.paste(input, {
|
||||
clipboardData: {
|
||||
getData: () => "alice@example.com\nbob@example.com",
|
||||
},
|
||||
});
|
||||
getData: () => 'alice@example.com\nbob@example.com'
|
||||
}
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ email: "alice@example.com" }),
|
||||
expect.objectContaining({ email: "bob@example.com" }),
|
||||
expect.objectContaining({ email: 'alice@example.com' }),
|
||||
expect.objectContaining({ email: 'bob@example.com' })
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("splits pasted space-separated emails", async () => {
|
||||
const { onChange } = setupFreeSolo();
|
||||
const input = screen.getByRole("combobox");
|
||||
it('splits pasted space-separated emails', async () => {
|
||||
const { onChange } = setupFreeSolo()
|
||||
const input = screen.getByRole('combobox')
|
||||
|
||||
fireEvent.paste(input, {
|
||||
clipboardData: {
|
||||
getData: () => "alice@example.com bob@example.com",
|
||||
},
|
||||
});
|
||||
getData: () => 'alice@example.com bob@example.com'
|
||||
}
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ email: "alice@example.com" }),
|
||||
expect.objectContaining({ email: "bob@example.com" }),
|
||||
expect.objectContaining({ email: 'alice@example.com' }),
|
||||
expect.objectContaining({ email: 'bob@example.com' })
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("skips duplicate emails already in selectedUsers", async () => {
|
||||
it('skips duplicate emails already in selectedUsers', async () => {
|
||||
const existing: User = {
|
||||
email: "alice@example.com",
|
||||
displayName: "Alice",
|
||||
};
|
||||
const { onChange } = setupFreeSolo([existing]);
|
||||
const input = screen.getByRole("combobox");
|
||||
email: 'alice@example.com',
|
||||
displayName: 'Alice'
|
||||
}
|
||||
const { onChange } = setupFreeSolo([existing])
|
||||
const input = screen.getByRole('combobox')
|
||||
|
||||
fireEvent.paste(input, {
|
||||
clipboardData: {
|
||||
getData: () => "alice@example.com, bob@example.com",
|
||||
},
|
||||
});
|
||||
getData: () => 'alice@example.com, bob@example.com'
|
||||
}
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.arrayContaining([
|
||||
existing,
|
||||
expect.objectContaining({ email: "bob@example.com" }),
|
||||
expect.objectContaining({ email: 'bob@example.com' })
|
||||
])
|
||||
);
|
||||
)
|
||||
// Should NOT have alice duplicated
|
||||
const call = onChange.mock.calls[0][1];
|
||||
const call = onChange.mock.calls[0][1]
|
||||
const aliceCount = call.filter(
|
||||
(u: User) => u.email === "alice@example.com"
|
||||
).length;
|
||||
expect(aliceCount).toBe(1);
|
||||
});
|
||||
});
|
||||
(u: User) => u.email === 'alice@example.com'
|
||||
).length
|
||||
expect(aliceCount).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
it("leaves invalid text in input when some emails are invalid", async () => {
|
||||
const { onChange } = setupFreeSolo();
|
||||
const input = screen.getByRole("combobox");
|
||||
it('leaves invalid text in input when some emails are invalid', async () => {
|
||||
const { onChange } = setupFreeSolo()
|
||||
const input = screen.getByRole('combobox')
|
||||
|
||||
fireEvent.paste(input, {
|
||||
clipboardData: {
|
||||
getData: () => "alice@example.com, not-an-email, bob@example.com",
|
||||
},
|
||||
});
|
||||
getData: () => 'alice@example.com, not-an-email, bob@example.com'
|
||||
}
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ email: "alice@example.com" }),
|
||||
expect.objectContaining({ email: "bob@example.com" }),
|
||||
expect.objectContaining({ email: 'alice@example.com' }),
|
||||
expect.objectContaining({ email: 'bob@example.com' })
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("does not intercept paste of a single email", async () => {
|
||||
const { onChange } = setupFreeSolo();
|
||||
const input = screen.getByRole("combobox");
|
||||
it('does not intercept paste of a single email', async () => {
|
||||
const { onChange } = setupFreeSolo()
|
||||
const input = screen.getByRole('combobox')
|
||||
|
||||
fireEvent.paste(input, {
|
||||
clipboardData: {
|
||||
getData: () => "alice@example.com",
|
||||
},
|
||||
});
|
||||
getData: () => 'alice@example.com'
|
||||
}
|
||||
})
|
||||
|
||||
// Single email should NOT trigger multi-paste handler
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
expect(onChange).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it("retains input value when field loses focus (blur)", async () => {
|
||||
let resolveSearch: (value: User[]) => void;
|
||||
const searchPromise = new Promise<User[]>((resolve) => {
|
||||
resolveSearch = resolve;
|
||||
});
|
||||
mockedSearchUsers.mockReturnValueOnce(searchPromise);
|
||||
it('retains input value when field loses focus (blur)', async () => {
|
||||
let resolveSearch: (value: User[]) => void
|
||||
const searchPromise = new Promise<User[]>(resolve => {
|
||||
resolveSearch = resolve
|
||||
})
|
||||
mockedSearchUsers.mockReturnValueOnce(searchPromise)
|
||||
await act(async () => {
|
||||
setup();
|
||||
});
|
||||
setup()
|
||||
})
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
const input = screen.getByRole('combobox')
|
||||
|
||||
await act(async () => {
|
||||
userEvent.type(input, "Test");
|
||||
});
|
||||
userEvent.type(input, 'Test')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedSearchUsers).toHaveBeenCalledWith("Test", ["user"]);
|
||||
});
|
||||
expect(mockedSearchUsers).toHaveBeenCalledWith('Test', ['user'])
|
||||
})
|
||||
|
||||
expect(input).toHaveValue("Test");
|
||||
expect(input).toHaveValue('Test')
|
||||
|
||||
await act(async () => {
|
||||
input.blur();
|
||||
});
|
||||
input.blur()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(input).toHaveValue("Test");
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(input).toHaveValue('Test')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
import { RootState } from "@/app/store";
|
||||
import RepeatEvent from "@/components/Event/EventRepeat";
|
||||
import * as eventThunks from "@/features/Calendars/services";
|
||||
import EventPopover from "@/features/Events/EventModal";
|
||||
import { RepetitionObject } from "@/features/Events/EventsTypes";
|
||||
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
|
||||
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
import { RootState } from '@/app/store'
|
||||
import RepeatEvent from '@/components/Event/EventRepeat'
|
||||
import * as eventThunks from '@/features/Calendars/services'
|
||||
import EventPopover from '@/features/Events/EventModal'
|
||||
import { RepetitionObject } from '@/features/Events/EventsTypes'
|
||||
import { CalendarApi, DateSelectArg } from '@fullcalendar/core'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
const baseRepetition: RepetitionObject = {
|
||||
freq: "",
|
||||
freq: '',
|
||||
interval: 1,
|
||||
occurrences: 0,
|
||||
endDate: "",
|
||||
};
|
||||
endDate: ''
|
||||
}
|
||||
|
||||
const mockOnClose = jest.fn();
|
||||
const mockSetSelectedRange = jest.fn();
|
||||
const mockOnClose = jest.fn()
|
||||
const mockSetSelectedRange = jest.fn()
|
||||
const mockCalendarRef = {
|
||||
current: { select: jest.fn() } as unknown as CalendarApi,
|
||||
};
|
||||
current: { select: jest.fn() } as unknown as CalendarApi
|
||||
}
|
||||
|
||||
const preloadedState = {
|
||||
user: {
|
||||
userData: {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||
openpaasId: "667037022b752d0026472254",
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro',
|
||||
openpaasId: '667037022b752d0026472254'
|
||||
},
|
||||
organiserData: {
|
||||
cn: "test",
|
||||
cal_address: "test@test.com",
|
||||
},
|
||||
cn: 'test',
|
||||
cal_address: 'test@test.com'
|
||||
}
|
||||
},
|
||||
calendars: {
|
||||
list: {
|
||||
"667037022b752d0026472254/cal1": {
|
||||
id: "667037022b752d0026472254/cal1",
|
||||
name: "Calendar 1",
|
||||
color: "#FF0000",
|
||||
},
|
||||
"667037022b752d0026472254/cal2": {
|
||||
id: "667037022b752d0026472254/cal2",
|
||||
name: "Calendar 2",
|
||||
color: "#00FF00",
|
||||
'667037022b752d0026472254/cal1': {
|
||||
id: '667037022b752d0026472254/cal1',
|
||||
name: 'Calendar 1',
|
||||
color: '#FF0000'
|
||||
},
|
||||
'667037022b752d0026472254/cal2': {
|
||||
id: '667037022b752d0026472254/cal2',
|
||||
name: 'Calendar 2',
|
||||
color: '#00FF00'
|
||||
}
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
};
|
||||
pending: false
|
||||
}
|
||||
}
|
||||
|
||||
const defaultSelectedRange = {
|
||||
startStr: "2025-07-18T09:00",
|
||||
endStr: "2025-07-18T10:00",
|
||||
start: new Date("2025-07-18T09:00"),
|
||||
end: new Date("2025-07-18T10:00"),
|
||||
startStr: '2025-07-18T09:00',
|
||||
endStr: '2025-07-18T10:00',
|
||||
start: new Date('2025-07-18T09:00'),
|
||||
end: new Date('2025-07-18T10:00'),
|
||||
allDay: false,
|
||||
resource: undefined,
|
||||
} as unknown as DateSelectArg;
|
||||
resource: undefined
|
||||
} as unknown as DateSelectArg
|
||||
|
||||
function setupRepeatEvent(
|
||||
props?: Partial<RepetitionObject>,
|
||||
state?: RootState
|
||||
) {
|
||||
const setRepetition = jest.fn();
|
||||
const setRepetition = jest.fn()
|
||||
renderWithProviders(
|
||||
<RepeatEvent
|
||||
repetition={{ ...baseRepetition, ...props }}
|
||||
@@ -72,20 +72,20 @@ function setupRepeatEvent(
|
||||
isOwn={true}
|
||||
/>,
|
||||
state
|
||||
);
|
||||
return { setRepetition };
|
||||
)
|
||||
return { setRepetition }
|
||||
}
|
||||
|
||||
async function setupEventPopover() {
|
||||
jest
|
||||
.spyOn(crypto, "randomUUID")
|
||||
.mockReturnValue("fixed-uuid-with-correct-format");
|
||||
.spyOn(crypto, 'randomUUID')
|
||||
.mockReturnValue('fixed-uuid-with-correct-format')
|
||||
const originalDateResolvedOptions =
|
||||
new Intl.DateTimeFormat().resolvedOptions();
|
||||
jest.spyOn(Intl.DateTimeFormat.prototype, "resolvedOptions").mockReturnValue({
|
||||
new Intl.DateTimeFormat().resolvedOptions()
|
||||
jest.spyOn(Intl.DateTimeFormat.prototype, 'resolvedOptions').mockReturnValue({
|
||||
...originalDateResolvedOptions,
|
||||
timeZone: "UTC",
|
||||
});
|
||||
timeZone: 'UTC'
|
||||
})
|
||||
|
||||
renderWithProviders(
|
||||
<EventPopover
|
||||
@@ -97,324 +97,320 @@ async function setupEventPopover() {
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
preloadedState
|
||||
);
|
||||
)
|
||||
|
||||
// Fill in title
|
||||
const titleInput = screen.getByLabelText("event.form.title");
|
||||
fireEvent.change(titleInput, { target: { value: "Meeting" } });
|
||||
const titleInput = screen.getByLabelText('event.form.title')
|
||||
fireEvent.change(titleInput, { target: { value: 'Meeting' } })
|
||||
|
||||
// Click More options to expand the dialog
|
||||
const showMoreButton = screen.getByRole("button", {
|
||||
name: "common.moreOptions",
|
||||
});
|
||||
fireEvent.click(showMoreButton);
|
||||
const showMoreButton = screen.getByRole('button', {
|
||||
name: 'common.moreOptions'
|
||||
})
|
||||
fireEvent.click(showMoreButton)
|
||||
|
||||
// Check Repeat checkbox to show repeat options
|
||||
const repeatCheckbox = screen.getByLabelText("event.form.repeat");
|
||||
fireEvent.click(repeatCheckbox);
|
||||
const repeatCheckbox = screen.getByLabelText('event.form.repeat')
|
||||
fireEvent.click(repeatCheckbox)
|
||||
|
||||
// Wait for RepeatEvent component to be rendered
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("event.repeat.frequency.weeks")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('event.repeat.frequency.weeks')).toBeInTheDocument()
|
||||
})
|
||||
}
|
||||
|
||||
async function expectRRule(expected: Partial<RepetitionObject>) {
|
||||
const spy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
const saveButton = screen.getByRole("button", { name: /save/i });
|
||||
act(() => fireEvent.click(saveButton));
|
||||
await waitFor(() => expect(spy).toHaveBeenCalled());
|
||||
.spyOn(eventThunks, 'putEventAsync')
|
||||
.mockImplementation(payload => {
|
||||
const promise = Promise.resolve(payload)
|
||||
;(promise as any).unwrap = () => promise
|
||||
return () => promise as any
|
||||
})
|
||||
const saveButton = screen.getByRole('button', { name: /save/i })
|
||||
act(() => fireEvent.click(saveButton))
|
||||
await waitFor(() => expect(spy).toHaveBeenCalled())
|
||||
|
||||
const received = spy.mock.calls[0][0];
|
||||
expect(received.newEvent.repetition).toMatchObject(expected);
|
||||
const received = spy.mock.calls[0][0]
|
||||
expect(received.newEvent.repetition).toMatchObject(expected)
|
||||
}
|
||||
|
||||
describe("RepeatEvent Component", () => {
|
||||
describe('RepeatEvent Component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
jest.clearAllMocks()
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it("renders with no repetition by default", () => {
|
||||
setupRepeatEvent();
|
||||
it('renders with no repetition by default', () => {
|
||||
setupRepeatEvent()
|
||||
|
||||
// Check that interval input shows default value
|
||||
const intervalInput = screen.getByTestId("repeat-interval");
|
||||
expect(intervalInput).toBeInTheDocument();
|
||||
const intervalInput = screen.getByTestId('repeat-interval')
|
||||
expect(intervalInput).toBeInTheDocument()
|
||||
|
||||
// Check that frequency dropdown shows default value
|
||||
const frequencySelect = screen.getByRole("combobox");
|
||||
expect(frequencySelect).toBeInTheDocument();
|
||||
});
|
||||
const frequencySelect = screen.getByRole('combobox')
|
||||
expect(frequencySelect).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("allows selecting repetition frequency", () => {
|
||||
const { setRepetition } = setupRepeatEvent();
|
||||
it('allows selecting repetition frequency', () => {
|
||||
const { setRepetition } = setupRepeatEvent()
|
||||
|
||||
// Click on frequency dropdown
|
||||
const frequencySelect = screen.getByRole("combobox");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const frequencySelect = screen.getByRole('combobox')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
|
||||
// Select Week(s)
|
||||
const weeklyOption = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.click(weeklyOption);
|
||||
const weeklyOption = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.click(weeklyOption)
|
||||
|
||||
expect(setRepetition).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ freq: "weekly" })
|
||||
);
|
||||
});
|
||||
expect.objectContaining({ freq: 'weekly' })
|
||||
)
|
||||
})
|
||||
|
||||
it("renders interval input when frequency is selected", () => {
|
||||
setupRepeatEvent({ freq: "daily" });
|
||||
it('renders interval input when frequency is selected', () => {
|
||||
setupRepeatEvent({ freq: 'daily' })
|
||||
|
||||
const intervalInput = screen.getByTestId("repeat-interval");
|
||||
expect(intervalInput).toBeInTheDocument();
|
||||
});
|
||||
const intervalInput = screen.getByTestId('repeat-interval')
|
||||
expect(intervalInput).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("updates interval value", () => {
|
||||
const { setRepetition } = setupRepeatEvent();
|
||||
it('updates interval value', () => {
|
||||
const { setRepetition } = setupRepeatEvent()
|
||||
|
||||
const intervalInput = screen.getByTestId("repeat-interval");
|
||||
fireEvent.change(intervalInput, { target: { value: "3" } });
|
||||
const intervalInput = screen.getByTestId('repeat-interval')
|
||||
fireEvent.change(intervalInput, { target: { value: '3' } })
|
||||
|
||||
expect(setRepetition).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ interval: 3 })
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("toggles day selection for weekly frequency", () => {
|
||||
const { setRepetition } = setupRepeatEvent({ freq: "weekly" });
|
||||
it('toggles day selection for weekly frequency', () => {
|
||||
const { setRepetition } = setupRepeatEvent({ freq: 'weekly' })
|
||||
|
||||
const mondayChip = screen.getByLabelText("event.repeat.days.monday");
|
||||
fireEvent.click(mondayChip);
|
||||
const mondayChip = screen.getByLabelText('event.repeat.days.monday')
|
||||
fireEvent.click(mondayChip)
|
||||
|
||||
expect(setRepetition).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ byday: ["MO"] })
|
||||
);
|
||||
});
|
||||
});
|
||||
expect.objectContaining({ byday: ['MO'] })
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Repeat Event Integration Tests", () => {
|
||||
describe('Repeat Event Integration Tests', () => {
|
||||
// Increase timeout for all tests in this describe block
|
||||
jest.setTimeout(30000);
|
||||
jest.setTimeout(30000)
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
jest.clearAllMocks()
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it("sends correct CalendarEvent payload for daily repeat", async () => {
|
||||
await setupEventPopover();
|
||||
it('sends correct CalendarEvent payload for daily repeat', async () => {
|
||||
await setupEventPopover()
|
||||
|
||||
// When Repeat checkbox is checked, repetition is set to empty object
|
||||
// We need to set the frequency manually
|
||||
const frequencySelect = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const dailyOption = screen.getByRole("option", {
|
||||
name: "event.repeat.frequency.days",
|
||||
});
|
||||
fireEvent.click(dailyOption);
|
||||
const frequencySelect = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
const dailyOption = screen.getByRole('option', {
|
||||
name: 'event.repeat.frequency.days'
|
||||
})
|
||||
fireEvent.click(dailyOption)
|
||||
|
||||
await expectRRule({ freq: "daily", interval: 1 });
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true);
|
||||
});
|
||||
await expectRRule({ freq: 'daily', interval: 1 })
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("sends correct API payload for repeat daily with 2 day interval", async () => {
|
||||
await setupEventPopover();
|
||||
it('sends correct API payload for repeat daily with 2 day interval', async () => {
|
||||
await setupEventPopover()
|
||||
|
||||
// Ensure frequency is daily
|
||||
const frequencySelect = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const dailyOption = screen.getByRole("option", {
|
||||
name: "event.repeat.frequency.days",
|
||||
});
|
||||
fireEvent.click(dailyOption);
|
||||
const frequencySelect = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
const dailyOption = screen.getByRole('option', {
|
||||
name: 'event.repeat.frequency.days'
|
||||
})
|
||||
fireEvent.click(dailyOption)
|
||||
|
||||
// Set interval to 2
|
||||
const intervalInput = screen.getByTestId("repeat-interval");
|
||||
fireEvent.change(intervalInput, { target: { value: "2" } });
|
||||
const intervalInput = screen.getByTestId('repeat-interval')
|
||||
fireEvent.change(intervalInput, { target: { value: '2' } })
|
||||
|
||||
await expectRRule({ freq: "daily", interval: 2 });
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true);
|
||||
});
|
||||
await expectRRule({ freq: 'daily', interval: 2 })
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("sends correct API payload for repeat daily for 5 repetitions", async () => {
|
||||
await setupEventPopover();
|
||||
it('sends correct API payload for repeat daily for 5 repetitions', async () => {
|
||||
await setupEventPopover()
|
||||
|
||||
// Ensure frequency is daily
|
||||
const frequencySelect = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const dailyOption = screen.getByRole("option", {
|
||||
name: "event.repeat.frequency.days",
|
||||
});
|
||||
fireEvent.click(dailyOption);
|
||||
const frequencySelect = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
const dailyOption = screen.getByRole('option', {
|
||||
name: 'event.repeat.frequency.days'
|
||||
})
|
||||
fireEvent.click(dailyOption)
|
||||
|
||||
// Select "After" end option
|
||||
const afterRadio = screen.getByLabelText(/after/i);
|
||||
fireEvent.click(afterRadio);
|
||||
const afterRadio = screen.getByLabelText(/after/i)
|
||||
fireEvent.click(afterRadio)
|
||||
|
||||
// Set occurrences to 5
|
||||
const occurrencesInput = screen.getByTestId("occurrences-input");
|
||||
fireEvent.change(occurrencesInput, { target: { value: "5" } });
|
||||
const occurrencesInput = screen.getByTestId('occurrences-input')
|
||||
fireEvent.change(occurrencesInput, { target: { value: '5' } })
|
||||
|
||||
await expectRRule({ freq: "daily", interval: 1, occurrences: 5 });
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true);
|
||||
});
|
||||
await expectRRule({ freq: 'daily', interval: 1, occurrences: 5 })
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("sends correct API payload for repeat daily until specific date", async () => {
|
||||
await setupEventPopover();
|
||||
it('sends correct API payload for repeat daily until specific date', async () => {
|
||||
await setupEventPopover()
|
||||
|
||||
// Ensure frequency is daily
|
||||
const frequencySelect = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const dailyOption = screen.getByRole("option", {
|
||||
name: "event.repeat.frequency.days",
|
||||
});
|
||||
fireEvent.click(dailyOption);
|
||||
const frequencySelect = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
const dailyOption = screen.getByRole('option', {
|
||||
name: 'event.repeat.frequency.days'
|
||||
})
|
||||
fireEvent.click(dailyOption)
|
||||
|
||||
// Select "On" end option
|
||||
const onRadio = screen
|
||||
.getAllByLabelText(/on/i)
|
||||
.find((el) => el.type === "radio");
|
||||
fireEvent.click(onRadio!);
|
||||
.find(el => el.type === 'radio')
|
||||
fireEvent.click(onRadio!)
|
||||
|
||||
// End date is set by UI to some valid date string (YYYY-MM-DD)
|
||||
await expectRRule({
|
||||
freq: "daily",
|
||||
freq: 'daily',
|
||||
interval: 1,
|
||||
endDate: expect.any(String),
|
||||
});
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true);
|
||||
});
|
||||
endDate: expect.any(String)
|
||||
})
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("sends correct API payload for repeat weekly on specific days", async () => {
|
||||
await setupEventPopover();
|
||||
it('sends correct API payload for repeat weekly on specific days', async () => {
|
||||
await setupEventPopover()
|
||||
|
||||
// Select Week(s) frequency
|
||||
const frequencySelect = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const weeklyOption = screen.getByRole("option", {
|
||||
name: "event.repeat.frequency.weeks",
|
||||
});
|
||||
fireEvent.click(weeklyOption);
|
||||
const frequencySelect = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
const weeklyOption = screen.getByRole('option', {
|
||||
name: 'event.repeat.frequency.weeks'
|
||||
})
|
||||
fireEvent.click(weeklyOption)
|
||||
|
||||
// Select Thursday
|
||||
const thursdayCheckbox = screen.getByLabelText(
|
||||
"event.repeat.days.thursday"
|
||||
);
|
||||
fireEvent.click(thursdayCheckbox);
|
||||
const thursdayCheckbox = screen.getByLabelText('event.repeat.days.thursday')
|
||||
fireEvent.click(thursdayCheckbox)
|
||||
|
||||
await expectRRule({
|
||||
freq: "weekly",
|
||||
freq: 'weekly',
|
||||
interval: 1,
|
||||
byday: ["FR", "TH"],
|
||||
});
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true);
|
||||
});
|
||||
byday: ['FR', 'TH']
|
||||
})
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("sends correct API payload for repeat weekly with 3 week interval", async () => {
|
||||
await setupEventPopover();
|
||||
it('sends correct API payload for repeat weekly with 3 week interval', async () => {
|
||||
await setupEventPopover()
|
||||
|
||||
// Select Week(s) frequency
|
||||
const frequencySelect = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const weeklyOption = screen.getByRole("option", {
|
||||
name: "event.repeat.frequency.weeks",
|
||||
});
|
||||
fireEvent.click(weeklyOption);
|
||||
const frequencySelect = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
const weeklyOption = screen.getByRole('option', {
|
||||
name: 'event.repeat.frequency.weeks'
|
||||
})
|
||||
fireEvent.click(weeklyOption)
|
||||
|
||||
// Set interval to 3
|
||||
const intervalInput = screen.getByTestId("repeat-interval");
|
||||
fireEvent.change(intervalInput, { target: { value: "3" } });
|
||||
const intervalInput = screen.getByTestId('repeat-interval')
|
||||
fireEvent.change(intervalInput, { target: { value: '3' } })
|
||||
|
||||
await expectRRule({ freq: "weekly", interval: 3 });
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true);
|
||||
});
|
||||
await expectRRule({ freq: 'weekly', interval: 3 })
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("sends correct API payload for repeat monthly", async () => {
|
||||
await setupEventPopover();
|
||||
it('sends correct API payload for repeat monthly', async () => {
|
||||
await setupEventPopover()
|
||||
|
||||
// Select Month(s) frequency
|
||||
const frequencySelect = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const monthlyOption = screen.getByRole("option", {
|
||||
name: "event.repeat.frequency.months",
|
||||
});
|
||||
fireEvent.click(monthlyOption);
|
||||
const frequencySelect = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
const monthlyOption = screen.getByRole('option', {
|
||||
name: 'event.repeat.frequency.months'
|
||||
})
|
||||
fireEvent.click(monthlyOption)
|
||||
|
||||
await expectRRule({ freq: "monthly", interval: 1 });
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true);
|
||||
});
|
||||
await expectRRule({ freq: 'monthly', interval: 1 })
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("sends correct API payload for repeat monthly and end after 5 occurrences", async () => {
|
||||
await setupEventPopover();
|
||||
it('sends correct API payload for repeat monthly and end after 5 occurrences', async () => {
|
||||
await setupEventPopover()
|
||||
|
||||
// Select Month(s) frequency
|
||||
const frequencySelect = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const monthlyOption = screen.getByRole("option", {
|
||||
name: "event.repeat.frequency.months",
|
||||
});
|
||||
fireEvent.click(monthlyOption);
|
||||
const frequencySelect = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
const monthlyOption = screen.getByRole('option', {
|
||||
name: 'event.repeat.frequency.months'
|
||||
})
|
||||
fireEvent.click(monthlyOption)
|
||||
|
||||
// Select "After" end option
|
||||
const afterRadio = screen.getByLabelText(/after/i);
|
||||
fireEvent.click(afterRadio);
|
||||
const afterRadio = screen.getByLabelText(/after/i)
|
||||
fireEvent.click(afterRadio)
|
||||
|
||||
// Set occurrences to 5
|
||||
const occurrencesInput = screen.getByTestId("occurrences-input");
|
||||
fireEvent.change(occurrencesInput, { target: { value: "5" } });
|
||||
const occurrencesInput = screen.getByTestId('occurrences-input')
|
||||
fireEvent.change(occurrencesInput, { target: { value: '5' } })
|
||||
|
||||
await expectRRule({ freq: "monthly", interval: 1, occurrences: 5 });
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true);
|
||||
});
|
||||
await expectRRule({ freq: 'monthly', interval: 1, occurrences: 5 })
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("sends correct API payload for repeat yearly", async () => {
|
||||
await setupEventPopover();
|
||||
it('sends correct API payload for repeat yearly', async () => {
|
||||
await setupEventPopover()
|
||||
|
||||
// Select Year(s) frequency
|
||||
const frequencySelect = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const yearlyOption = screen.getByRole("option", {
|
||||
name: "event.repeat.frequency.years",
|
||||
});
|
||||
fireEvent.click(yearlyOption);
|
||||
const frequencySelect = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
const yearlyOption = screen.getByRole('option', {
|
||||
name: 'event.repeat.frequency.years'
|
||||
})
|
||||
fireEvent.click(yearlyOption)
|
||||
|
||||
await expectRRule({ freq: "yearly", interval: 1 });
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true);
|
||||
});
|
||||
await expectRRule({ freq: 'yearly', interval: 1 })
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("sends correct API payload for repeat yearly with end option changes", async () => {
|
||||
await setupEventPopover();
|
||||
it('sends correct API payload for repeat yearly with end option changes', async () => {
|
||||
await setupEventPopover()
|
||||
|
||||
// Select Year(s) frequency
|
||||
const frequencySelect = screen.getByText("event.repeat.frequency.weeks");
|
||||
fireEvent.mouseDown(frequencySelect);
|
||||
const yearlyOption = screen.getByRole("option", {
|
||||
name: "event.repeat.frequency.years",
|
||||
});
|
||||
fireEvent.click(yearlyOption);
|
||||
const frequencySelect = screen.getByText('event.repeat.frequency.weeks')
|
||||
fireEvent.mouseDown(frequencySelect)
|
||||
const yearlyOption = screen.getByRole('option', {
|
||||
name: 'event.repeat.frequency.years'
|
||||
})
|
||||
fireEvent.click(yearlyOption)
|
||||
|
||||
// First choose "After" with 5 occurrences
|
||||
const afterRadio = screen.getByLabelText(/after/i);
|
||||
fireEvent.click(afterRadio);
|
||||
const occurrencesInput = screen.getByTestId("occurrences-input");
|
||||
fireEvent.change(occurrencesInput, { target: { value: "5" } });
|
||||
const afterRadio = screen.getByLabelText(/after/i)
|
||||
fireEvent.click(afterRadio)
|
||||
const occurrencesInput = screen.getByTestId('occurrences-input')
|
||||
fireEvent.change(occurrencesInput, { target: { value: '5' } })
|
||||
|
||||
// Then change mind and choose "Never"
|
||||
const neverRadio = screen.getByLabelText(/never/i);
|
||||
fireEvent.click(neverRadio);
|
||||
const neverRadio = screen.getByLabelText(/never/i)
|
||||
fireEvent.click(neverRadio)
|
||||
|
||||
await expectRRule({
|
||||
freq: "yearly",
|
||||
interval: 1,
|
||||
});
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
freq: 'yearly',
|
||||
interval: 1
|
||||
})
|
||||
expect(mockOnClose).toHaveBeenCalledWith(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,224 +1,219 @@
|
||||
import {
|
||||
ResourceSearch,
|
||||
Resource,
|
||||
} from "@/components/Attendees/ResourceSearch";
|
||||
import { searchUsers } from "@/features/User/userAPI";
|
||||
import { act, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
import { ResourceSearch, Resource } from '@/components/Attendees/ResourceSearch'
|
||||
import { searchUsers } from '@/features/User/userAPI'
|
||||
import { act, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
jest.mock("@/features/User/userAPI");
|
||||
const mockedSearchUsers = searchUsers as jest.MockedFunction<
|
||||
typeof searchUsers
|
||||
>;
|
||||
jest.mock('@/features/User/userAPI')
|
||||
const mockedSearchUsers = searchUsers as jest.MockedFunction<typeof searchUsers>
|
||||
|
||||
describe("ResourceSearch", () => {
|
||||
describe('ResourceSearch', () => {
|
||||
const baseResource: Resource = {
|
||||
displayName: "Projector Room",
|
||||
};
|
||||
displayName: 'Projector Room'
|
||||
}
|
||||
|
||||
function setup(
|
||||
selectedResources: Resource[] = [],
|
||||
props?: Partial<React.ComponentProps<typeof ResourceSearch>>
|
||||
) {
|
||||
const onChange = jest.fn();
|
||||
const onChange = jest.fn()
|
||||
renderWithProviders(
|
||||
<ResourceSearch
|
||||
objectTypes={["resource"]}
|
||||
objectTypes={['resource']}
|
||||
selectedResources={selectedResources}
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return { onChange };
|
||||
)
|
||||
return { onChange }
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
mockedSearchUsers.mockReset();
|
||||
});
|
||||
jest.useFakeTimers()
|
||||
mockedSearchUsers.mockReset()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it("calls searchUsers after debounce when typing", async () => {
|
||||
it('calls searchUsers after debounce when typing', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([
|
||||
baseResource,
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>);
|
||||
setup();
|
||||
baseResource
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>)
|
||||
setup()
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Room");
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Room')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedSearchUsers).toHaveBeenCalledWith("Room", ["resource"]);
|
||||
});
|
||||
});
|
||||
expect(mockedSearchUsers).toHaveBeenCalledWith('Room', ['resource'])
|
||||
})
|
||||
})
|
||||
|
||||
it("renders search results and allows selection", async () => {
|
||||
it('renders search results and allows selection', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([
|
||||
baseResource,
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>);
|
||||
const { onChange } = setup();
|
||||
baseResource
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>)
|
||||
const { onChange } = setup()
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Room");
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Room')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
const option = await screen.findByText("Projector Room");
|
||||
await userEvent.click(option);
|
||||
const option = await screen.findByText('Projector Room')
|
||||
await userEvent.click(option)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
expect(onChange).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it("does not show already selected resources in options", async () => {
|
||||
it('does not show already selected resources in options', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([
|
||||
baseResource,
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>);
|
||||
setup([baseResource]);
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Projector");
|
||||
baseResource
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>)
|
||||
setup([baseResource])
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Projector')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
// It shouldn't be in the dropdown options anymore
|
||||
const options = screen.queryAllByRole("option");
|
||||
const options = screen.queryAllByRole('option')
|
||||
expect(
|
||||
options.find((opt) => opt.textContent === "Projector Room")
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
options.find(opt => opt.textContent === 'Projector Room')
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
it("respects disabled state", () => {
|
||||
setup([], { disabled: true });
|
||||
expect(screen.getByRole("combobox")).toBeDisabled();
|
||||
});
|
||||
it('respects disabled state', () => {
|
||||
setup([], { disabled: true })
|
||||
expect(screen.getByRole('combobox')).toBeDisabled()
|
||||
})
|
||||
|
||||
it("no options doesn't show dropdown when input is empty", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([
|
||||
baseResource,
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>);
|
||||
setup();
|
||||
const input = screen.getByRole("combobox");
|
||||
baseResource
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>)
|
||||
setup()
|
||||
const input = screen.getByRole('combobox')
|
||||
|
||||
await userEvent.type(input, "Room");
|
||||
await userEvent.type(input, 'Room')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByRole('listbox')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
await userEvent.clear(input);
|
||||
await userEvent.clear(input)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it("shows 'No results' when search succeeds but returns empty array", async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([]);
|
||||
setup();
|
||||
mockedSearchUsers.mockResolvedValueOnce([])
|
||||
setup()
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Room");
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Room')
|
||||
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
const noResults = await screen.findByText(
|
||||
"resourceSearch.noResults",
|
||||
'resourceSearch.noResults',
|
||||
{},
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
expect(noResults).toBeInTheDocument();
|
||||
});
|
||||
)
|
||||
expect(noResults).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("clears options when search fails and shows error snackbar", async () => {
|
||||
it('clears options when search fails and shows error snackbar', async () => {
|
||||
mockedSearchUsers.mockResolvedValueOnce([
|
||||
baseResource,
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>);
|
||||
setup();
|
||||
baseResource
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>)
|
||||
setup()
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Room");
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Room')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
await Promise.resolve();
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Projector Room")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Projector Room')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
mockedSearchUsers.mockRejectedValueOnce(new Error("Network error"));
|
||||
await userEvent.clear(input);
|
||||
await userEvent.type(input, "Error");
|
||||
mockedSearchUsers.mockRejectedValueOnce(new Error('Network error'))
|
||||
await userEvent.clear(input)
|
||||
await userEvent.type(input, 'Error')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
await Promise.resolve();
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
const errorMessage = await screen.findByText("resourceSearch.searchError");
|
||||
expect(errorMessage).toBeInTheDocument();
|
||||
const errorMessage = await screen.findByText('resourceSearch.searchError')
|
||||
expect(errorMessage).toBeInTheDocument()
|
||||
|
||||
expect(screen.queryByText("Projector Room")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Projector Room')).not.toBeInTheDocument()
|
||||
|
||||
mockedSearchUsers.mockResolvedValueOnce([
|
||||
baseResource,
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>);
|
||||
await userEvent.clear(input);
|
||||
await userEvent.type(input, "Room");
|
||||
baseResource
|
||||
] as unknown as Awaited<ReturnType<typeof searchUsers>>)
|
||||
await userEvent.clear(input)
|
||||
await userEvent.type(input, 'Room')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
await Promise.resolve();
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Projector Room")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
expect(screen.getByText('Projector Room')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it("shows loading text when searching", async () => {
|
||||
let resolveSearch: (value: Resource[]) => void;
|
||||
const searchPromise = new Promise<Resource[]>((resolve) => {
|
||||
resolveSearch = resolve;
|
||||
});
|
||||
it('shows loading text when searching', async () => {
|
||||
let resolveSearch: (value: Resource[]) => void
|
||||
const searchPromise = new Promise<Resource[]>(resolve => {
|
||||
resolveSearch = resolve
|
||||
})
|
||||
mockedSearchUsers.mockReturnValueOnce(
|
||||
searchPromise as unknown as ReturnType<typeof searchUsers>
|
||||
);
|
||||
setup();
|
||||
)
|
||||
setup()
|
||||
|
||||
const input = screen.getByRole("combobox");
|
||||
await userEvent.type(input, "Room");
|
||||
const input = screen.getByRole('combobox')
|
||||
await userEvent.type(input, 'Room')
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
const loadingText = await screen.findByText(
|
||||
"resourceSearch.loading",
|
||||
'resourceSearch.loading',
|
||||
{},
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
expect(loadingText).toBeInTheDocument();
|
||||
)
|
||||
expect(loadingText).toBeInTheDocument()
|
||||
|
||||
await act(async () => {
|
||||
resolveSearch!([baseResource]);
|
||||
await searchPromise;
|
||||
});
|
||||
});
|
||||
});
|
||||
resolveSearch!([baseResource])
|
||||
await searchPromise
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import { ResponsiveDialog } from "@/components/Dialog";
|
||||
import { Button, TextField, TwakeMuiThemeProvider } from "@linagora/twake-mui";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { ResponsiveDialog } from '@/components/Dialog'
|
||||
import { Button, TextField, TwakeMuiThemeProvider } from '@linagora/twake-mui'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
|
||||
describe("ResponsiveDialog", () => {
|
||||
const mockOnClose = jest.fn();
|
||||
const mockOnExpandToggle = jest.fn();
|
||||
describe('ResponsiveDialog', () => {
|
||||
const mockOnClose = jest.fn()
|
||||
const mockOnExpandToggle = jest.fn()
|
||||
|
||||
const renderWithTheme = (ui: React.ReactElement) => {
|
||||
return render(<TwakeMuiThemeProvider>{ui}</TwakeMuiThemeProvider>);
|
||||
};
|
||||
return render(<TwakeMuiThemeProvider>{ui}</TwakeMuiThemeProvider>)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnClose.mockClear();
|
||||
mockOnExpandToggle.mockClear();
|
||||
});
|
||||
mockOnClose.mockClear()
|
||||
mockOnExpandToggle.mockClear()
|
||||
})
|
||||
|
||||
it("renders in normal mode by default", () => {
|
||||
it('renders in normal mode by default', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test Dialog">
|
||||
<TextField label="Name" />
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("Test Dialog")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/name/i)).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Test Dialog')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText(/name/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders title in normal mode", () => {
|
||||
it('renders title in normal mode', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -37,13 +37,13 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("My Title")).toBeInTheDocument();
|
||||
expect(screen.queryByLabelText("show less")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('My Title')).toBeInTheDocument()
|
||||
expect(screen.queryByLabelText('show less')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders back arrow in extended mode", () => {
|
||||
it('renders back arrow in extended mode', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -54,13 +54,13 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.queryByText("My Title")).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText("show less")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByText('My Title')).not.toBeInTheDocument()
|
||||
expect(screen.getByLabelText('show less')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("calls onExpandToggle when back arrow is clicked", () => {
|
||||
it('calls onExpandToggle when back arrow is clicked', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -71,15 +71,15 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
const backButton = screen.getByLabelText("show less");
|
||||
fireEvent.click(backButton);
|
||||
const backButton = screen.getByLabelText('show less')
|
||||
fireEvent.click(backButton)
|
||||
|
||||
expect(mockOnExpandToggle).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(mockOnExpandToggle).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("renders actions when provided", () => {
|
||||
it('renders actions when provided', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -89,38 +89,38 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("Custom Action")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Custom Action')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("does not render actions when not provided", () => {
|
||||
it('does not render actions when not provided', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test">
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
const dialogActions = container.querySelector(".MuiDialogActions-root");
|
||||
expect(dialogActions).not.toBeInTheDocument();
|
||||
});
|
||||
const dialogActions = container.querySelector('.MuiDialogActions-root')
|
||||
expect(dialogActions).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("calls onClose when backdrop is clicked", () => {
|
||||
it('calls onClose when backdrop is clicked', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test">
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
const backdrop = document.querySelector(".MuiBackdrop-root");
|
||||
const backdrop = document.querySelector('.MuiBackdrop-root')
|
||||
if (backdrop) {
|
||||
fireEvent.click(backdrop);
|
||||
fireEvent.click(backdrop)
|
||||
}
|
||||
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
});
|
||||
expect(mockOnClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("applies custom normalMaxWidth", () => {
|
||||
it('applies custom normalMaxWidth', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -130,24 +130,24 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Normal Width Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("Normal Width Content")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Normal Width Content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("wraps children in Stack component", () => {
|
||||
it('wraps children in Stack component', () => {
|
||||
renderWithTheme(
|
||||
<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();
|
||||
});
|
||||
expect(screen.getByLabelText('Field 1')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Field 2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("uses correct spacing in normal mode", () => {
|
||||
it('uses correct spacing in normal mode', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -158,12 +158,12 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Normal Spacing Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("Normal Spacing Content")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Normal Spacing Content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("uses correct spacing in extended mode", () => {
|
||||
it('uses correct spacing in extended mode', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -174,12 +174,12 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Extended Spacing Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("Extended Spacing Content")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Extended Spacing Content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("applies contentSx custom styles", () => {
|
||||
it('applies contentSx custom styles', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -189,28 +189,28 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Custom Styled Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("Custom Styled Content")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Custom Styled Content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("applies titleSx custom styles", () => {
|
||||
it('applies titleSx custom styles', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
onClose={mockOnClose}
|
||||
title="Test"
|
||||
titleSx={{ color: "red" }}
|
||||
titleSx={{ color: 'red' }}
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
const title = screen.getByText("Test");
|
||||
expect(title).toBeInTheDocument();
|
||||
});
|
||||
const title = screen.getByText('Test')
|
||||
expect(title).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("shows dividers when dividers prop is true", () => {
|
||||
it('shows dividers when dividers prop is true', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -220,12 +220,12 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Content with Dividers</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("Content with Dividers")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Content with Dividers')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("does not show back arrow when onExpandToggle is not provided", () => {
|
||||
it('does not show back arrow when onExpandToggle is not provided', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -235,13 +235,13 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.queryByLabelText("show less")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByLabelText('show less')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('Test Title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("accepts custom headerHeight", () => {
|
||||
it('accepts custom headerHeight', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -252,12 +252,12 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Custom Header Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("Custom Header Content")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Custom Header Content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders with custom expandedContentMaxWidth", () => {
|
||||
it('renders with custom expandedContentMaxWidth', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -268,22 +268,22 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Wide Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("Wide Content")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Wide Content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("does not render dialog content when open is false", () => {
|
||||
it('does not render dialog content when open is false', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog open={false} onClose={mockOnClose} title="Test">
|
||||
<div>Test Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.queryByText("Test Content")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByText('Test Content')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders correctly in extended mode", () => {
|
||||
it('renders correctly in extended mode', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -294,13 +294,13 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Extended Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByText("Extended Content")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("show less")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Extended Content')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('show less')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders expand and close icons in normal mode when showHeaderActions is true", () => {
|
||||
it('renders expand and close icons in normal mode when showHeaderActions is true', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -312,13 +312,13 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.getByLabelText("expand")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("close")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByLabelText('expand')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('close')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("does not render header icons when showHeaderActions is false", () => {
|
||||
it('does not render header icons when showHeaderActions is false', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -330,14 +330,14 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
expect(screen.queryByLabelText("expand")).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText("close")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByLabelText('expand')).not.toBeInTheDocument()
|
||||
expect(screen.queryByLabelText('close')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('Test Title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("calls onClose when close icon is clicked", () => {
|
||||
it('calls onClose when close icon is clicked', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -348,15 +348,15 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
const closeButton = screen.getByLabelText("close");
|
||||
fireEvent.click(closeButton);
|
||||
const closeButton = screen.getByLabelText('close')
|
||||
fireEvent.click(closeButton)
|
||||
|
||||
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(mockOnClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("calls onExpandToggle when expand icon is clicked", () => {
|
||||
it('calls onExpandToggle when expand icon is clicked', () => {
|
||||
renderWithTheme(
|
||||
<ResponsiveDialog
|
||||
open={true}
|
||||
@@ -368,11 +368,11 @@ describe("ResponsiveDialog", () => {
|
||||
>
|
||||
<div>Content</div>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
)
|
||||
|
||||
const expandButton = screen.getByLabelText("expand");
|
||||
fireEvent.click(expandButton);
|
||||
const expandButton = screen.getByLabelText('expand')
|
||||
fireEvent.click(expandButton)
|
||||
|
||||
expect(mockOnExpandToggle).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
expect(mockOnExpandToggle).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,102 +1,102 @@
|
||||
import { renderHook, act } from "@testing-library/react";
|
||||
import { useUserSearch } from "@/components/Attendees/useUserSearch";
|
||||
import { searchUsers } from "@/features/User/userAPI";
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { useUserSearch } from '@/components/Attendees/useUserSearch'
|
||||
import { searchUsers } from '@/features/User/userAPI'
|
||||
|
||||
jest.mock("@/features/User/userAPI", () => ({
|
||||
searchUsers: jest.fn(),
|
||||
}));
|
||||
jest.mock('@/features/User/userAPI', () => ({
|
||||
searchUsers: jest.fn()
|
||||
}))
|
||||
|
||||
describe("useUserSearch", () => {
|
||||
const mockSearchUsers = searchUsers as jest.Mock;
|
||||
describe('useUserSearch', () => {
|
||||
const mockSearchUsers = searchUsers as jest.Mock
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
jest.useFakeTimers()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it("should initialize with default values", () => {
|
||||
it('should initialize with default values', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useUserSearch({ objectTypes: ["user"], errorMessage: "Error" })
|
||||
);
|
||||
useUserSearch({ objectTypes: ['user'], errorMessage: 'Error' })
|
||||
)
|
||||
|
||||
expect(result.current.query).toBe("");
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.options).toEqual([]);
|
||||
expect(result.current.hasSearched).toBe(false);
|
||||
expect(result.current.isOpen).toBe(false);
|
||||
expect(result.current.inputError).toBeNull();
|
||||
expect(result.current.snackbarOpen).toBe(false);
|
||||
expect(result.current.snackbarMessage).toBe("");
|
||||
});
|
||||
expect(result.current.query).toBe('')
|
||||
expect(result.current.loading).toBe(false)
|
||||
expect(result.current.options).toEqual([])
|
||||
expect(result.current.hasSearched).toBe(false)
|
||||
expect(result.current.isOpen).toBe(false)
|
||||
expect(result.current.inputError).toBeNull()
|
||||
expect(result.current.snackbarOpen).toBe(false)
|
||||
expect(result.current.snackbarMessage).toBe('')
|
||||
})
|
||||
|
||||
it("should debounce and fetch users when query changes", async () => {
|
||||
const mockUsers = [{ displayName: "John Doe", email: "john@example.com" }];
|
||||
mockSearchUsers.mockResolvedValueOnce(mockUsers);
|
||||
it('should debounce and fetch users when query changes', async () => {
|
||||
const mockUsers = [{ displayName: 'John Doe', email: 'john@example.com' }]
|
||||
mockSearchUsers.mockResolvedValueOnce(mockUsers)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useUserSearch({ objectTypes: ["user"], errorMessage: "Error" })
|
||||
);
|
||||
useUserSearch({ objectTypes: ['user'], errorMessage: 'Error' })
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery("John");
|
||||
});
|
||||
result.current.setQuery('John')
|
||||
})
|
||||
|
||||
expect(result.current.loading).toBe(false); // Before debounce
|
||||
expect(result.current.loading).toBe(false) // Before debounce
|
||||
|
||||
// Wait for the mock promise to resolve within act
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
await Promise.resolve(); // allow microtasks to flush
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
await Promise.resolve() // allow microtasks to flush
|
||||
})
|
||||
|
||||
expect(mockSearchUsers).toHaveBeenCalledWith("John", ["user"]);
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.options).toEqual(mockUsers);
|
||||
expect(result.current.hasSearched).toBe(true);
|
||||
});
|
||||
expect(mockSearchUsers).toHaveBeenCalledWith('John', ['user'])
|
||||
expect(result.current.loading).toBe(false)
|
||||
expect(result.current.options).toEqual(mockUsers)
|
||||
expect(result.current.hasSearched).toBe(true)
|
||||
})
|
||||
|
||||
it("should clear options and handle empty query", () => {
|
||||
it('should clear options and handle empty query', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useUserSearch({ objectTypes: ["user"], errorMessage: "Error" })
|
||||
);
|
||||
useUserSearch({ objectTypes: ['user'], errorMessage: 'Error' })
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery(" "); // empty query with spaces
|
||||
});
|
||||
result.current.setQuery(' ') // empty query with spaces
|
||||
})
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
})
|
||||
|
||||
expect(mockSearchUsers).not.toHaveBeenCalled();
|
||||
expect(result.current.options).toEqual([]);
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.hasSearched).toBe(false);
|
||||
});
|
||||
expect(mockSearchUsers).not.toHaveBeenCalled()
|
||||
expect(result.current.options).toEqual([])
|
||||
expect(result.current.loading).toBe(false)
|
||||
expect(result.current.hasSearched).toBe(false)
|
||||
})
|
||||
|
||||
it("should handle search errors and show the custom error message", async () => {
|
||||
mockSearchUsers.mockRejectedValueOnce(new Error("API Error"));
|
||||
it('should handle search errors and show the custom error message', async () => {
|
||||
mockSearchUsers.mockRejectedValueOnce(new Error('API Error'))
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useUserSearch({ objectTypes: ["user"], errorMessage: "Custom Error" })
|
||||
);
|
||||
useUserSearch({ objectTypes: ['user'], errorMessage: 'Custom Error' })
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery("FailedSearch");
|
||||
});
|
||||
result.current.setQuery('FailedSearch')
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
await Promise.resolve(); // allow microtasks to flush
|
||||
});
|
||||
jest.advanceTimersByTime(300)
|
||||
await Promise.resolve() // allow microtasks to flush
|
||||
})
|
||||
|
||||
expect(result.current.hasSearched).toBe(false);
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.snackbarOpen).toBe(true);
|
||||
expect(result.current.snackbarMessage).toBe("Custom Error");
|
||||
});
|
||||
});
|
||||
expect(result.current.hasSearched).toBe(false)
|
||||
expect(result.current.loading).toBe(false)
|
||||
expect(result.current.snackbarOpen).toBe(true)
|
||||
expect(result.current.snackbarMessage).toBe('Custom Error')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user