@@ -1,11 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { screen } from "@testing-library/react";
|
|
||||||
import App from "../src/App";
|
|
||||||
import { JSX } from "react/jsx-runtime";
|
|
||||||
import { renderWithProviders } from "./utils/Renderwithproviders";
|
|
||||||
|
|
||||||
test("renders app", () => {
|
|
||||||
renderWithProviders(<App />);
|
|
||||||
const linkElement = screen.getByText("Twake");
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// __test__/features/calendars/calendarApi.test.ts
|
||||||
|
|
||||||
|
import {
|
||||||
|
getCalendar,
|
||||||
|
getCalendars,
|
||||||
|
} from "../../../src/features/Calendars/CalendarApi";
|
||||||
|
import { clientConfig } from "../../../src/features/User/oidcAuth";
|
||||||
|
import { api } from "../../../src/utils/apiUtils";
|
||||||
|
clientConfig.url = "https://example.com";
|
||||||
|
|
||||||
|
jest.mock("../../../src/utils/apiUtils");
|
||||||
|
|
||||||
|
describe("Calendar API", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getCalendars", () => {
|
||||||
|
it("fetches calendar list for a user", async () => {
|
||||||
|
const mockUserId = "user123";
|
||||||
|
const mockResponse = [{ id: "calendar1" }, { id: "calendar2" }];
|
||||||
|
|
||||||
|
(api.get as jest.Mock).mockReturnValue({
|
||||||
|
json: jest.fn().mockResolvedValue(mockResponse),
|
||||||
|
});
|
||||||
|
|
||||||
|
const calendars = await getCalendars(mockUserId);
|
||||||
|
|
||||||
|
expect(api.get).toHaveBeenCalledWith(
|
||||||
|
`dav/calendars/${mockUserId}.json?personal=true&sharedDelegationStatus=accepted&sharedPublicSubscription=true&withRights=true`,
|
||||||
|
{
|
||||||
|
headers: { Accept: "application/calendar+json" },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(calendars).toEqual(mockResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getCalendar", () => {
|
||||||
|
it("fetches calendar events for a given ID and match window", async () => {
|
||||||
|
const calendarId = "calendar1";
|
||||||
|
const match = { start: "2025-07-01", end: "2025-07-31" };
|
||||||
|
const mockCalendarData = { events: ["event1", "event2"] };
|
||||||
|
|
||||||
|
(api as unknown as jest.Mock).mockReturnValue({
|
||||||
|
json: jest.fn().mockResolvedValue(mockCalendarData),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await getCalendar(calendarId, match);
|
||||||
|
|
||||||
|
expect(api).toHaveBeenCalledWith(`dav/calendars/${calendarId}.json`, {
|
||||||
|
method: "REPORT",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json, text/plain, */*",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ match }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(mockCalendarData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import { screen, fireEvent } from "@testing-library/react";
|
||||||
|
import { useAppDispatch } from "../../../src/app/hooks";
|
||||||
|
import { createCalendar } from "../../../src/features/Calendars/CalendarSlice";
|
||||||
|
import CalendarPopover from "../../../src/features/Calendars/CalendarModal";
|
||||||
|
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||||
|
|
||||||
|
jest.mock("../../../src/app/hooks");
|
||||||
|
|
||||||
|
describe("CalendarPopover", () => {
|
||||||
|
const mockDispatch = jest.fn();
|
||||||
|
const mockOnClose = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
(useAppDispatch as unknown as jest.Mock).mockReturnValue(mockDispatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderPopover = (open = true) => {
|
||||||
|
const preloadedState = {
|
||||||
|
user: {
|
||||||
|
userData: {
|
||||||
|
sub: "test",
|
||||||
|
email: "test@test.com",
|
||||||
|
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||||
|
openpaasId: "667037022b752d0026472254",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calendars: { list: {}, pending: true },
|
||||||
|
};
|
||||||
|
renderWithProviders(
|
||||||
|
<CalendarPopover
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={open}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders popover and inputs", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
expect(screen.getByLabelText(/Name/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/Description/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Create a Calendar")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates name and description fields", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
const nameInput = screen.getByLabelText(/Name/i);
|
||||||
|
fireEvent.change(nameInput, { target: { value: "My Calendar" } });
|
||||||
|
expect(nameInput).toHaveValue("My Calendar");
|
||||||
|
|
||||||
|
const descInput = screen.getByLabelText(/Description/i);
|
||||||
|
fireEvent.change(descInput, { target: { value: "Test description" } });
|
||||||
|
expect(descInput).toHaveValue("Test description");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("selects a color when a color button is clicked", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
// There are multiple color buttons; pick the first
|
||||||
|
const colorButtons = screen
|
||||||
|
.getAllByRole("button")
|
||||||
|
.filter((btn) => btn.style.backgroundColor !== "");
|
||||||
|
fireEvent.click(colorButtons[0]);
|
||||||
|
|
||||||
|
// The header background should update (check via inline style)
|
||||||
|
expect(screen.getByText("Create a Calendar").style.backgroundColor).toBe(
|
||||||
|
colorButtons[0].style.backgroundColor
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("dispatches createCalendar and calls onClose when Save clicked", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/Name/i), {
|
||||||
|
target: { value: "Test Calendar" },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/Description/i), {
|
||||||
|
target: { value: "Test Description" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedColor = "#D50000";
|
||||||
|
|
||||||
|
const colorButtons = screen
|
||||||
|
.getAllByRole("button")
|
||||||
|
.filter((btn) => btn.style.backgroundColor !== "");
|
||||||
|
|
||||||
|
fireEvent.click(colorButtons[0]);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText(/Save/i));
|
||||||
|
|
||||||
|
expect(mockDispatch).toHaveBeenCalledWith(
|
||||||
|
createCalendar({
|
||||||
|
name: "Test Calendar",
|
||||||
|
description: "Test Description",
|
||||||
|
color: expectedColor,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
|
||||||
|
// Inputs should be reset (optional check)
|
||||||
|
expect(screen.getByLabelText(/Name/i)).toHaveValue("");
|
||||||
|
expect(screen.getByLabelText(/Description/i)).toHaveValue("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls onClose when Cancel clicked", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText(/Cancel/i));
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { screen, fireEvent } from "@testing-library/react";
|
||||||
|
import {
|
||||||
|
addEvent,
|
||||||
|
createCalendar,
|
||||||
|
} from "../../../src/features/Calendars/CalendarSlice";
|
||||||
|
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||||
|
import EventPopover from "../../../src/features/Events/EventModal";
|
||||||
|
import { DateSelectArg } from "@fullcalendar/core";
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
import { formatDateToYYYYMMDDTHHMMSS } from "../../../src/utils/dateUtils";
|
||||||
|
import preview from "jest-preview";
|
||||||
|
import * as appHooks from "../../../src/app/hooks"; // Import the module
|
||||||
|
|
||||||
|
describe("EventPopover", () => {
|
||||||
|
const mockOnClose = jest.fn();
|
||||||
|
jest.mock("crypto");
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
jest.spyOn(appHooks, "useAppDispatch").mockReturnValue(dispatch);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderPopover = (
|
||||||
|
selectedRange = {
|
||||||
|
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
|
||||||
|
) => {
|
||||||
|
const preloadedState = {
|
||||||
|
user: {
|
||||||
|
userData: {
|
||||||
|
sub: "test",
|
||||||
|
email: "test@test.com",
|
||||||
|
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||||
|
openpaasId: "667037022b752d0026472254",
|
||||||
|
},
|
||||||
|
organiserData: {
|
||||||
|
cn: "test",
|
||||||
|
cal_address: "mailto:test@test.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calendars: {
|
||||||
|
list: {
|
||||||
|
"667037022b752d0026472254/cal1": {
|
||||||
|
name: "Calendar 1",
|
||||||
|
color: "#FF0000",
|
||||||
|
},
|
||||||
|
"667037022b752d0026472254/cal2": {
|
||||||
|
name: "Calendar 2",
|
||||||
|
color: "#00FF00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pending: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
renderWithProviders(
|
||||||
|
<EventPopover
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
selectedRange={selectedRange}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders correctly with inputs and calendar options", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
expect(screen.getByText(/Create Event/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/title/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/Start/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/End/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/Description/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/Location/i)).toBeInTheDocument();
|
||||||
|
preview.debug();
|
||||||
|
// Calendar options
|
||||||
|
const select = screen.getByRole("combobox");
|
||||||
|
fireEvent.mouseDown(select);
|
||||||
|
expect(screen.getByText("Calendar 1")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Calendar 2")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fills start and end from selectedRange", () => {
|
||||||
|
const selectedRange = {
|
||||||
|
startStr: "2026-07-20T10:00",
|
||||||
|
endStr: "2026-07-20T12:00",
|
||||||
|
start: new Date("2026-07-20T10:00"),
|
||||||
|
end: new Date("2026-07-20T12:00"),
|
||||||
|
allDay: false,
|
||||||
|
resource: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderPopover(selectedRange as unknown as DateSelectArg);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText(/Start/i)).toHaveValue("2026-07-20T10:00");
|
||||||
|
expect(screen.getByLabelText(/End/i)).toHaveValue("2026-07-20T12:00");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates inputs on change", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<EventPopover
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
selectedRange={null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/title/i), {
|
||||||
|
target: { value: "My Event" },
|
||||||
|
});
|
||||||
|
expect(screen.getByLabelText(/title/i)).toHaveValue("My Event");
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/Description/i), {
|
||||||
|
target: { value: "Event Description" },
|
||||||
|
});
|
||||||
|
expect(screen.getByLabelText(/Description/i)).toHaveValue(
|
||||||
|
"Event Description"
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/Location/i), {
|
||||||
|
target: { value: "Conference Room" },
|
||||||
|
});
|
||||||
|
expect(screen.getByLabelText(/Location/i)).toHaveValue("Conference Room");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes selected calendar", async () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
const select = screen.getByRole("combobox");
|
||||||
|
fireEvent.mouseDown(select); // Open menu
|
||||||
|
|
||||||
|
const option = await screen.findByText("Calendar 2");
|
||||||
|
fireEvent.click(option);
|
||||||
|
|
||||||
|
// The Select control value isn't easy to check directly because MUI Select wraps input,
|
||||||
|
// but we can test by triggering save later
|
||||||
|
});
|
||||||
|
|
||||||
|
it("dispatches addEvent and calls onClose when Save is clicked", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
// Select calendar id
|
||||||
|
const select = screen.getByRole("combobox");
|
||||||
|
fireEvent.mouseDown(select);
|
||||||
|
fireEvent.click(screen.getByText("Calendar 1"));
|
||||||
|
|
||||||
|
// Fill inputs
|
||||||
|
fireEvent.change(screen.getByLabelText(/title/i), {
|
||||||
|
target: { value: "Meeting" },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/Start/i), {
|
||||||
|
target: { value: "2025-07-18T09:00" },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/End/i), {
|
||||||
|
target: { value: "2025-07-18T10:00" },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/Description/i), {
|
||||||
|
target: { value: "Discuss project" },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/Location/i), {
|
||||||
|
target: { value: "Zoom" },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText(/Save/i));
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Extract dispatched action argument
|
||||||
|
const dispatchedArg = dispatch.mock.calls[0][0].payload;
|
||||||
|
console.log(dispatchedArg);
|
||||||
|
expect(dispatchedArg.calendarUid).toBe("667037022b752d0026472254/cal1");
|
||||||
|
expect(dispatchedArg.event.title).toBe("Meeting");
|
||||||
|
expect(dispatchedArg.event.description).toBe("Discuss project");
|
||||||
|
expect(dispatchedArg.event.location).toBe("Zoom");
|
||||||
|
expect(dispatchedArg.event.color).toBe("#FF0000");
|
||||||
|
expect(dispatchedArg.event.organizer).toEqual({
|
||||||
|
cn: "test",
|
||||||
|
cal_address: "mailto:test@test.com",
|
||||||
|
});
|
||||||
|
|
||||||
|
// onClose should be called
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls onClose when Cancel clicked", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText(/Cancel/i));
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { parseCalendarEvent } from "../../../src/features/Events/eventUtils";
|
||||||
|
|
||||||
|
describe("parseCalendarEvent", () => {
|
||||||
|
const baseColor = "#00FF00";
|
||||||
|
const calendarId = "calendar-123";
|
||||||
|
|
||||||
|
it("parses a full event correctly", () => {
|
||||||
|
const rawData = [
|
||||||
|
["UID", {}, "text", "event-1"],
|
||||||
|
["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"],
|
||||||
|
["DTEND", {}, "date-time", "2025-07-18T10:00:00Z"],
|
||||||
|
["SUMMARY", {}, "text", "Team Meeting"],
|
||||||
|
["DESCRIPTION", {}, "text", "Discuss roadmap"],
|
||||||
|
["LOCATION", {}, "text", "Zoom"],
|
||||||
|
["ORGANIZER", { cn: "Alice" }, "cal-address", "mailto:alice@example.com"],
|
||||||
|
[
|
||||||
|
"ATTENDEE",
|
||||||
|
{ cn: "Bob", partstat: "ACCEPTED" },
|
||||||
|
"cal-address",
|
||||||
|
"mailto:bob@example.com",
|
||||||
|
],
|
||||||
|
["X-OPENPAAS-VIDEOCONFERENCE", {}, "text", "https://meet.link"],
|
||||||
|
["STATUS", {}, "text", "CONFIRMED"],
|
||||||
|
["SEQUENCE", {}, "integer", "2"],
|
||||||
|
["DTSTAMP", {}, "date-time", "2025-07-18T08:00:00Z"],
|
||||||
|
] as unknown as [string, Record<string, string>, string, any];
|
||||||
|
|
||||||
|
const result = parseCalendarEvent(rawData, baseColor, calendarId);
|
||||||
|
|
||||||
|
expect(result.uid).toBe("event-1");
|
||||||
|
expect(result.title).toBe("Team Meeting");
|
||||||
|
expect(result.description).toBe("Discuss roadmap");
|
||||||
|
expect(result.location).toBe("Zoom");
|
||||||
|
expect(result.start).toBe("2025-07-18T09:00:00Z");
|
||||||
|
expect(result.end).toBe("2025-07-18T10:00:00Z");
|
||||||
|
expect(result.stamp).toBe("2025-07-18T08:00:00Z");
|
||||||
|
expect(result.sequence).toBe(2);
|
||||||
|
expect(result.color).toBe(baseColor);
|
||||||
|
expect(result.status).toBe("CONFIRMED");
|
||||||
|
expect(result.x_openpass_videoconference).toBe("https://meet.link");
|
||||||
|
|
||||||
|
expect(result.organizer).toEqual({
|
||||||
|
cn: "Alice",
|
||||||
|
cal_address: "alice@example.com",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.attendee).toEqual([
|
||||||
|
{
|
||||||
|
cn: "Bob",
|
||||||
|
cal_address: "bob@example.com",
|
||||||
|
partstat: "ACCEPTED",
|
||||||
|
rsvp: "",
|
||||||
|
role: "",
|
||||||
|
cutype: "",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("appends recurrence-id to UID if present", () => {
|
||||||
|
const rawData: any = [
|
||||||
|
["UID", {}, "text", "event-2"],
|
||||||
|
["RECURRENCE-ID", {}, "date-time", "2025-07-18T09:00:00Z"],
|
||||||
|
["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = parseCalendarEvent(rawData, baseColor, calendarId);
|
||||||
|
|
||||||
|
expect(result.uid).toBe("event-2/2025-07-18T09:00:00Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns error if UID or start is missing", () => {
|
||||||
|
const rawDataMissingUid: any = [
|
||||||
|
["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = parseCalendarEvent(rawDataMissingUid, baseColor, calendarId);
|
||||||
|
expect(result.error).toMatch(/missing crucial event param/);
|
||||||
|
|
||||||
|
const rawDataMissingStart: any = [["UID", {}, "text", "event-3"]];
|
||||||
|
|
||||||
|
const result2 = parseCalendarEvent(
|
||||||
|
rawDataMissingStart,
|
||||||
|
baseColor,
|
||||||
|
calendarId
|
||||||
|
);
|
||||||
|
expect(result2.error).toMatch(/missing crucial event param/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles optional organizer and attendee fields gracefully", () => {
|
||||||
|
const rawData = [
|
||||||
|
["UID", {}, "text", "event-4"],
|
||||||
|
["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"],
|
||||||
|
["ATTENDEE", {}, "cal-address", "mailto:john@example.com"],
|
||||||
|
["ORGANIZER", {}, "cal-address", "mailto:jane@example.com"],
|
||||||
|
] as unknown as [string, Record<string, string>, string, any];
|
||||||
|
|
||||||
|
const result = parseCalendarEvent(rawData, baseColor, calendarId);
|
||||||
|
|
||||||
|
expect(result.attendee).toEqual([
|
||||||
|
{
|
||||||
|
cn: "",
|
||||||
|
cal_address: "john@example.com",
|
||||||
|
partstat: "",
|
||||||
|
rsvp: "",
|
||||||
|
role: "",
|
||||||
|
cutype: "",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result.organizer).toEqual({
|
||||||
|
cn: "",
|
||||||
|
cal_address: "jane@example.com",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { screen, waitFor } from "@testing-library/react";
|
||||||
|
import thunk from "redux-thunk";
|
||||||
|
import HandleLogin from "../../../src/features/User/HandleLogin";
|
||||||
|
import * as oidcAuth from "../../../src/features/User/oidcAuth";
|
||||||
|
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||||
|
import { clientConfig } from "../../../src/features/User/oidcAuth";
|
||||||
|
import * as apiUtils from "../../../src/utils/apiUtils";
|
||||||
|
clientConfig.url = "https://example.com";
|
||||||
|
|
||||||
|
|
||||||
|
describe("HandleLogin", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(apiUtils, "redirectTo").mockImplementation(() => {});
|
||||||
|
|
||||||
|
jest.clearAllMocks();
|
||||||
|
sessionStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("redirects and sets sessionStorage when no userData", async () => {
|
||||||
|
const loginUrlMock = {
|
||||||
|
code_verifier: "verifier123",
|
||||||
|
state: "state123",
|
||||||
|
redirectTo: new URL("http://login.url"),
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.spyOn(oidcAuth, "Auth").mockResolvedValue(loginUrlMock);
|
||||||
|
|
||||||
|
renderWithProviders(<HandleLogin />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(oidcAuth.Auth).toHaveBeenCalled();
|
||||||
|
expect(sessionStorage.getItem("redirectState")).toEqual(
|
||||||
|
JSON.stringify({
|
||||||
|
code_verifier: "verifier123",
|
||||||
|
state: "state123",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(apiUtils.redirectTo).toHaveBeenCalledWith(loginUrlMock.redirectTo);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText(/error/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows Loading when userData exists and calendars pending is true", () => {
|
||||||
|
const preloadedState = {
|
||||||
|
user: {
|
||||||
|
userData: {
|
||||||
|
sub: "test",
|
||||||
|
email: "test@test.com",
|
||||||
|
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||||
|
openpaasId: "667037022b752d0026472254",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calendars: { list: {}, pending: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithProviders(<HandleLogin />, preloadedState);
|
||||||
|
expect(screen.getByAltText(/loading/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
test("shows Loading when userData exists and calendars pending is false", () => {
|
||||||
|
const preloadedState = {
|
||||||
|
user: {
|
||||||
|
userData: {
|
||||||
|
sub: "cmoussu",
|
||||||
|
email: "cmoussu@linagora.com",
|
||||||
|
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||||
|
openpaasId: "667037022b752d0026472254",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
renderWithProviders(<HandleLogin />, preloadedState);
|
||||||
|
|
||||||
|
expect(screen.getByAltText("loading")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
test("shows Error when userData doesnt exists and calendars pending is false", () => {
|
||||||
|
renderWithProviders(<HandleLogin />);
|
||||||
|
|
||||||
|
expect(screen.getByText("Error")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
// __test__/features/user/CallbackResume.test.tsx
|
||||||
|
import React from "react";
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { CallbackResume } from "../../../src/features/User/LoginCallback";
|
||||||
|
import { useAppDispatch } from "../../../src/app/hooks";
|
||||||
|
import * as oidcAuth from "../../../src/features/User/oidcAuth";
|
||||||
|
import { push } from "redux-first-history";
|
||||||
|
import {
|
||||||
|
setTokens,
|
||||||
|
setUserData,
|
||||||
|
getOpenPaasUserIdAsync,
|
||||||
|
} from "../../../src/features/User/userSlice";
|
||||||
|
import { getCalendarsListAsync } from "../../../src/features/Calendars/CalendarSlice";
|
||||||
|
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||||
|
|
||||||
|
// Mocks
|
||||||
|
jest.mock("../../../src/app/hooks", () => ({
|
||||||
|
useAppDispatch: jest.fn(),
|
||||||
|
useAppSelector: jest.fn(() => ({})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../../src/features/User/oidcAuth", () => ({
|
||||||
|
Callback: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../../src/features/User/userSlice", () => ({
|
||||||
|
setUserData: jest.fn((data) => ({ type: "SET_USER", payload: data })),
|
||||||
|
setTokens: jest.fn((tokens) => ({ type: "SET_TOKENS", payload: tokens })),
|
||||||
|
getOpenPaasUserIdAsync: jest.fn(() => ({ type: "GET_USER_ID" })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../../src/features/Calendars/CalendarSlice", () => ({
|
||||||
|
getCalendarsListAsync: jest.fn(() => ({ type: "GET_CALENDARS" })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("CallbackResume", () => {
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
(useAppDispatch as unknown as jest.Mock).mockReturnValue(dispatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call Callback and dispatch necessary actions", async () => {
|
||||||
|
const mockTokenSet = { access_token: "abc" };
|
||||||
|
const mockUserInfo = { name: "Test User" };
|
||||||
|
|
||||||
|
const mockData = {
|
||||||
|
tokenSet: mockTokenSet,
|
||||||
|
userinfo: mockUserInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
(oidcAuth.Callback as jest.Mock).mockResolvedValue(mockData);
|
||||||
|
|
||||||
|
sessionStorage.setItem(
|
||||||
|
"redirectState",
|
||||||
|
JSON.stringify({ code_verifier: "verifier123", state: "state456" })
|
||||||
|
);
|
||||||
|
|
||||||
|
render(<CallbackResume />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(oidcAuth.Callback).toHaveBeenCalledWith("verifier123", "state456");
|
||||||
|
expect(dispatch).toHaveBeenCalledWith(setUserData(mockUserInfo));
|
||||||
|
expect(dispatch).toHaveBeenCalledWith(setTokens(mockTokenSet));
|
||||||
|
expect(dispatch).toHaveBeenCalledWith(getOpenPaasUserIdAsync());
|
||||||
|
expect(dispatch).toHaveBeenCalledWith(getCalendarsListAsync());
|
||||||
|
expect(dispatch).toHaveBeenCalledWith(push("/"));
|
||||||
|
expect(sessionStorage.getItem("redirectState")).toBe(null);
|
||||||
|
expect(sessionStorage.getItem("tokenSet")).toEqual(
|
||||||
|
JSON.stringify(mockTokenSet)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle missing redirectState gracefully", async () => {
|
||||||
|
sessionStorage.removeItem("redirectState");
|
||||||
|
renderWithProviders(<CallbackResume />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(dispatch).toHaveBeenCalledWith(push("/"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
// __tests__/auth.test.ts
|
||||||
|
import * as client from "openid-client";
|
||||||
|
import {
|
||||||
|
clientConfig,
|
||||||
|
getClientConfig,
|
||||||
|
Auth,
|
||||||
|
Logout,
|
||||||
|
Callback,
|
||||||
|
} from "../../../src/features/User/oidcAuth";
|
||||||
|
import * as apiUtils from "../../../src/utils/apiUtils";
|
||||||
|
|
||||||
|
clientConfig.url = "https://example.com";
|
||||||
|
const localAdress = "https://local.exemple.com";
|
||||||
|
|
||||||
|
describe("OpenID Client Auth Module", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
jest.spyOn(apiUtils, "getLocation").mockImplementation(() => localAdress);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getClientConfig", () => {
|
||||||
|
it("should call discovery with clientConfig.url", async () => {
|
||||||
|
const discoveryMock = client.discovery as jest.Mock;
|
||||||
|
discoveryMock.mockResolvedValue("discoveredClient");
|
||||||
|
|
||||||
|
const result = await getClientConfig();
|
||||||
|
|
||||||
|
expect(discoveryMock).toHaveBeenCalledWith(
|
||||||
|
new URL(clientConfig.url),
|
||||||
|
clientConfig.client_id
|
||||||
|
);
|
||||||
|
expect(result).toBe("discoveredClient");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Auth", () => {
|
||||||
|
it("should generate PKCE and build authorization URL with PKCE", async () => {
|
||||||
|
(client.randomPKCECodeVerifier as jest.Mock).mockReturnValue(
|
||||||
|
"verifier123"
|
||||||
|
);
|
||||||
|
(client.calculatePKCECodeChallenge as jest.Mock).mockResolvedValue(
|
||||||
|
"challenge123"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mock discovery returning an object with serverMetadata()
|
||||||
|
const discoveredClient = {
|
||||||
|
serverMetadata: jest.fn(() => ({
|
||||||
|
supportsPKCE: () => true,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
(client.discovery as jest.Mock).mockResolvedValue(discoveredClient);
|
||||||
|
|
||||||
|
(client.buildAuthorizationUrl as jest.Mock).mockReturnValue(
|
||||||
|
"https://auth.url"
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await Auth();
|
||||||
|
|
||||||
|
expect(client.randomPKCECodeVerifier).toHaveBeenCalled();
|
||||||
|
expect(client.calculatePKCECodeChallenge).toHaveBeenCalledWith(
|
||||||
|
"verifier123"
|
||||||
|
);
|
||||||
|
expect(client.buildAuthorizationUrl).toHaveBeenCalledWith(
|
||||||
|
discoveredClient,
|
||||||
|
expect.objectContaining({
|
||||||
|
code_challenge: "challenge123",
|
||||||
|
code_challenge_method: clientConfig.code_challenge_method,
|
||||||
|
redirect_uri: clientConfig.redirect_uri,
|
||||||
|
scope: clientConfig.scope,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
redirectTo: "https://auth.url",
|
||||||
|
code_verifier: "verifier123",
|
||||||
|
state: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should generate state when PKCE not supported", async () => {
|
||||||
|
(client.randomPKCECodeVerifier as jest.Mock).mockReturnValue(
|
||||||
|
"verifier123"
|
||||||
|
);
|
||||||
|
(client.calculatePKCECodeChallenge as jest.Mock).mockResolvedValue(
|
||||||
|
"challenge123"
|
||||||
|
);
|
||||||
|
|
||||||
|
const discoveredClient = {
|
||||||
|
serverMetadata: jest.fn(() => ({
|
||||||
|
supportsPKCE: () => false,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
(client.discovery as jest.Mock).mockResolvedValue(discoveredClient);
|
||||||
|
(client.randomState as jest.Mock).mockReturnValue("state123");
|
||||||
|
(client.buildAuthorizationUrl as jest.Mock).mockReturnValue(
|
||||||
|
"https://auth.url"
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await Auth();
|
||||||
|
|
||||||
|
expect(client.randomState).toHaveBeenCalled();
|
||||||
|
expect(result.state).toBe("state123");
|
||||||
|
expect(result.redirectTo).toBe("https://auth.url");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Logout", () => {
|
||||||
|
it("should build end session URL", async () => {
|
||||||
|
const discoveredClient = {};
|
||||||
|
(client.discovery as jest.Mock).mockResolvedValue(discoveredClient);
|
||||||
|
(client.buildEndSessionUrl as jest.Mock).mockReturnValue(
|
||||||
|
"https://logout.url"
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await Logout();
|
||||||
|
|
||||||
|
expect(client.buildEndSessionUrl).toHaveBeenCalledWith(discoveredClient, {
|
||||||
|
post_logout_redirect_uri: clientConfig.post_logout_redirect_uri,
|
||||||
|
});
|
||||||
|
expect(result).toBe("https://logout.url");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Callback", () => {
|
||||||
|
it("should perform authorization code grant and fetch user info", async () => {
|
||||||
|
const discoveredClient = {};
|
||||||
|
(client.discovery as jest.Mock).mockResolvedValue(discoveredClient);
|
||||||
|
|
||||||
|
const mockTokenSet = {
|
||||||
|
access_token: "access123",
|
||||||
|
claims: jest.fn(() => ({ sub: "user123" })),
|
||||||
|
};
|
||||||
|
(client.authorizationCodeGrant as jest.Mock).mockResolvedValue(
|
||||||
|
mockTokenSet
|
||||||
|
);
|
||||||
|
(client.fetchUserInfo as jest.Mock).mockResolvedValue({ name: "User" });
|
||||||
|
const result = await Callback("verifier123", "state123");
|
||||||
|
|
||||||
|
expect(client.authorizationCodeGrant).toHaveBeenCalledWith(
|
||||||
|
discoveredClient,
|
||||||
|
new URL(localAdress),
|
||||||
|
{ pkceCodeVerifier: "verifier123", expectedState: "state123" }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(client.fetchUserInfo).toHaveBeenCalledWith(
|
||||||
|
discoveredClient,
|
||||||
|
"access123",
|
||||||
|
"user123"
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
tokenSet: mockTokenSet,
|
||||||
|
userinfo: { name: "User" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should catch and log errors", async () => {
|
||||||
|
const error = new Error("fail");
|
||||||
|
(client.discovery as jest.Mock).mockResolvedValue({});
|
||||||
|
(client.authorizationCodeGrant as jest.Mock).mockRejectedValue(error);
|
||||||
|
const consoleErrorSpy = jest
|
||||||
|
.spyOn(console, "error")
|
||||||
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
|
const result = await Callback("verifier", "state");
|
||||||
|
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledWith("Token grant error:", error);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
|
||||||
|
consoleErrorSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { clientConfig } from "../../../src/features/User/oidcAuth";
|
||||||
|
import getOpenPaasUserId from "../../../src/features/User/userAPI";
|
||||||
|
import { api } from "../../../src/utils/apiUtils";
|
||||||
|
|
||||||
|
jest.mock("../../../src/utils/apiUtils");
|
||||||
|
|
||||||
|
clientConfig.url = "https://example.com";
|
||||||
|
|
||||||
|
describe("getOpenPaasUserId", () => {
|
||||||
|
it("should fetch and return user data", async () => {
|
||||||
|
const mockUser = { id: "123", name: "OpenPaas User" };
|
||||||
|
|
||||||
|
(api.get as jest.Mock).mockReturnValue({
|
||||||
|
json: jest.fn().mockResolvedValue(mockUser),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await getOpenPaasUserId();
|
||||||
|
|
||||||
|
expect(api.get).toHaveBeenCalledWith("api/user");
|
||||||
|
expect(result).toEqual(mockUser);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,21 +1,12 @@
|
|||||||
import type { RenderOptions } from "@testing-library/react";
|
import type { RenderOptions } from "@testing-library/react";
|
||||||
import {
|
import { render } from "@testing-library/react";
|
||||||
act,
|
|
||||||
fireEvent,
|
|
||||||
render,
|
|
||||||
screen,
|
|
||||||
waitFor,
|
|
||||||
} from "@testing-library/react";
|
|
||||||
import React, { PropsWithChildren } from "react";
|
import React, { PropsWithChildren } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import "../i18n";
|
|
||||||
|
|
||||||
import { MemoryRouter } from "react-router-dom";
|
import { MemoryRouter } from "react-router-dom";
|
||||||
import type { AppStore, RootState } from "../../src/app/store";
|
import type { AppStore, RootState } from "../../src/app/store";
|
||||||
import { setupStore } from "../../src/app/store";
|
import { setupStore } from "../../src/app/store";
|
||||||
import { t } from "i18next";
|
import { userData, userOrganiser } from "../../src/features/User/userDataTypes";
|
||||||
import { userData } from "../../src/features/User/userDataTypes";
|
|
||||||
|
|
||||||
interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {
|
interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {
|
||||||
preloadedState?: Partial<RootState>;
|
preloadedState?: Partial<RootState>;
|
||||||
@@ -24,30 +15,15 @@ interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {
|
|||||||
|
|
||||||
export function renderWithProviders(
|
export function renderWithProviders(
|
||||||
ui: React.ReactElement,
|
ui: React.ReactElement,
|
||||||
|
preloadedState = {},
|
||||||
extendedRenderOptions: ExtendedRenderOptions = {}
|
extendedRenderOptions: ExtendedRenderOptions = {}
|
||||||
) {
|
) {
|
||||||
const {
|
const { store = setupStore(preloadedState), ...renderOptions } =
|
||||||
preloadedState = {
|
extendedRenderOptions;
|
||||||
user: { userData: null as unknown as userData },
|
|
||||||
router: {
|
|
||||||
location: {
|
|
||||||
pathname: "",
|
|
||||||
search: "",
|
|
||||||
hash: "",
|
|
||||||
state: null,
|
|
||||||
key: "o01z0jry",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
store = setupStore(preloadedState),
|
|
||||||
...renderOptions
|
|
||||||
} = extendedRenderOptions;
|
|
||||||
|
|
||||||
const Wrapper = ({ children }: PropsWithChildren) => {
|
const Wrapper = ({ children }: PropsWithChildren) => {
|
||||||
useTranslation();
|
|
||||||
return (
|
return (
|
||||||
<MemoryRouter initialEntries={["/manager.html"]}>
|
<MemoryRouter initialEntries={["/"]}>
|
||||||
<Provider store={store}>{children}</Provider>
|
<Provider store={store}>{children}</Provider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||||
|
['@babel/preset-react', { runtime: 'automatic', importSource: 'preact' }],
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
process(sourceText, sourcePath, options) {
|
||||||
|
return {
|
||||||
|
code: `module.exports = ${JSON.stringify(path.basename(sourcePath))};`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import type { Config } from "jest";
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
collectCoverage: true,
|
||||||
|
coverageDirectory: "coverage",
|
||||||
|
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
displayName: "dom",
|
||||||
|
clearMocks: true,
|
||||||
|
moduleFileExtensions: [
|
||||||
|
"js",
|
||||||
|
"mjs",
|
||||||
|
"cjs",
|
||||||
|
"jsx",
|
||||||
|
"ts",
|
||||||
|
"tsx",
|
||||||
|
"json",
|
||||||
|
"node",
|
||||||
|
],
|
||||||
|
testEnvironment: "jsdom",
|
||||||
|
testMatch: ["**/*.test.tsx"],
|
||||||
|
transform: {
|
||||||
|
"^.+\\.tsx?$": ["ts-jest", { tsconfig: "tsconfig.json" }],
|
||||||
|
"^.+\\.(js|jsx|mjs)$": "babel-jest",
|
||||||
|
"^.+\\.(css|scss|sass|less)$": "jest-preview/transforms/css",
|
||||||
|
"^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)":
|
||||||
|
"jest-preview/transforms/file",
|
||||||
|
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
||||||
|
"<rootDir>/fileTransformer.ts",
|
||||||
|
},
|
||||||
|
transformIgnorePatterns: [
|
||||||
|
"/node_modules/(?!(preact|@fullcalendar|react-calendar|get-user-locale|memoize|mimic-function|@wojtekmaj|ky)/)",
|
||||||
|
],
|
||||||
|
|
||||||
|
moduleNameMapper: { "^preact(/(.*)|$)": "preact$1" },
|
||||||
|
setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: "node",
|
||||||
|
clearMocks: true,
|
||||||
|
moduleFileExtensions: [
|
||||||
|
"js",
|
||||||
|
"mjs",
|
||||||
|
"cjs",
|
||||||
|
"jsx",
|
||||||
|
"ts",
|
||||||
|
"tsx",
|
||||||
|
"json",
|
||||||
|
"node",
|
||||||
|
],
|
||||||
|
testEnvironment: "node",
|
||||||
|
testMatch: ["**/*.test.ts"],
|
||||||
|
transform: {
|
||||||
|
"^.+\\.tsx?$": ["ts-jest", { tsconfig: "tsconfig.json" }],
|
||||||
|
"^.+\\.(js|jsx|mjs)$": "babel-jest",
|
||||||
|
},
|
||||||
|
transformIgnorePatterns: ["/node_modules/(?!(ky)/)"],
|
||||||
|
setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
Generated
+1956
-2854
File diff suppressed because it is too large
Load Diff
+14
-4
@@ -27,14 +27,16 @@
|
|||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^6.23.1",
|
"react-router-dom": "^6.23.1",
|
||||||
"redux-first-history": "^5.2.0",
|
"redux-first-history": "^5.2.0",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "PORT=5000 rsbuild dev",
|
"start": "PORT=5000 rsbuild dev",
|
||||||
"build": "rsbuild build",
|
"build": "rsbuild build",
|
||||||
"preview": "rsbuild preview",
|
"preview": "rsbuild preview",
|
||||||
"test": "jest test",
|
"test": "jest",
|
||||||
"lint": "eslint src"
|
"lint": "eslint src",
|
||||||
|
"jest-preview": "jest-preview"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -54,7 +56,12 @@
|
|||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "24.x"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/preset-env": "^7.28.0",
|
||||||
|
"@babel/preset-react": "^7.27.1",
|
||||||
"@rsbuild/core": "^1.4.3",
|
"@rsbuild/core": "^1.4.3",
|
||||||
"@rsbuild/plugin-react": "^1.3.3",
|
"@rsbuild/plugin-react": "^1.3.3",
|
||||||
"@rsbuild/plugin-svgr": "^1.2.0",
|
"@rsbuild/plugin-svgr": "^1.2.0",
|
||||||
@@ -62,12 +69,15 @@
|
|||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^30.0.0",
|
||||||
|
"babel-jest": "^30.0.4",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"i18next-resources-for-ts": "1.4.0",
|
"i18next-resources-for-ts": "1.4.0",
|
||||||
"jest": "^27.5.1",
|
"jest": "^30.0.4",
|
||||||
|
"jest-environment-jsdom": "^30.0.4",
|
||||||
"jest-preview": "^0.3.1",
|
"jest-preview": "^0.3.1",
|
||||||
|
"ts-jest": "^29.4.0",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ import React from "react";
|
|||||||
import logo from "../../static/images/calendar.svg";
|
import logo from "../../static/images/calendar.svg";
|
||||||
|
|
||||||
export function Loading() {
|
export function Loading() {
|
||||||
return <img src={logo} />;
|
return <img src={logo} alt="loading" />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Auth } from "./oidcAuth";
|
|||||||
import { Loading } from "../../components/Loading/Loading";
|
import { Loading } from "../../components/Loading/Loading";
|
||||||
import { Error } from "../../components/Error/Error";
|
import { Error } from "../../components/Error/Error";
|
||||||
import { push } from "redux-first-history";
|
import { push } from "redux-first-history";
|
||||||
|
import { redirectTo } from "../../utils/apiUtils";
|
||||||
|
|
||||||
export function HandleLogin() {
|
export function HandleLogin() {
|
||||||
const userData = useAppSelector((state) => state.user.userData);
|
const userData = useAppSelector((state) => state.user.userData);
|
||||||
@@ -22,7 +23,7 @@ export function HandleLogin() {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
window.location.assign(loginurl.redirectTo);
|
redirectTo(loginurl.redirectTo);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as client from "openid-client";
|
import * as client from "openid-client";
|
||||||
|
import { getLocation } from "../../utils/apiUtils";
|
||||||
|
|
||||||
export const clientConfig = {
|
export const clientConfig = {
|
||||||
url: (window as any).SSO_BASE_URL ?? "",
|
url: (window as any).SSO_BASE_URL ?? "",
|
||||||
@@ -49,14 +50,14 @@ export async function Logout() {
|
|||||||
export async function Callback(code_verifier: string, state: any) {
|
export async function Callback(code_verifier: string, state: any) {
|
||||||
try {
|
try {
|
||||||
const openIdClientConfig = await getClientConfig();
|
const openIdClientConfig = await getClientConfig();
|
||||||
const currentUrl = new URL(window.location.href);
|
const currentLocation = getLocation();
|
||||||
|
|
||||||
console.log("Callback URL:", currentUrl.toString());
|
console.log("Callback URL:", currentLocation);
|
||||||
console.log("Code verifier:", code_verifier);
|
console.log("Code verifier:", code_verifier);
|
||||||
|
|
||||||
const tokenSet = await client.authorizationCodeGrant(
|
const tokenSet = await client.authorizationCodeGrant(
|
||||||
openIdClientConfig,
|
openIdClientConfig,
|
||||||
currentUrl,
|
new URL(currentLocation),
|
||||||
{
|
{
|
||||||
pkceCodeVerifier: code_verifier,
|
pkceCodeVerifier: code_verifier,
|
||||||
expectedState: state,
|
expectedState: state,
|
||||||
|
|||||||
+17
-5
@@ -1,5 +1,17 @@
|
|||||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
import "@testing-library/jest-dom";
|
||||||
// allows you to do things like:
|
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
import { TextEncoder, TextDecoder } from "util";
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
import { clientConfig } from "./features/User/oidcAuth";
|
||||||
import '@testing-library/jest-dom';
|
|
||||||
|
global.TextEncoder = TextEncoder;
|
||||||
|
|
||||||
|
jest.mock("openid-client", () => ({
|
||||||
|
discovery: jest.fn(),
|
||||||
|
randomPKCECodeVerifier: jest.fn(),
|
||||||
|
calculatePKCECodeChallenge: jest.fn(),
|
||||||
|
randomState: jest.fn(),
|
||||||
|
buildAuthorizationUrl: jest.fn(),
|
||||||
|
buildEndSessionUrl: jest.fn(),
|
||||||
|
authorizationCodeGrant: jest.fn(),
|
||||||
|
fetchUserInfo: jest.fn(),
|
||||||
|
}));
|
||||||
|
|||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
declare module "*.svg" {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
@@ -29,9 +29,17 @@ export const api = ky.extend({
|
|||||||
state: loginurl.state,
|
state: loginurl.state,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
window.location.assign(loginurl.redirectTo);
|
redirectTo(loginurl.redirectTo);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function redirectTo(url: URL) {
|
||||||
|
window.location.assign(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocation() {
|
||||||
|
return window.location.href;
|
||||||
|
}
|
||||||
|
|||||||
+4
-10
@@ -1,11 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "ES2022",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
@@ -13,14 +9,12 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"module": "esnext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src", "__test__"]
|
||||||
"src"
|
|
||||||
, "__test__/App.test.tsx" ]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user