feat: refine widget interactions

This commit is contained in:
Thomas Camlong authored and ajnart committed 2026-08-14 11:07:08 +02:00
1 parent a8a7545f40
commit 842e86012d
107 files changed
+3144 -1233

No files matched your search

+37 -1
View File
@@ -14,7 +14,8 @@ vi.mock("@homarr/core/infrastructure/http", () => ({
const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync);
const mockCreateCertificateAgent = vi.mocked(createCertificateAgentAsync);
const mockCloseDispatcher = vi.fn();
const mockDispatcher = { close: mockCloseDispatcher };
const mockDestroyDispatcher = vi.fn().mockResolvedValue(undefined);
const mockDispatcher = { close: mockCloseDispatcher, destroy: mockDestroyDispatcher };
beforeEach(() => {
vi.clearAllMocks();
@@ -156,6 +157,41 @@ describe("timetable response bounds", () => {
expect(mockCloseDispatcher).toHaveBeenCalledOnce();
});
it("destroys the pinned station dispatcher when response handling fails", async () => {
mockFetch.mockResolvedValue(new Response("not json"));
await expect(
timetableSearchStationsRequestHandler
.handler({
baseUrl: "https://timetable.example.com",
query: "invalid-stations",
pinnedAddresses: [{ address: "93.184.216.34", family: 4 }],
})
.getDataAsync(),
).rejects.toThrow();
expect(mockDestroyDispatcher).toHaveBeenCalledOnce();
expect(mockCloseDispatcher).not.toHaveBeenCalled();
});
it("destroys the pinned timetable dispatcher when response handling fails", async () => {
mockFetch.mockResolvedValue(new Response("not json"));
await expect(
timetableGetTimetableRequestHandler
.handler({
baseUrl: "https://timetable.example.com",
stationId: "invalid-departures",
limit: 1,
pinnedAddresses: [{ address: "93.184.216.34", family: 4 }],
})
.getDataAsync(),
).rejects.toThrow();
expect(mockDestroyDispatcher).toHaveBeenCalledOnce();
expect(mockCloseDispatcher).not.toHaveBeenCalled();
});
it("never caches more departures than requested", async () => {
mockFetch.mockResolvedValue(
new Response(
+16 -4
View File
@@ -124,9 +124,10 @@ const searchStationsAsync = async (
query: string,
pinnedAddresses?: TimetableResolvedAddress[],
): Promise<Station[]> => {
const dispatcher = pinnedAddresses ? await createPinnedTimetableDispatcherAsync(baseUrl, pinnedAddresses) : undefined;
let dispatcher = pinnedAddresses ? await createPinnedTimetableDispatcherAsync(baseUrl, pinnedAddresses) : undefined;
let response: Awaited<ReturnType<typeof fetchWithTrustedCertificatesAsync>> | undefined;
try {
const response = await fetchWithTrustedCertificatesAsync(
response = await fetchWithTrustedCertificatesAsync(
buildUrl(baseUrl, "/timetable/api/completion.json", { term: query, show_ids: 1, nofavorites: 1 }),
{ ...timetableFetchOptions, dispatcher },
);
@@ -138,6 +139,11 @@ const searchStationsAsync = async (
.filter((item) => supportedStationTypes.some((type) => item.iconclass.endsWith(type)))
.map((item) => (item.id !== undefined ? { id: item.id, name: item.label } : null))
.filter((item) => item !== null);
} catch (error) {
if (response?.body) await response.body.cancel().catch(() => {});
await dispatcher?.destroy().catch(() => {});
dispatcher = undefined;
throw error;
} finally {
await dispatcher?.close();
}
@@ -161,9 +167,10 @@ const getTimetableAsync = async (
minute: "2-digit",
hour12: false,
}).format(now);
const dispatcher = pinnedAddresses ? await createPinnedTimetableDispatcherAsync(baseUrl, pinnedAddresses) : undefined;
let dispatcher = pinnedAddresses ? await createPinnedTimetableDispatcherAsync(baseUrl, pinnedAddresses) : undefined;
let response: Awaited<ReturnType<typeof fetchWithTrustedCertificatesAsync>> | undefined;
try {
const response = await fetchWithTrustedCertificatesAsync(
response = await fetchWithTrustedCertificatesAsync(
buildUrl(baseUrl, "/timetable/api/stationboard.json", {
stop: options.stationId,
limit: options.limit,
@@ -205,6 +212,11 @@ const getTimetableAsync = async (
};
}),
};
} catch (error) {
if (response?.body) await response.body.cancel().catch(() => {});
await dispatcher?.destroy().catch(() => {});
dispatcher = undefined;
throw error;
} finally {
await dispatcher?.close();
}