Skip to content

feat: shared testing package, transaction toasts, and wallet re-authorization - #263

Open
Dawnaurora25 wants to merge 2 commits into
SorobanForge:mainfrom
Dawnaurora25:feat/testing-package-toasts-wallet-reauth
Open

Dawnaurora25 wants to merge 2 commits into
SorobanForge:mainfrom
Dawnaurora25:feat/testing-package-toasts-wallet-reauth

Conversation

@Dawnaurora25

Copy link
Copy Markdown

Summary

Closes #150, Closes #155, Closes #129, Closes #123.

Four issues, one coherent change: the workspace gains a real, private test-doubles package, the dashboard gains a shared notification surface, and the wallet layer stops trusting localStorage for auto-reconnect. They are shipped together because they interlock — #155's cleanup is what turns the packages/testing placeholder into the package #150 asks for, and both the SDK suite and the wallet tests consume the new package's conventions.

Issue What shipped
#150 packages/testing is a real workspace package: FakeTransport, Stream/Escrow/Split fixtures, createTestClient, README with a worked example, and the SDK suite as its first consumer.
#155 Only packages that exist are documented and referenced; the stale @stellar-starter-kit/core path mapping is gone; the lockfile accounts for the workspace graph.
#129 ToastProvider + useToast in packages/ui, used by the streams page to report create/withdraw/cancel and their transaction hashes.
#123 The stored wallet session is re-authorized on load; denied/unavailable wallets silently return to disconnected, with a isCheckingWallet state that stops the connect button from flashing.

