fix(ui): fix ts issues

This commit is contained in:
Lukasz Mierzwa
2026-03-09 15:41:44 +00:00
committed by Łukasz Mierzwa
parent 9aa2f93e00
commit 386dc3a9a4
15 changed files with 110 additions and 109 deletions
+6 -13
View File
@@ -20,18 +20,14 @@ const MockSettings = (defaultFilters: string[]) => {
const filtersBase64 = btoa(JSON.stringify(defaultFilters));
const settings = document.createElement("span");
settings.id = "settings";
(settings as any).dataset = {
defaultFiltersBase64: filtersBase64,
};
settings.dataset.defaultFiltersBase64 = filtersBase64;
return settings;
});
};
const FiltersSetting = (filters: any) => {
const FiltersSetting = (filters: unknown) => {
const settings = document.createElement("span");
(settings as any).dataset = {
defaultFiltersBase64: btoa(JSON.stringify(filters)),
};
settings.dataset.defaultFiltersBase64 = btoa(JSON.stringify(filters));
return ParseDefaultFilters(settings);
};
@@ -45,20 +41,19 @@ describe("SettingsElement()", () => {
const spy = MockSettings([]);
const settings = SettingsElement();
expect(spy).toHaveBeenCalledTimes(1);
expect((settings as any).id).toBe("settings");
expect(settings?.id).toBe("settings");
});
});
describe("ParseDefaultFilters()", () => {
it("returns [] on missing filters attr", () => {
const settings = document.createElement("span");
(settings as any).dataset = {};
expect(ParseDefaultFilters(settings)).toHaveLength(0);
});
it("returns [] on empty filters attr", () => {
const settings = document.createElement("span");
(settings as any).dataset = { defaultFiltersBase64: "" };
settings.dataset.defaultFiltersBase64 = "";
expect(ParseDefaultFilters(settings)).toHaveLength(0);
});
@@ -82,9 +77,7 @@ describe("ParseDefaultFilters()", () => {
it("ignores template placeholder values", () => {
// Scenario: default filters attribute rendered with the literal template placeholder should be ignored
const settings = document.createElement("span");
(settings as any).dataset = {
defaultFiltersBase64: "{{ .DefaultFilter }}",
};
settings.dataset.defaultFiltersBase64 = "{{ .DefaultFilter }}";
expect(ParseDefaultFilters(settings)).toHaveLength(0);
});
});
+7 -7
View File
@@ -310,7 +310,7 @@ describe("<AlertAck />", () => {
silenceFormStore.data.setAuthor("karma/ui");
await renderAndClick();
expect(
JSON.parse((fetchMock.callHistory.lastCall()?.options as any).body),
JSON.parse(fetchMock.callHistory.lastCall()!.options.body as string),
).toEqual({
comment: "COMMENT",
createdBy: "karma/ui",
@@ -342,7 +342,7 @@ describe("<AlertAck />", () => {
});
await renderAndClick();
expect(
JSON.parse((fetchMock.callHistory.lastCall()?.options as any).body),
JSON.parse(fetchMock.callHistory.lastCall()!.options.body as string),
).toEqual({
comment: "comment",
createdBy: "me",
@@ -374,7 +374,7 @@ describe("<AlertAck />", () => {
});
await renderAndClick();
expect(
JSON.parse((fetchMock.callHistory.lastCall()?.options as any).body),
JSON.parse(fetchMock.callHistory.lastCall()!.options.body as string),
).toEqual({
comment:
"ACK! This alert was acknowledged using karma on Tue, 01 Feb 2000 00:00:00 GMT",
@@ -407,7 +407,7 @@ describe("<AlertAck />", () => {
});
await renderAndClick();
const comment = JSON.parse(
(fetchMock.callHistory.lastCall()?.options as any).body,
fetchMock.callHistory.lastCall()!.options.body as string,
).comment;
expect(comment).not.toEqual(
"ACK! This alert was acknowledged using karma on Tue Feb 01 2000 00:00:00 GMT",
@@ -432,7 +432,7 @@ describe("<AlertAck />", () => {
});
await renderAndClick();
expect(
JSON.parse((fetchMock.callHistory.lastCall()?.options as any).body),
JSON.parse(fetchMock.callHistory.lastCall()!.options.body as string),
).toEqual({
comment: "FOO: bar",
createdBy: "auth@example.com",
@@ -466,7 +466,7 @@ describe("<AlertAck />", () => {
silenceFormStore.data.setAuthor("bob@example.com");
await renderAndClick();
expect(
JSON.parse((fetchMock.callHistory.lastCall()?.options as any).body),
JSON.parse(fetchMock.callHistory.lastCall()!.options.body as string),
).toEqual({
comment: "FOO: bar",
createdBy: "bob@example.com",
@@ -499,7 +499,7 @@ describe("<AlertAck />", () => {
silenceFormStore.data.setAuthor("");
await renderAndClick();
expect(
JSON.parse((fetchMock.callHistory.lastCall()?.options as any).body),
JSON.parse(fetchMock.callHistory.lastCall()!.options.body as string),
).toEqual({
comment: "FOO: bar",
createdBy: "me",
+13 -17
View File
@@ -6,6 +6,7 @@ import fetchMock from "@fetch-mock/jest";
import { useInView } from "react-intersection-observer";
import { mockInViewResponse } from "__fixtures__/InView";
import { MockAlertGroup, MockAlert } from "__fixtures__/Alerts";
import {
EmptyHistoryResponse,
@@ -289,10 +290,9 @@ describe("<AlertHistory />", () => {
},
);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
false,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(false),
);
MockAlerts(3);
const { unmount } = render(
@@ -320,10 +320,9 @@ describe("<AlertHistory />", () => {
},
);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
false,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(false),
);
MockAlerts(3);
const { rerender, unmount } = render(
@@ -334,10 +333,9 @@ describe("<AlertHistory />", () => {
});
expect(fetchMock.callHistory.calls()).toHaveLength(0);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
true,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(true),
);
rerender(<AlertHistory group={group} grid={grid}></AlertHistory>);
await act(async () => {
@@ -361,11 +359,9 @@ describe("<AlertHistory />", () => {
},
);
const inView = true;
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
inView,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(true),
);
MockAlerts(3);
const { unmount } = render(
@@ -1,7 +1,7 @@
import { render, fireEvent, waitFor } from "@testing-library/react";
import { MockThemeContext } from "__fixtures__/Theme";
import { Settings } from "Stores/Settings";
import { Settings, type CollapseStateT } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
import { AlertGroupCollapseConfiguration } from "./AlertGroupCollapseConfiguration";
@@ -26,7 +26,9 @@ describe("<AlertGroupCollapseConfiguration />", () => {
});
it("resets stored config to defaults if it is invalid", async () => {
settingsStore.alertGroupConfig.setDefaultCollapseState("foo" as any);
settingsStore.alertGroupConfig.setDefaultCollapseState(
"foo" as unknown as CollapseStateT,
);
const { container } = renderConfiguration();
const select = container.querySelector("div.react-select__value-container");
expect(select?.textContent).toBe(
@@ -2,7 +2,7 @@ import { render, fireEvent } from "@testing-library/react";
import { MockThemeContext } from "__fixtures__/Theme";
import { useFetchGetMock } from "__fixtures__/useFetchGet";
import { Settings } from "Stores/Settings";
import { Settings, type SortOrderT } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
import { AlertGroupSortConfiguration } from "./AlertGroupSortConfiguration";
@@ -43,7 +43,7 @@ describe("<AlertGroupSortConfiguration />", () => {
});
it("invalid sortOrder value is reset on mount", () => {
settingsStore.gridConfig.setSortOrder("badValue" as any);
settingsStore.gridConfig.setSortOrder("badValue" as unknown as SortOrderT);
renderConfiguration();
expect(settingsStore.gridConfig.config.sortOrder).toBe(
settingsStore.gridConfig.options.default.value,
+1 -1
View File
@@ -145,7 +145,7 @@ describe("<ModalInner />", () => {
it("scroll isn't enabled if ref is null", () => {
const useRefSpy = jest.spyOn(React, "useRef").mockImplementation(() =>
Object.defineProperty({} as any, "current", {
Object.defineProperty({} as { current: unknown }, "current", {
get: () => null,
set: () => {},
}),
@@ -345,7 +345,7 @@ describe("History localStorage", () => {
it("localStored persists observable changes to localStorage", () => {
const { localStored } = require("Common/LocalStore");
const { runInAction } = require("mobx");
const store = localStored("test_persist_key", { filters: [] as any[] });
const store = localStored("test_persist_key", { filters: [] as unknown[] });
// Flush the initial write (reaction fires via setTimeout 0)
jest.runAllTimers();
+5 -5
View File
@@ -4,7 +4,7 @@ import { render, screen } from "@testing-library/react";
import { ErrorBoundary } from "./ErrorBoundary";
let consoleSpy: any;
let consoleSpy: jest.SpyInstance;
beforeEach(() => {
jest.useFakeTimers();
@@ -76,7 +76,7 @@ describe("<ErrorBoundary />", () => {
// Verifies that reloadApp uses functional setState to decrement reloadSeconds
const boundary = new ErrorBoundary({ children: <span /> });
const setStateSpy = jest.spyOn(boundary, "setState");
(boundary as any).state = { cachedError: null, reloadSeconds: 2 };
boundary.state = { cachedError: null, reloadSeconds: 2 };
boundary.reloadApp();
@@ -91,7 +91,7 @@ describe("<ErrorBoundary />", () => {
// Verifies that reloadApp calls window.location.reload when reloadSeconds <= 1 (line 65)
const boundary = new ErrorBoundary({ children: <span /> });
const setStateSpy = jest.spyOn(boundary, "setState");
(boundary as any).state = { cachedError: null, reloadSeconds: 1 };
boundary.state = { cachedError: null, reloadSeconds: 1 };
boundary.reloadApp();
@@ -103,7 +103,7 @@ describe("<ErrorBoundary />", () => {
const boundary = new ErrorBoundary({ children: <span /> });
const setStateSpy = jest.spyOn(boundary, "setState");
const error = new Error("Test error");
(boundary as any).state = { cachedError: error, reloadSeconds: 60 };
boundary.state = { cachedError: error, reloadSeconds: 60 };
boundary.componentDidCatch(error, { componentStack: "" });
@@ -115,7 +115,7 @@ describe("<ErrorBoundary />", () => {
const boundary = new ErrorBoundary({ children: <span /> });
const setIntervalSpy = jest.spyOn(global, "setInterval");
const error = new Error("Test error");
(boundary as any).timer = 123;
boundary.timer = 123 as unknown as ReturnType<typeof setInterval>;
boundary.componentDidCatch(error, { componentStack: "" });
+1 -1
View File
@@ -511,7 +511,7 @@ describe("useFetchGet", () => {
});
return "ok";
},
} as any);
} as unknown as Response);
jest.useRealTimers();
const Component = () => {
+24 -29
View File
@@ -4,6 +4,8 @@ import { renderHook } from "@testing-library/react";
import { useInView } from "react-intersection-observer";
import { mockInViewResponse } from "__fixtures__/InView";
import { useFlashTransition, defaultProps } from "./useFlashTransition";
describe("useFlashTransition", () => {
@@ -12,10 +14,9 @@ describe("useFlashTransition", () => {
});
it("does nothing when value changes but element is out of viewport", () => {
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
false,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(false),
);
let value = 0;
const { result, rerender } = renderHook(() => useFlashTransition(value));
@@ -27,10 +28,9 @@ describe("useFlashTransition", () => {
});
it("flashes when value changes and element is in viewport", () => {
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
true,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(true),
);
let value = 2;
const { result, rerender } = renderHook(() => useFlashTransition(value));
@@ -46,10 +46,9 @@ describe("useFlashTransition", () => {
});
it("flashes when value changes and element moves into viewport", () => {
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
false,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(false),
);
let value = 2;
const { result, rerender } = renderHook(() => useFlashTransition(value));
@@ -59,10 +58,9 @@ describe("useFlashTransition", () => {
expect(result.current.props).toMatchObject(defaultProps);
act(() => {
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
true,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(true),
);
});
rerender();
expect(result.current.props).toMatchObject({
@@ -73,10 +71,9 @@ describe("useFlashTransition", () => {
});
it("stops flashing props.onEntered is called", () => {
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
true,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(true),
);
let value = 2;
const { result, rerender } = renderHook(() => useFlashTransition(value));
@@ -89,25 +86,23 @@ describe("useFlashTransition", () => {
enter: true,
});
act(() => (result.current.props as any).onEntered());
act(() => result.current.props.onEntered!({} as HTMLElement, false));
expect(result.current.props).toMatchObject(defaultProps);
});
it("unmounts cleanly when not flashing", () => {
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
false,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(false),
);
const { unmount } = renderHook(() => useFlashTransition(4));
unmount();
});
it("unmounts cleanly when flashing", () => {
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
false,
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(false),
);
let value = 5;
const { rerender, unmount } = renderHook(() => useFlashTransition(value));
+11 -5
View File
@@ -12,7 +12,7 @@ import {
NewUnappliedFilter,
} from "Stores/AlertStore";
declare let global: any;
declare let global: typeof globalThis;
beforeEach(() => {
fetchMock.mockReset();
@@ -491,12 +491,16 @@ describe("UpdateLocationSearch", () => {
});
it("{a: foo} is not pushed to location.search", () => {
UpdateLocationSearch({ a: "foo" } as any);
UpdateLocationSearch({ a: "foo" } as unknown as Parameters<
typeof UpdateLocationSearch
>[0]);
expect(window.location.search).toBe("?q=");
});
it("{a: foo, q: bar} is pushed to location.search", () => {
UpdateLocationSearch({ a: "foo", q: ["bar"] } as any);
UpdateLocationSearch({ a: "foo", q: ["bar"] } as unknown as Parameters<
typeof UpdateLocationSearch
>[0]);
expect(window.location.search).toBe("?q=bar");
});
@@ -649,7 +653,7 @@ describe("AlertStore.fetch", () => {
type: "opaque",
body: "auth needed",
json: jest.fn(() => EmptyAPIResponse()),
}) as any,
}) as unknown as Promise<Response>,
);
await expect(
@@ -685,7 +689,9 @@ describe("AlertStore.fetch", () => {
const store = new AlertStore(["label=value"]);
// initial fetch, should update settings
store.settings.setValues({ foo: "bar" } as any);
store.settings.setValues({ foo: "bar" } as unknown as Parameters<
typeof store.settings.setValues
>[0]);
await expect(
store.fetch("", false, "", "", false, {}, 5, {}),
).resolves.toBeUndefined();
+13
View File
@@ -0,0 +1,13 @@
import type { InViewHookResponse } from "react-intersection-observer";
const mockInViewResponse = (inView: boolean): InViewHookResponse => {
const ref = jest.fn() as (node?: Element | null) => void;
const response = Object.assign([ref, inView, undefined] as const, {
ref,
inView,
entry: undefined,
});
return response as unknown as InViewHookResponse;
};
export { mockInViewResponse };
+9 -10
View File
@@ -20,11 +20,12 @@ import { EmptyAPIResponse } from "__fixtures__/Fetch";
import { DefaultsBase64 } from "__fixtures__/Defaults";
import { mockMatchMedia } from "__fixtures__/matchMedia";
const settingsElement = {
dataset: {
defaultFiltersBase64: "WyJmb289YmFyIiwiYmFyPX5iYXoiXQ==",
},
};
const settingsElement = document.createElement("span");
settingsElement.dataset.defaultFiltersBase64 =
"WyJmb289YmFyIiwiYmFyPX5iYXoiXQ==";
const defaultsElement = document.createElement("span");
defaultsElement.innerHTML = DefaultsBase64;
beforeEach(() => {
window.matchMedia = mockMatchMedia({});
@@ -41,7 +42,7 @@ it("renders without crashing with missing defaults div", async () => {
.spyOn(global.document, "getElementById")
.mockImplementation((name: string) => {
return name === "settings"
? (settingsElement as any)
? settingsElement
: name === "defaults"
? null
: name === "root"
@@ -71,11 +72,9 @@ it("renders without crashing with defaults present", async () => {
.spyOn(global.document, "getElementById")
.mockImplementation((name: string) => {
return name === "settings"
? (settingsElement as any)
? settingsElement
: name === "defaults"
? {
innerHTML: DefaultsBase64,
}
? defaultsElement
: name === "root"
? root
: null;
+5 -10
View File
@@ -1,11 +1,11 @@
import React from "react";
import "@testing-library/jest-dom";
import fetchMock, { manageFetchMockGlobally } from "@fetch-mock/jest";
import { useInView } from "react-intersection-observer";
import { mockInViewResponse } from "__fixtures__/InView";
import { createMocks as createIdleTimerMocks } from "react-idle-timer";
import { configure } from "mobx";
@@ -35,9 +35,6 @@ jest.mock("react-intersection-observer");
FetchRetryConfig.minTimeout = 2;
FetchRetryConfig.maxTimeout = 10;
// floating-ui uses useLayoutEffect
React.useLayoutEffect = React.useEffect;
// Fail tests on any console output except explicitly allowed messages
const allowedMessages = [
// React.lazy suspended resource warnings (React 19 testing limitation)
@@ -66,9 +63,7 @@ beforeEach(() => {
useFetchGet as jest.MockedFunction<typeof useFetchGetMock>
).mockImplementation(useFetchGetMock);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue([
jest.fn(),
true,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
] as any);
(useInView as jest.MockedFunction<typeof useInView>).mockReturnValue(
mockInViewResponse(true),
);
});
+8 -6
View File
@@ -7,6 +7,11 @@ class ResizeObserverPolyfill {
disconnect() {}
}
interface GlobalWithResizeObserver {
ResizeObserver: typeof ResizeObserverPolyfill;
window: { ResizeObserver: typeof ResizeObserverPolyfill };
}
export default class CustomTestEnvironment extends TestEnvironment {
async setup() {
await super.setup();
@@ -14,11 +19,8 @@ export default class CustomTestEnvironment extends TestEnvironment {
this.global.Response = Response;
this.global.ReadableStream = ReadableStream;
this.global.fetch = fetch;
// Polyfill ResizeObserver for react-cool-dimensions (checks window.ResizeObserver)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.global as any).ResizeObserver = ResizeObserverPolyfill;
// Also set on window for libraries that check window.ResizeObserver
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.global as any).window.ResizeObserver = ResizeObserverPolyfill;
const g = this.global as unknown as GlobalWithResizeObserver;
g.ResizeObserver = ResizeObserverPolyfill;
g.window.ResizeObserver = ResizeObserverPolyfill;
}
}