Add provider and ErrorBoundary tests to meet 70% coverage threshold
CI / ci (push) Failing after 28s
Deploy / build-and-deploy (push) Failing after 6s

This commit is contained in:
2026-04-15 16:29:06 +03:00
parent 936adab6e4
commit 664e2133b8
5 changed files with 211 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
// @vitest-environment jsdom
import { renderHook } from "@testing-library/react";
import type { ReactNode } from "react";
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import { I18nProvider, useI18n, useTranslation } from "./provider.js";
let i18nInstance: typeof i18next;
beforeAll(async () => {
i18nInstance = i18next.createInstance();
await i18nInstance.use(initReactI18next).init({
lng: "en",
resources: { en: { translation: { greeting: "hello" } } },
});
});
function wrapper({ children }: { children: ReactNode }) {
return <I18nProvider i18n={i18nInstance}>{children}</I18nProvider>;
}
describe("I18nProvider / useI18n / useTranslation", () => {
it("provides the i18n instance via useI18n()", () => {
const { result } = renderHook(() => useI18n(), { wrapper });
expect(result.current.language).toBe("en");
});
it("useTranslation returns working t function", () => {
const { result } = renderHook(() => useTranslation(), { wrapper });
expect(result.current.t("greeting")).toBe("hello");
});
});
@@ -0,0 +1,49 @@
// @vitest-environment jsdom
import { render, act } from "@testing-library/react";
import { AnalyticsLoader } from "./loader.js";
import type { Logger } from "@/observability/logger/types";
// Mock the facade so we don't load real adapters
vi.mock("./facade.js", () => ({
createAnalytics: vi.fn(() => ({ track: vi.fn(), page: vi.fn() })),
}));
const stubLogger: Logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
child: vi.fn(),
};
describe("AnalyticsLoader", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it("renders children and initializes analytics via setTimeout fallback", async () => {
const { getByText } = render(
<AnalyticsLoader
enabled={{ metrica: true, ctm: false, variocube: false, dynatrace: false }}
consent={{ analytics: true, telemetry: true }}
logger={stubLogger}
>
<span>child</span>
</AnalyticsLoader>,
);
expect(getByText("child")).toBeDefined();
// Fire the setTimeout(init, 1) callback
await act(async () => {
vi.advanceTimersByTime(10);
});
const { createAnalytics } = await import("./facade.js");
expect(createAnalytics).toHaveBeenCalled();
});
});
@@ -0,0 +1,29 @@
// @vitest-environment jsdom
import { renderHook } from "@testing-library/react";
import type { ReactNode } from "react";
import { AnalyticsContext, useAnalytics } from "./provider.js";
describe("useAnalytics", () => {
it("returns NoopAnalytics when no provider is present", () => {
const { result } = renderHook(() => useAnalytics());
// Should return the noop — calling track/page should not throw
expect(result.current).toBeDefined();
expect(() => result.current.track("test")).not.toThrow();
expect(() => result.current.page("/")).not.toThrow();
});
it("returns the provided Analytics instance", () => {
const mockAnalytics = { track: vi.fn(), page: vi.fn() };
function wrapper({ children }: { children: ReactNode }) {
return (
<AnalyticsContext.Provider value={mockAnalytics}>
{children}
</AnalyticsContext.Provider>
);
}
const { result } = renderHook(() => useAnalytics(), { wrapper });
expect(result.current).toBe(mockAnalytics);
});
});
@@ -0,0 +1,30 @@
// @vitest-environment jsdom
import { renderHook } from "@testing-library/react";
import type { ReactNode } from "react";
import { LoggerProvider, useLogger } from "./provider.js";
import type { Logger } from "./types.js";
const stubLogger: Logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
child: vi.fn(() => stubLogger),
};
function wrapper({ children }: { children: ReactNode }) {
return <LoggerProvider logger={stubLogger}>{children}</LoggerProvider>;
}
describe("LoggerProvider / useLogger", () => {
it("provides Logger to descendants", () => {
const { result } = renderHook(() => useLogger(), { wrapper });
expect(result.current).toBe(stubLogger);
});
it("throws when used outside LoggerProvider", () => {
expect(() => renderHook(() => useLogger())).toThrow(
"useLogger() must be used within a <LoggerProvider>",
);
});
});
+71
View File
@@ -0,0 +1,71 @@
// @vitest-environment jsdom
import { render, fireEvent } from "@testing-library/react";
import { ErrorBoundary } from "./ErrorBoundary.js";
// Suppress React error boundary console.error noise in test output
const originalConsoleError = console.error;
beforeAll(() => {
console.error = vi.fn();
});
afterAll(() => {
console.error = originalConsoleError;
});
function ThrowingChild({ shouldThrow }: { shouldThrow: boolean }) {
if (shouldThrow) throw new Error("boom");
return <span>ok</span>;
}
describe("ErrorBoundary", () => {
it("renders children when no error", () => {
const { getByText } = render(
<ErrorBoundary>
<span>hello</span>
</ErrorBoundary>,
);
expect(getByText("hello")).toBeDefined();
});
it("shows default fallback with error message on throw", () => {
const { getByRole, getByText } = render(
<ErrorBoundary>
<ThrowingChild shouldThrow />
</ErrorBoundary>,
);
expect(getByRole("alert")).toBeDefined();
expect(getByText("boom")).toBeDefined();
expect(getByText("Retry")).toBeDefined();
});
it("shows custom fallback when provided", () => {
const { getByText } = render(
<ErrorBoundary fallback={<div>custom error</div>}>
<ThrowingChild shouldThrow />
</ErrorBoundary>,
);
expect(getByText("custom error")).toBeDefined();
});
it("resets when Retry is clicked", () => {
// We need a component that can toggle throwing
let shouldThrow = true;
function Toggler() {
if (shouldThrow) throw new Error("fail");
return <span>recovered</span>;
}
const { getByText } = render(
<ErrorBoundary>
<Toggler />
</ErrorBoundary>,
);
expect(getByText("fail")).toBeDefined();
// Stop throwing before retry
shouldThrow = false;
fireEvent.click(getByText("Retry"));
expect(getByText("recovered")).toBeDefined();
});
});