diff --git a/src/components/balance/PriceFetchControl.test.tsx b/src/components/balance/PriceFetchControl.test.tsx
index d70ca60..f4deb51 100644
--- a/src/components/balance/PriceFetchControl.test.tsx
+++ b/src/components/balance/PriceFetchControl.test.tsx
@@ -11,6 +11,10 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
// Mocks — declared before imports to satisfy vi.mock hoisting
// ---------------------------------------------------------------------------
+// React's hooks from the shared harness (#409): the consent dialog opens on a
+// click (internal state), so the render test below presets its slot (#437).
+vi.mock("react", async (orig) => (await import("../../test/hookHarness")).fakeReact(await orig()));
+
vi.mock("../../hooks/useIsPremium", () => ({
useIsPremium: vi.fn(),
}));
@@ -40,6 +44,7 @@ vi.mock("lucide-react", () => ({
// Imports (after mock declarations)
// ---------------------------------------------------------------------------
+import type { ReactNode } from "react";
import { useIsPremium } from "../../hooks/useIsPremium";
import { prices } from "../../services/balance.service";
import type { PriceResult } from "../../services/balance.service";
@@ -48,6 +53,7 @@ import {
setPreference,
} from "../../services/userPreferenceService";
import { renderToStaticMarkup } from "react-dom/server";
+import { hookHarness } from "../../test/hookHarness";
import PriceFetchControl, {
__resetBestEffortDismissForTests,
} from "./PriceFetchControl";
@@ -56,6 +62,7 @@ import PriceFetchControl, {
// Helpers
// ---------------------------------------------------------------------------
+const harness = hookHarness();
const mockUseIsPremium = vi.mocked(useIsPremium);
const mockFetchPrice = vi.mocked(prices.fetchPrice);
const mockGetPreference = vi.mocked(getPreference);
@@ -363,18 +370,35 @@ describe("PriceFetchControl — displayed texts", () => {
__resetBestEffortDismissForTests();
vi.resetAllMocks();
setPremium(true);
+ harness.clear();
});
- it("names the fetch button and the stock best-effort notice through t", () => {
- const html = renderToStaticMarkup(
- ,
+ /**
+ * One render of a stock's control, its hooks from the harness. `consentOpen`
+ * presets slot 0, the control's first hook (`showConsentModal`). The slots
+ * follow the order of the control's hook calls: a hook of any kind added
+ * before it shifts them without a word, and the preset lands on another hook
+ * (one slot per hook call: the `slot` helper of `fakeReact`,
+ * src/test/hookHarness.ts).
+ */
+ function markup(consentOpen = false): string {
+ if (consentOpen) harness.slots[0] = true;
+ return renderToStaticMarkup(
+ harness.render(
+ () =>
+ PriceFetchControl({
+ symbol: "AAPL",
+ date: "2026-04-25",
+ categoryKind: "priced",
+ assetType: "stock",
+ onPriceFetched: vi.fn(),
+ }) as ReactNode,
+ ),
);
+ }
+
+ it("names the fetch button and the stock best-effort notice through t", () => {
+ const html = markup();
expect(html).toContain('aria-label="t(balance.priceFetching.button)"');
expect(html).toContain(">t(balance.priceFetching.button)<");
expect(html).toContain(">t(balance.priceFetching.bestEffortNotice)<");
@@ -382,4 +406,15 @@ describe("PriceFetchControl — displayed texts", () => {
expect(html).not.toContain(">best-effort<");
expect(html).toContain('aria-label="t(common.close)"');
});
+
+ it("names the consent dialog through t once it is open (#437)", () => {
+ expect(markup()).not.toContain('role="dialog"');
+ harness.clear();
+ const html = markup(true);
+ expect(html).toContain('role="dialog"');
+ expect(html).toContain(">t(balance.priceFetching.consent.title)<");
+ expect(html).toContain(">t(balance.priceFetching.consent.body)<");
+ expect(html).toContain(">t(balance.priceFetching.consent.decline)<");
+ expect(html).toContain(">t(balance.priceFetching.consent.accept)<");
+ });
});
diff --git a/src/components/settings/LicenseKeyForm.render.test.tsx b/src/components/settings/LicenseKeyForm.render.test.tsx
index c28ba59..6ecacc1 100644
--- a/src/components/settings/LicenseKeyForm.render.test.tsx
+++ b/src/components/settings/LicenseKeyForm.render.test.tsx
@@ -1,5 +1,6 @@
// Render tests for the key entry form of the License card (#347). No jsdom needed:
-// react-dom/server renders in vitest's node environment, and `t` echoes its key.
+// react-dom/server renders in vitest's node environment, and the marked `t` of
+// src/test/i18nMock.ts renders `t(key)`.
import { describe, it, expect, vi } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
@@ -44,4 +45,10 @@ describe("LicenseKeyForm", () => {
expect(render(null, true)).toMatch(/