From a2b32fc82135bce412e16466d7afbbf6a9c23b0e Mon Sep 17 00:00:00 2001 From: adeolu Date: Fri, 24 Jul 2026 14:20:37 +0100 Subject: [PATCH] @ test(screens): cover AccountScreen refresh state and ConnectScreen wallet options Add the AccountScreen refresh loading/last-updated behaviour and the ConnectScreen supported-wallet and onboarding UI that the coverage in #199 depends on, then test both. AccountScreen: - track a last-updated timestamp that is set once refreshAccount resolves - test the refresh button disabled/aria-busy state while isLoadingAccount - test that the timestamp only appears after a refresh completes ConnectScreen: - render supported wallet options with accessible logos - add a collapsible "New to Stellar?" onboarding section - hide the hero image on small viewports (hidden sm:block) - test wallet options, collapsible expand, and the responsive hero class @ --- src/screens/AccountScreen.test.tsx | 95 ++++++++++++++++++++++++++++-- src/screens/AccountScreen.tsx | 16 ++++- src/screens/ConnectScreen.test.tsx | 47 +++++++++++++++ src/screens/ConnectScreen.tsx | 60 ++++++++++++++++++- 4 files changed, 207 insertions(+), 11 deletions(-) diff --git a/src/screens/AccountScreen.test.tsx b/src/screens/AccountScreen.test.tsx index 5652dce..899a38f 100644 --- a/src/screens/AccountScreen.test.tsx +++ b/src/screens/AccountScreen.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { beforeEach,describe, expect, it, vi } from "vitest"; import { useSorokit } from "@/context/useSorokit"; @@ -21,14 +21,21 @@ vi.mock("@/components/ClaimableBalanceCard", () => ({ ClaimableBalanceCard: () =>
Claimable Balances
, })); +type Ctx = ReturnType; + +function mockContext(overrides: Partial = {}) { + vi.mocked(useSorokit).mockReturnValue({ + isConnected: false, + isLoadingAccount: false, + refreshAccount: vi.fn(), + ...overrides, + } as unknown as Ctx); +} + describe("AccountScreen", () => { beforeEach(() => { vi.clearAllMocks(); - vi.mocked(useSorokit).mockReturnValue({ - isConnected: false, - isLoadingAccount: false, - refreshAccount: vi.fn(), - } as unknown as ReturnType); + mockContext(); }); it("renders the screen heading as a level 2 heading", () => { @@ -38,4 +45,80 @@ describe("AccountScreen", () => { ).toBeInTheDocument(); expect(screen.getByText("Balances and account details")).toBeInTheDocument(); }); + + describe("refresh control (#81)", () => { + it("does not render the refresh button when disconnected", () => { + mockContext({ isConnected: false }); + render(); + expect( + screen.queryByRole("button", { name: /refresh account data/i }), + ).not.toBeInTheDocument(); + }); + + it("renders the refresh button when connected", () => { + mockContext({ isConnected: true }); + render(); + expect( + screen.getByRole("button", { name: /refresh account data/i }), + ).toBeInTheDocument(); + }); + + it("disables the refresh button while isLoadingAccount is true", () => { + mockContext({ isConnected: true, isLoadingAccount: true }); + render(); + const button = screen.getByRole("button", { + name: /refresh account data/i, + }); + expect(button).toBeDisabled(); + expect(button).toHaveAttribute("aria-busy", "true"); + }); + + it("keeps the refresh button enabled when not loading", () => { + mockContext({ isConnected: true, isLoadingAccount: false }); + render(); + expect( + screen.getByRole("button", { name: /refresh account data/i }), + ).toBeEnabled(); + }); + + it("calls refreshAccount when the refresh button is clicked", async () => { + const refreshAccount = vi.fn().mockResolvedValue(undefined); + mockContext({ isConnected: true, refreshAccount }); + render(); + + fireEvent.click( + screen.getByRole("button", { name: /refresh account data/i }), + ); + + expect(refreshAccount).toHaveBeenCalledTimes(1); + // Let the post-refresh state update settle to avoid act() warnings. + await waitFor(() => + expect(screen.getByText(/last updated/i)).toBeInTheDocument(), + ); + }); + }); + + describe("last updated timestamp (#81)", () => { + it("does not show a last-updated timestamp before any refresh", () => { + mockContext({ isConnected: true }); + render(); + expect(screen.queryByText(/last updated/i)).not.toBeInTheDocument(); + }); + + it("shows the last-updated timestamp after refreshAccount resolves", async () => { + const refreshAccount = vi.fn().mockResolvedValue(undefined); + mockContext({ isConnected: true, refreshAccount }); + render(); + + expect(screen.queryByText(/last updated/i)).not.toBeInTheDocument(); + + fireEvent.click( + screen.getByRole("button", { name: /refresh account data/i }), + ); + + await waitFor(() => + expect(screen.getByText(/last updated/i)).toBeInTheDocument(), + ); + }); + }); }); diff --git a/src/screens/AccountScreen.tsx b/src/screens/AccountScreen.tsx index 2e84419..9343ff8 100644 --- a/src/screens/AccountScreen.tsx +++ b/src/screens/AccountScreen.tsx @@ -1,5 +1,6 @@ import { Refresh01Icon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; +import { useState } from "react"; import { AccountCard } from "@/components/AccountCard"; import { BalanceList } from "@/components/BalanceList"; @@ -9,18 +10,29 @@ import { useSorokit } from "@/context/useSorokit"; export function AccountScreen() { const { isConnected, isLoadingAccount, refreshAccount } = useSorokit(); + const [lastUpdated, setLastUpdated] = useState(null); + + async function handleRefresh() { + await refreshAccount(); + setLastUpdated(new Date()); + } return (

Account

Balances and account details

{isConnected && ( -
+
+ {lastUpdated && ( + + Last updated {lastUpdated.toLocaleTimeString()} + + )}
- {/* Hero image */} + {/* Hero image — hidden on short/mobile viewports to keep the card in view */} sorokit wallet dashboard preview {/* Card */} @@ -88,10 +91,61 @@ export function ConnectScreen() { Connecting to your wallet…

)} + {/* Supported wallet options */} +
+

+ Supported wallets +

+
    + {SUPPORTED_WALLETS.map((name) => ( +
  • + + + + {name} +
  • + ))} +
+
+

Powered by sorokit-core · Stellar network

+ + {/* New to Stellar? — collapsible onboarding help */} +
+ + New to Stellar? + +
+

+ A Stellar wallet lets you hold assets and sign transactions. + Install one of the supported wallets above, create an account, + then come back and connect. +

+ + Learn how Stellar accounts work → + +
+