1. @stellar-starter-kit/testing — a real testing package (Closes #150)

Before: every package hand-rolled its own stubs. The SDK, for example, carried a 190-line packages/sdk/src/test-helpers.ts that patched SorobanRpc.Server.prototype ad hoc. packages/testing existed only as an empty, untracked directory.

After: packages/testing is a proper workspace package (added to pnpm-workspace.yaml's packages/* glob, declared in pnpm-lock.yaml, built and linted by Turborepo like every sibling).

What it exports

  • createFakeTransport(options?) — the in-memory RPC transport. Constructing one swaps the five RPC methods the SDK and the dashboard call (getAccount, simulateTransaction, prepareTransaction, sendTransaction, getTransaction) for canned, in-memory answers: sequence 100, a successful simulation, prepareTransaction passthrough, PENDING/SUCCESS submissions with hash abc123. Every method stays a vi.fn, so a test can still do mockResolvedValueOnce/mockRejectedValueOnce and assert on mock.calls. Helpers: calls() (round trips), reset(), restore(), and respondWith({ get_stream: streamScVal() }) for per-method simulations.
  • Fixtures for Stream / Escrow / Split. streamFixture(), escrowFixture(), splitFixture() return fully-populated domain values with Partial overrides (decimal strings for u64/i128, per the packages/types convention), plus streamScVal() which encodes a Stream exactly as the contract's get_stream returns it, and stable CONTRACT_ID/TOKEN_ID/PUBLIC_KEY/RECIPIENT/ARBITER.
  • createTestClient(factory, options?) — the harness. Installs a transport and builds a client with test-safe defaults (https://rpc.invalid, 1 ms poll interval, identity signer), returning { client, transport, withClient, restore }. withClient(overrides) builds additional clients against the same transport, which is what the existing poll/timeout tests need.
  • Response builders so tests stop re-declaring them: simulationSuccess, simulationError, sendResponse, txResponse, trappedResult, badSequenceResult, contractErrorEvent, invokedCall, simulateByMethod.
  • A test-only package must not depend on @stellar-starter-kit/sdk (the SDK's tests depend on it, which would make the workspace graph circular and break Turborepo's ^build/^test task graph). createTestClient therefore takes the client factory — createTestClient((config) => new StreamsClient(config)) — and TestClientConfig mirrors StreamsClientOptions field for field so it type-checks without a cast. This is documented in both the README and the source.

The SDK is the first consumer

packages/sdk/src/test-helpers.ts is deleted. All five SDK test files now import from @stellar-starter-kit/testing, declaring it as a workspace devDependency:

let harness: TestClientHarness<StreamsClient>;
let rpc: FakeTransport;

const makeClient = (overrides: Partial<StreamsClientOptions> = {}) =>
  harness.withClient(overrides);

beforeEach(() => {
  harness = createTestClient((config) => new StreamsClient(config));
  rpc = harness.transport;
});

packages/sdk/vitest.config.ts resolves the package from source (../testing/src/index.ts), so pnpm test works without building dist first — the test job in CI installs and runs, it does not build. packages/sdk/tsconfig.json drops its stale @stellar-starter-kit/core path mapping (a reference to a package that does not exist — part of #155).

Private and never bundled

"private": true, no apps/web dependency, no import from shipped runtime code. The README states this explicitly and explains the intended usage with one complete example test.


2. ToastProvider + useToast (Closes #129)

packages/ui/src/toast.tsx (exported from the package index, alongside its types):

  • Variants: success, error, info via toast.success(...), toast.error(...), toast.info(...), or the generic toast.show(variant, title, options).
  • Auto-dismiss with a configurable delay: per-toast duration overrides the provider's defaultDuration (5 s); 0 keeps a toast until it is dismissed. The timer pauses on hover, focus, and resumes with the remaining time — it is not restarted.
  • Accessible container in a portal: rendered with createPortal(..., document.body) after hydration, on a role="status" + aria-live="polite" + aria-atomic="false" region, so every toast is announced without stealing focus. Each toast has a labelled dismiss button (aria-label="Dismiss notification") and decorative icons are aria-hidden.
  • Transaction hashes reported consistently: options.hash + options.explorerUrl render a "View transaction" link instead of the ad-hoc HTML string the pages were interpolating into banner text.
  • Overflow: maxToasts (default 4) drops the oldest toast.
  • Motion comes from framer-motion, already a dependency of packages/ui — no new dependencies anywhere in this PR.

Wired into the app

  • apps/web/src/app/layout.tsx wraps the app in <ToastProvider> (inside WalletProvider), so the container exists for every route.
  • apps/web/src/app/streams/page.tsx removes its local error/success state and the AnimatePresence banner, and reports every outcome through toasts: create, withdraw, and cancel all go through runAction/handleCreate → notify(...), and successes go through a single notifyTx(title, hash) helper that formats the tx link once. A stream lookup failure, a rejected signature, a missing contract id, and a failed connect are error toasts.

3. Re-authorize the wallet session on load (Closes #123)

packages/wallets/src/WalletProvider.tsx:

  • Silent access request on load. The single new requestAddress(provider) helper is the only place an address is read from a wallet — it is now shared by connect and by the restore effect, so both paths ask for access the same way (Freighter isConnected + requestAccess, with the getAddress fallback for older builds; Albedo publicKey; Rabet connect; Hana getPublicKey).
  • Connected only when granted. The stored address is used as evidence that the user connected once, not as proof of this session. Until the wallet grants, the provider stays disconnected; on a grant it adopts the address the wallet returned and re-persists it.
  • Denied or unavailable → disconnected, no banner. Every failure (extension gone, request refused, request errored) clears the session and localStorage and never sets error, because the user never asked to connect.
  • isCheckingWallet. A new context field alongside isConnecting, true while the restore request is in flight; the streams page disables the connect button and shows "Checking wallet…" instead of "Wallet not connected", so returning users no longer see the button flash.

Tests (granted, denied, unavailable)

packages/wallets/src/WalletProvider.test.tsx gains five cases, rendering inside act so the async restore is fully flushed before asserting:

  • granted → the address comes from the grant, not localStorage, and localStorage is re-persisted;
  • a pending grant → isCheckingWallet is true and isConnected is false, then flips when access arrives (this is the anti-flash guarantee, asserted directly with a deferred promise);
  • denied → disconnected, error === null, localStorage cleared, no requestAccess call leaked;
  • unavailable (isConnected: false) → disconnected, error === null, requestAccess never called;
  • saved Albedo session rejecting → disconnected, error === null.

The pre-existing "auto-reconnect" and "disconnect" tests were updated to grant access first, since the old behaviour they asserted (showing a stored address before it was granted) is exactly the bug.


4. Workspace + documentation hygiene (Closes #155)

  • packages/cli, packages/core, packages/config have no scaffolded content and no owner: they are not re-created. packages/testing is the one placeholder that earned a real package (see above), so nothing is left dangling.
  • Stale references removed: the @stellar-starter-kit/core path mapping in packages/sdk/tsconfig.json (a mapping to a packages/core/dist that never existed).
  • ARCHITECTURE.md and the README layout tree list only packages that exist, now including testing with a one-line responsibility; the README also documents the testing package and links its README.
  • pnpm-lock.yaml matches package.json files exactly (new packages/testing importer, packages/sdk → link:../testing), so pnpm install --frozen-lockfile in CI stays green.
  • .gitignore already covers every artifact these directories can produce (node_modules, .turbo, dist, *.tsbuildinfo), so nothing regenerates as a spurious change — verified rather than changed.

Files changed

Area Files
New package packages/testing/{package.json,tsconfig.json,vitest.config.ts,README.md}, packages/testing/src/{index,transport,fixtures,client,index.test}.ts
SDK packages/sdk/package.json, tsconfig.json, vitest.config.ts, src/*.test.ts (5 files), deleted src/test-helpers.ts
UI packages/ui/src/toast.tsx (new), packages/ui/src/index.ts
Wallets packages/wallets/src/WalletProvider.tsx, WalletProvider.test.tsx
App apps/web/src/app/layout.tsx, apps/web/src/app/streams/page.tsx
Repo pnpm-lock.yaml, ARCHITECTURE.md, README.md

Verification

  • packages/testing ships its own tests (src/index.test.ts): the transport answers getAccount/simulate/submit/confirm in memory, respondWith routes by contract method, an unregistered method fails loudly, restore() puts the real SorobanRpc.Server methods back, createTestClient applies and overrides defaults on one shared transport, and the fixtures/streamScVal round-trip through scValToNative. packages/testing has a test script precisely so turbo run test exercises it instead of failing on an empty suite.
  • No new runtime dependencies were introduced: packages/testing depends only on @stellar/stellar-sdk, @stellar-starter-kit/types, and vitest; the toast uses framer-motion + lucide-react, which packages/ui already depended on; the app already depended on @stellar-starter-kit/ui.
  • Task-graph check: packages/testing depends on nothing that depends on it, so ^build / ^test stay acyclic for turbo run build|typecheck|test.
  • Static pre-checks run on this branch (without installing dependencies, per the task brief): every changed .ts/.tsx parses with zero syntax diagnostics (TypeScript compiler API), every JSON file parses, pnpm-lock.yaml loads as valid YAML with the new importer present, and every named import the SDK tests take from @stellar-starter-kit/testing and ./index resolves to a real export. The full gate — pnpm lint + format:check, typecheck, test, build, and the Rust contract job — runs on CI.

Gives the workspace one place for test doubles instead of hand-rolled stubs:
`packages/testing` becomes a real, private workspace package exporting a
FakeTransport (in-memory Soroban RPC), Stream/Escrow/Split fixtures, and a
createTestClient harness, with the SDK suite as its first consumer (Closes SorobanForge#150).

Replaces the per-page inline success/error banners with a shared ToastProvider
+ useToast in packages/ui — success/error/info, configurable auto-dismiss that
pauses on hover or focus, and a portal container announced through a polite
live region — and reports stream create/withdraw/cancel through it (Closes SorobanForge#129).

Stops trusting localStorage for auto-reconnect: the stored session is only shown
as connected once the wallet actually grants access, a denied or unavailable
wallet returns to disconnected without an error banner, and a new
isCheckingWallet state keeps connect buttons from flashing while that happens
(Closes SorobanForge#123).

Documents only the packages that exist (README layout, ARCHITECTURE table),
drops the stale `@stellar-starter-kit/core` path mapping, and keeps the lockfile
in sync with the new package (Closes SorobanForge#155).
@drips-wave

drips-wave Bot commented Oct 18, 2026

Copy link
Copy Markdown

@Dawnaurora25 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Oct 18, 2026

Copy link
Copy Markdown

@Dawnaurora25 is attempting to deploy a commit to the techalhaji's projects Team on Vercel.

A member of the Team first needs to authorize it.

@techAlhaji

Copy link
Copy Markdown
Collaborator

resolve conflicts please

The Lint Codebase job runs `prettier --check .`, and six files introduced
by the test-doubles migration did not match the repo's formatter output:
the five SDK suites had the `makeClient` arrow split although it fits on
one line, and `createFakeTransport` wrapped spy chains that fit. Formatting
them is required before the typecheck and test jobs can even be reached.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants