diff --git a/src/i18n/provider.test.tsx b/src/i18n/provider.test.tsx
new file mode 100644
index 00000000..52e3cd32
--- /dev/null
+++ b/src/i18n/provider.test.tsx
@@ -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 {children};
+}
+
+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");
+ });
+});
diff --git a/src/observability/analytics/loader.test.tsx b/src/observability/analytics/loader.test.tsx
new file mode 100644
index 00000000..d03b34f8
--- /dev/null
+++ b/src/observability/analytics/loader.test.tsx
@@ -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(
+
+ child
+ ,
+ );
+
+ 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();
+ });
+});
diff --git a/src/observability/analytics/provider.test.tsx b/src/observability/analytics/provider.test.tsx
new file mode 100644
index 00000000..c86af86f
--- /dev/null
+++ b/src/observability/analytics/provider.test.tsx
@@ -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 (
+
+ {children}
+
+ );
+ }
+
+ const { result } = renderHook(() => useAnalytics(), { wrapper });
+ expect(result.current).toBe(mockAnalytics);
+ });
+});
diff --git a/src/observability/logger/provider.test.tsx b/src/observability/logger/provider.test.tsx
new file mode 100644
index 00000000..f5b88aa2
--- /dev/null
+++ b/src/observability/logger/provider.test.tsx
@@ -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 {children};
+}
+
+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 ",
+ );
+ });
+});
diff --git a/src/ui/errors/ErrorBoundary.test.tsx b/src/ui/errors/ErrorBoundary.test.tsx
new file mode 100644
index 00000000..f3f6dd74
--- /dev/null
+++ b/src/ui/errors/ErrorBoundary.test.tsx
@@ -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 ok;
+}
+
+describe("ErrorBoundary", () => {
+ it("renders children when no error", () => {
+ const { getByText } = render(
+
+ hello
+ ,
+ );
+ expect(getByText("hello")).toBeDefined();
+ });
+
+ it("shows default fallback with error message on throw", () => {
+ const { getByRole, getByText } = render(
+
+
+ ,
+ );
+ expect(getByRole("alert")).toBeDefined();
+ expect(getByText("boom")).toBeDefined();
+ expect(getByText("Retry")).toBeDefined();
+ });
+
+ it("shows custom fallback when provided", () => {
+ const { getByText } = render(
+ custom error}>
+
+ ,
+ );
+ 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 recovered;
+ }
+
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ expect(getByText("fail")).toBeDefined();
+
+ // Stop throwing before retry
+ shouldThrow = false;
+ fireEvent.click(getByText("Retry"));
+
+ expect(getByText("recovered")).toBeDefined();
+ });
+});