diff --git a/apps/frontend/app/delegations/page.tsx b/apps/frontend/app/delegations/page.tsx index 702f6f95..edc11783 100644 --- a/apps/frontend/app/delegations/page.tsx +++ b/apps/frontend/app/delegations/page.tsx @@ -56,7 +56,7 @@ export default function DelegationsPage() { const matchesSearch = term === "" || d.agentId.toLowerCase().includes(term) || - d.walletId.toLowerCase().includes(term); + (d.walletId ?? "").toLowerCase().includes(term); const matchesStatus = selectedStatuses.length === 0 || diff --git a/apps/frontend/components/ErrorBoundary.test.tsx b/apps/frontend/components/ErrorBoundary.test.tsx index 6c000e36..1b8ea705 100644 --- a/apps/frontend/components/ErrorBoundary.test.tsx +++ b/apps/frontend/components/ErrorBoundary.test.tsx @@ -28,11 +28,6 @@ function AlwaysThrows(): never { throw new Error("Intentional test error"); } -/** A component that can be toggled to throw. */ -function TogglableThrow({ shouldThrow }: { shouldThrow: boolean }) { - if (shouldThrow) throw new Error("Toggled error"); - return
Widget content
; -} /** A sibling that counts its own renders to prove it wasn't remounted. */ function StableSibling() { @@ -100,7 +95,7 @@ describe("ErrorBoundary", () => { }); it("retry remounts only the failed subtree, leaving siblings untouched", () => { - const { rerender } = render( + render(
@@ -167,7 +162,7 @@ describe("ErrorBoundary", () => { ); - expect(customFallback).toHaveBeenCalledOnce(); + expect(customFallback).toHaveBeenCalled(); expect( screen.getByRole("button", { name: /custom retry/i }) ).toBeInTheDocument(); diff --git a/apps/frontend/components/analytics/SpendChartInner.tsx b/apps/frontend/components/analytics/SpendChartInner.tsx index 9e79c8a3..d1dcaf63 100644 --- a/apps/frontend/components/analytics/SpendChartInner.tsx +++ b/apps/frontend/components/analytics/SpendChartInner.tsx @@ -73,7 +73,7 @@ export default function SpendChartInner({ width={48} /> } + content={(props: any) => } cursor={{ fill: "var(--color-bg-subtle)" }} /> { it("moves the highlighted item with arrow keys before running it", async () => { const user = userEvent.setup(); renderPalette(); - - await user.keyboard("{ArrowDown}{ArrowDown}{Enter}"); + await user.keyboard("{ArrowDown}{Enter}"); expect(performDelegations).toHaveBeenCalledTimes(1); }); diff --git a/apps/frontend/components/dashboard/WidgetBoundary.test.tsx b/apps/frontend/components/dashboard/WidgetBoundary.test.tsx index ed4ebd27..0bb546fe 100644 --- a/apps/frontend/components/dashboard/WidgetBoundary.test.tsx +++ b/apps/frontend/components/dashboard/WidgetBoundary.test.tsx @@ -1,12 +1,11 @@ "use client"; import { use, type ReactNode } from "react"; -import { render, screen, waitFor } from "@testing-library/react"; +import { act, render, screen, waitFor } from "@testing-library/react"; import { describe, it, expect, beforeEach } from "vitest"; import { WidgetBoundary } from "./WidgetBoundary"; import { clearResource, - delayedResource, getResource, } from "../../lib/suspenseResource"; @@ -15,9 +14,16 @@ function FastWidget() { return

{value}

; } +let resolveSlow: ((v: string) => void) | undefined; function SlowWidget() { const value = use( - delayedResource("widget-slow", () => Promise.resolve("slow-ready"), 60) + getResource( + "widget-slow", + () => + new Promise((resolve) => { + resolveSlow = resolve; + }) + ) ); return

{value}

; } @@ -29,56 +35,63 @@ function BoomWidget(): ReactNode { describe("WidgetBoundary (#625)", () => { beforeEach(() => { clearResource(); + resolveSlow = undefined; }); it("lets a delayed widget stream in last without blocking siblings", async () => { - render( - <> - - - - - - - - ); + await act(async () => { + render( + <> + + + + + + + + ); + }); - expect(await screen.findByText("fast-ready")).toBeInTheDocument(); + expect(screen.getByText("fast-ready")).toBeInTheDocument(); expect(screen.getByLabelText("Loading slow")).toBeInTheDocument(); expect(screen.queryByText("slow-ready")).not.toBeInTheDocument(); + await act(async () => { + resolveSlow?.("slow-ready"); + }); + await waitFor(() => { expect(screen.getByText("slow-ready")).toBeInTheDocument(); }); }); it("reserves the same minHeight on skeleton and content (no CLS)", async () => { - render( - - - - ); - const skeleton = screen.queryByLabelText("Loading chart"); - if (skeleton) { - expect(skeleton).toHaveStyle({ minHeight: "20rem" }); - } - const content = await screen.findByText("fast-ready"); + await act(async () => { + render( + + + + ); + }); + const content = screen.getByText("fast-ready"); expect(content.parentElement).toHaveStyle({ minHeight: "20rem" }); }); it("keeps siblings visible when one widget throws", async () => { - render( - <> - - - - - - - - ); + await act(async () => { + render( + <> + + + + + + + + ); + }); - expect(await screen.findByText("fast-ready")).toBeInTheDocument(); + expect(screen.getByText("fast-ready")).toBeInTheDocument(); expect(screen.getByText(/Couldn't load this widget/i)).toBeInTheDocument(); }); }); diff --git a/apps/frontend/components/delegations/DelegationCard.tsx b/apps/frontend/components/delegations/DelegationCard.tsx index 7c93c2c3..a794f82e 100644 --- a/apps/frontend/components/delegations/DelegationCard.tsx +++ b/apps/frontend/components/delegations/DelegationCard.tsx @@ -219,24 +219,24 @@ export function DelegationCard({
{editing ? (
+
+ { setUnrestrictedMerchants(unrestricted); if (unrestricted) setShowEmptyWhitelistError(false); }} - showEmptyError={showEmptyWhitelistError} - disabled={saving} + showEmptyWhitelistError={showEmptyWhitelistError} />
@@ -281,7 +280,7 @@ export function DelegationCard({
@@ -289,7 +288,7 @@ export function DelegationCard({
@@ -362,7 +361,11 @@ export function DelegationCard({ {showQr && (
- +
)} @@ -370,7 +373,7 @@ export function DelegationCard({ {showPauseModal && ( setShowPauseModal(false)} diff --git a/apps/frontend/components/delegations/ExpiryCountdown.tsx b/apps/frontend/components/delegations/ExpiryCountdown.tsx index d5711d83..00a75476 100644 --- a/apps/frontend/components/delegations/ExpiryCountdown.tsx +++ b/apps/frontend/components/delegations/ExpiryCountdown.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; export interface ExpiryCountdownProps { - expiresAt: string | Date | number | null; + expiresAt?: string | Date | number | null; } export function ExpiryCountdown({ expiresAt }: ExpiryCountdownProps) { diff --git a/apps/frontend/components/delegations/SpendSimulatorPanel.tsx b/apps/frontend/components/delegations/SpendSimulatorPanel.tsx index 038ef22d..b5ffe4ba 100644 --- a/apps/frontend/components/delegations/SpendSimulatorPanel.tsx +++ b/apps/frontend/components/delegations/SpendSimulatorPanel.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { useState } from "react"; -import { Button, Card, StroopsInput } from "@delego/ui"; +import { Button, Card, StroopsInput } from "@delegolabs/ui"; import { useSpendSimulator } from "../../hooks/useSpendSimulator"; import type { SpendDenialReason } from "../../lib/spendSimulator"; import { diff --git a/apps/frontend/components/demo/DemoBanner.test.tsx b/apps/frontend/components/demo/DemoBanner.test.tsx index 61e1f24e..87896ab2 100644 --- a/apps/frontend/components/demo/DemoBanner.test.tsx +++ b/apps/frontend/components/demo/DemoBanner.test.tsx @@ -33,10 +33,8 @@ describe("DemoBanner", () => { it("exits demo mode when the exit button is clicked", async () => { enableDemoMode(); const originalLocation = window.location; - // @ts-expect-error -- overriding window.location for the test delete (window as any).location; - // @ts-expect-error -- partial Location stub is enough for this assertion - window.location = { href: "" }; + (window as any).location = { href: "" }; const user = userEvent.setup(); render(); @@ -47,6 +45,6 @@ describe("DemoBanner", () => { expect(isDemoMode()).toBe(false); expect(window.location.href).toBe("/"); - window.location = originalLocation; + (window as any).location = originalLocation; }); }); diff --git a/apps/frontend/components/escrows/CancelGraceBanner.test.tsx b/apps/frontend/components/escrows/CancelGraceBanner.test.tsx index 4e309d27..d7768fc8 100644 --- a/apps/frontend/components/escrows/CancelGraceBanner.test.tsx +++ b/apps/frontend/components/escrows/CancelGraceBanner.test.tsx @@ -1,6 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; +import { render, screen, fireEvent } from "@testing-library/react"; import { NextIntlClientProvider } from "next-intl"; import type { CancellationGrace } from "@delegolabs/types"; import { CancelGraceBanner } from "./CancelGraceBanner"; @@ -61,13 +60,12 @@ describe("CancelGraceBanner", () => { expect(screen.getByRole("button", { name: "Undo" })).toBeInTheDocument(); }); - it("clicking Undo clears the banner immediately (optimistic)", async () => { + it("clicking Undo clears the banner immediately (optimistic)", () => { let resolveUndo: (v: unknown) => void = () => {}; mockUndo.mockReturnValue(new Promise((resolve) => (resolveUndo = resolve))); - const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); renderBanner(makeGrace()); - await user.click(screen.getByRole("button", { name: "Undo" })); + fireEvent.click(screen.getByRole("button", { name: "Undo" })); expect(screen.queryByText("Cancelling…")).toBeNull(); diff --git a/apps/frontend/components/escrows/DisputeStatusPanel.tsx b/apps/frontend/components/escrows/DisputeStatusPanel.tsx index 02363b83..2b63c0b5 100644 --- a/apps/frontend/components/escrows/DisputeStatusPanel.tsx +++ b/apps/frontend/components/escrows/DisputeStatusPanel.tsx @@ -78,7 +78,7 @@ export function DisputeStatusPanel({ escrow, dispute, optimistic }: DisputeStatu
Evidence