mirror of
https://github.com/gethomepage/homepage.git
synced 2026-09-28 15:01:17 -07:00
Chore: full react hooks eslint compliance (#7040)
This commit is contained in:
@@ -38,16 +38,16 @@ const colorVariants = {
|
||||
export default function Component({ service }) {
|
||||
const { widget } = service;
|
||||
const { i18n } = useTranslation();
|
||||
const [showDate, setShowDate] = useState(null);
|
||||
const [events, setEvents] = useState({});
|
||||
const nowDate = DateTime.now().setLocale(i18n.language);
|
||||
const currentDate = widget?.timezone ? nowDate.setZone(widget?.timezone).startOf("day") : nowDate;
|
||||
const [showDate, setShowDate] = useState(null);
|
||||
const { settings } = useContext(SettingsContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showDate) {
|
||||
setShowDate(currentDate);
|
||||
}
|
||||
// seeded after mount, not during render: "today" is client-only and would break hydration
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
if (!showDate) setShowDate(currentDate);
|
||||
}, [showDate, currentDate]);
|
||||
|
||||
// params for API fetch
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ICAL from "ical.js";
|
||||
import { DateTime } from "luxon";
|
||||
import { useTranslation } from "next-i18next/pages";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
|
||||
import Error from "../../../components/services/widget/error";
|
||||
import useWidgetAPI from "../../../utils/proxy/use-widget-api";
|
||||
@@ -17,65 +17,67 @@ function simpleHash(str) {
|
||||
return Math.abs(hash).toString(36);
|
||||
}
|
||||
|
||||
function buildEvent(event, type) {
|
||||
return {
|
||||
id: event.getFirstPropertyValue("uid"),
|
||||
type,
|
||||
title: event.getFirstPropertyValue("summary"),
|
||||
rrule: event.getFirstPropertyValue("rrule"),
|
||||
dtstart:
|
||||
event.getFirstPropertyValue("dtstart") ||
|
||||
event.getFirstPropertyValue("due") ||
|
||||
event.getFirstPropertyValue("completed") ||
|
||||
ICAL.Time.now(), // handles events without a date
|
||||
dtend:
|
||||
event.getFirstPropertyValue("dtend") ||
|
||||
event.getFirstPropertyValue("due") ||
|
||||
event.getFirstPropertyValue("completed") ||
|
||||
ICAL.Time.now(), // handles events without a date
|
||||
location: event.getFirstPropertyValue("location"),
|
||||
status: event.getFirstPropertyValue("status"),
|
||||
url: event.getFirstPropertyValue("url"),
|
||||
};
|
||||
}
|
||||
|
||||
export default function Integration({ config, params, setEvents, hideErrors, timezone }) {
|
||||
const { t } = useTranslation();
|
||||
const { data: icalData, error: icalError } = useWidgetAPI(config, config.name, {
|
||||
refreshInterval: 300000, // 5 minutes
|
||||
});
|
||||
|
||||
const { events, dataError } = useMemo(() => {
|
||||
if (icalError || !icalData || icalData.error) {
|
||||
return { events: [], dataError: undefined };
|
||||
}
|
||||
|
||||
if (!icalData.data) {
|
||||
return {
|
||||
events: [],
|
||||
dataError: { message: `'${config.name}': ${t("calendar.errorWhenLoadingData")}` },
|
||||
};
|
||||
}
|
||||
|
||||
const jCal = ICAL.parse(icalData.data);
|
||||
const vCalendar = new ICAL.Component(jCal);
|
||||
const parsedEvents = [
|
||||
...vCalendar.getAllSubcomponents("vevent").map((event) => buildEvent(event, "vevent")),
|
||||
...vCalendar.getAllSubcomponents("vtodo").map((todo) => buildEvent(todo, "vtodo")),
|
||||
];
|
||||
|
||||
return {
|
||||
events: parsedEvents,
|
||||
dataError:
|
||||
parsedEvents.length === 0 ? { message: `'${config.name}': ${t("calendar.noEventsFound")}` } : undefined,
|
||||
};
|
||||
}, [icalData, icalError, config.name, t]);
|
||||
|
||||
useEffect(() => {
|
||||
const { showName = false } = config?.params || {};
|
||||
let events = [];
|
||||
|
||||
if (!icalError && icalData && !icalData.error) {
|
||||
if (!icalData.data) {
|
||||
icalData.error = { message: `'${config.name}': ${t("calendar.errorWhenLoadingData")}` };
|
||||
return;
|
||||
}
|
||||
|
||||
const jCal = ICAL.parse(icalData.data);
|
||||
const vCalendar = new ICAL.Component(jCal);
|
||||
|
||||
const buildEvent = (event, type) => {
|
||||
return {
|
||||
id: event.getFirstPropertyValue("uid"),
|
||||
type,
|
||||
title: event.getFirstPropertyValue("summary"),
|
||||
rrule: event.getFirstPropertyValue("rrule"),
|
||||
dtstart:
|
||||
event.getFirstPropertyValue("dtstart") ||
|
||||
event.getFirstPropertyValue("due") ||
|
||||
event.getFirstPropertyValue("completed") ||
|
||||
ICAL.Time.now(), // handles events without a date
|
||||
dtend:
|
||||
event.getFirstPropertyValue("dtend") ||
|
||||
event.getFirstPropertyValue("due") ||
|
||||
event.getFirstPropertyValue("completed") ||
|
||||
ICAL.Time.now(), // handles events without a date
|
||||
location: event.getFirstPropertyValue("location"),
|
||||
status: event.getFirstPropertyValue("status"),
|
||||
url: event.getFirstPropertyValue("url"),
|
||||
};
|
||||
};
|
||||
|
||||
const getEvents = () => {
|
||||
const vEvents = vCalendar.getAllSubcomponents("vevent").map((event) => buildEvent(event, "vevent"));
|
||||
|
||||
const vTodos = vCalendar.getAllSubcomponents("vtodo").map((todo) => buildEvent(todo, "vtodo"));
|
||||
|
||||
return [...vEvents, ...vTodos];
|
||||
};
|
||||
|
||||
events = getEvents();
|
||||
if (events.length === 0) {
|
||||
icalData.error = { message: `'${config.name}': ${t("calendar.noEventsFound")}` };
|
||||
}
|
||||
}
|
||||
|
||||
const startDate = DateTime.fromISO(params.start);
|
||||
const endDate = DateTime.fromISO(params.end);
|
||||
|
||||
if (icalError || events.length === 0 || !startDate.isValid || !endDate.isValid) {
|
||||
if (events.length === 0 || !startDate.isValid || !endDate.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,7 +115,7 @@ export default function Integration({ config, params, setEvents, hideErrors, tim
|
||||
return occurrences;
|
||||
};
|
||||
|
||||
const eventsToAdd = [];
|
||||
const eventsToAdd = {};
|
||||
events.forEach((event) => {
|
||||
const occurrences = getOcurrencesFromRange(event);
|
||||
|
||||
@@ -154,8 +156,8 @@ export default function Integration({ config, params, setEvents, hideErrors, tim
|
||||
});
|
||||
|
||||
setEvents((prevEvents) => ({ ...prevEvents, ...eventsToAdd }));
|
||||
}, [icalData, icalError, config, params, setEvents, timezone, t]);
|
||||
}, [events, config, params, setEvents, timezone]);
|
||||
|
||||
const error = icalError ?? icalData?.error;
|
||||
const error = icalError ?? icalData?.error ?? dataError;
|
||||
return error && !hideErrors && <Error error={{ message: `${config.type}: ${error.message ?? error}` }} />;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { useWidgetAPI } = vi.hoisted(() => ({
|
||||
@@ -12,6 +12,45 @@ vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
|
||||
import Integration from "./ical";
|
||||
|
||||
describe("widgets/calendar/integrations/ical", () => {
|
||||
it("reports a missing calendar payload without mutating the response", () => {
|
||||
const data = {};
|
||||
useWidgetAPI.mockReturnValue({ data, error: undefined });
|
||||
|
||||
render(
|
||||
<Integration
|
||||
config={{ name: "Work", type: "ical" }}
|
||||
params={{ start: "2099-01-01", end: "2099-01-02" }}
|
||||
setEvents={vi.fn()}
|
||||
hideErrors={false}
|
||||
timezone="utc"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/'Work': calendar\.errorWhenLoadingData/)).toBeInTheDocument();
|
||||
expect(data).toEqual({});
|
||||
});
|
||||
|
||||
it("reports a calendar with no events without mutating the response", () => {
|
||||
const data = {
|
||||
data: ["BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//Test//EN", "END:VCALENDAR", ""].join("\n"),
|
||||
};
|
||||
const originalData = structuredClone(data);
|
||||
useWidgetAPI.mockReturnValue({ data, error: undefined });
|
||||
|
||||
render(
|
||||
<Integration
|
||||
config={{ name: "Empty", type: "ical" }}
|
||||
params={{ start: "2099-01-01", end: "2099-01-02" }}
|
||||
setEvents={vi.fn()}
|
||||
hideErrors={false}
|
||||
timezone="utc"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/'Empty': calendar\.noEventsFound/)).toBeInTheDocument();
|
||||
expect(data).toEqual(originalData);
|
||||
});
|
||||
|
||||
it("adds parsed events within the date range", async () => {
|
||||
useWidgetAPI.mockReturnValue({
|
||||
data: {
|
||||
|
||||
Reference in New Issue
Block a user