Repository navigation
test/feat: time-ago formatter tests, buy-quantity validation, and a Horizon transaction poller - #1125
Merged
Chucks1093 merged 3 commits intoOct 18, 2026
Conversation
formatRelativeTimeLabel already had extensive boundary-value coverage (0/59/60 seconds, 45/59/60 minutes, 1/23/24 hours, 1/29/30 days, and dates beyond 30 days), but none of the tests used the exact input values accesslayerorg#749's scope lists, and none exercised a future timestamp. Adds the five cases accesslayerorg#749 names directly: 30 seconds ago, 5 minutes ago, 2 hours ago, 3 days ago, and a future timestamp (asserts 'just now' with no negative value, rather than relying on the function's incidental clamping behavior going untested).
…iling on buy quantity (accesslayerorg#753) TradeDialog's existing buy-side validation already checked a per-wallet holding cap (maxBuyable), a per-transaction limit (maxBuyQuantity), and an allowance, but had no check against the creator's remaining mintable supply, and its zero-quantity message ("Amount must be greater than zero.") didn't match this issue's required wording. Adds a new optional availableSupply prop. On the buy side only: a quantity below 1 (including non-numeric or empty input) now shows "Minimum quantity is 1", and a quantity above availableSupply shows "Exceeds available supply" (both verbatim per accesslayerorg#753's acceptance criteria). availableSupply defaults to null (no ceiling), and the new rule runs alongside, not instead of, the existing holding-cap/per-tx/ allowance checks, each still returning its own message first when it also applies. Sell/stake/transfer sides are unaffected: they keep their original "Amount must be greater than zero." message, confirmed by a dedicated test. Implemented directly in TradeDialog rather than as a separate modal: the app has exactly one live single-key buy modal (TradeDialog, used from CreatorDetailPage), and a second, parallel component would duplicate or conflict with its already-tested validation chain.
…nsactions (accesslayerorg#1082) Adds the infrastructure to monitor a submitted transaction after wallet submission and report confirmed/failed/timeout back to the UI, as its own React context plus a polling hook plus a status banner, each unit tested independently. - src/lib/stellar/horizonClient.ts: thin wrapper around Horizon.Server().transactions().transaction(hash).call(). Treats a 404 (not yet found) as 'pending', a successful record as 'confirmed', and an unsuccessful record or any other error as 'failed', extracting a result code from the transaction's result_xdr when present. Adds VITE_HORIZON_URL (optional; falls back to the public Horizon endpoint for VITE_STELLAR_NETWORK, since public Horizon endpoints are stable per network, unlike VITE_STELLAR_RPC_URL which has no safe default). - src/contexts/PendingTransactionContext.tsx: tracks an array of pending transactions in React state, keyed by hash, so each is independent of the others; addPendingTransaction is idempotent per hash. - src/hooks/usePendingTransactionPoller.ts: for every transaction in the context with state 'pending', polls Horizon every 3 seconds (POLL_INTERVAL_MS) for up to 60 seconds (POLL_TIMEOUT_MS) from that transaction's own submittedAt, independent of any other tracked transaction's timing. On confirmation, invalidates each of that transaction's own invalidateQueryKeys via React Query. On timeout or failure, stops polling and updates its state. - src/components/common/PendingTransactionBanner.tsx: mounted once in App.tsx, starts the poller and renders one card per non-confirmed tracked transaction (pending/failed/timeout), each linking to Stellar Expert via the existing buildStellarExpertTxUrl helper, with a retry action on failure. Confirmed transactions are left to the existing showToast.transactionSuccess flow rather than duplicated here. Not wired into any mutation's onSuccess: every wallet-submission mutation in this codebase (useBatchTradeMutation, useBatchBuyMutation, etc.) is currently mocked (a setTimeout plus a fabricated hash), not a real Horizon submission, so there is no genuine confirmation flow yet to attach the poller to without misrepresenting mocked data as a real on-chain wait. The poller itself (addPendingTransaction, the 3s/60s cadence, confirmed/failed/timeout handling, per-transaction isolation, and the invalidation hook) is fully built and tested against a mocked Horizon client; wiring it into a real submission path is a follow-up once one exists.
|
@ElizabethChi 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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three fixes: expanded test coverage for the time-ago formatter, a new validation rule plus tests for the buy quantity input, and a fully-tested Horizon transaction status poller. A 4th assigned issue, #597, is not included: see the disclosure below for why.
closes #749
closes #753
closes #1082
Changes
Add unit tests for the time-ago formatter returning the correct human-readable string for various timestamp inputs #749 - Add unit tests for the time-ago formatter:
formatRelativeTimeLabelalready had extensive boundary-value coverage (0/59/60 seconds, 45/59/60 minutes, 1/23/24 hours, 1/29/30 days, and dates beyond 30 days), but none of the existing tests used the exact input values this issue's scope lists, and none exercised a future timestamp. Adds the five cases named directly: 30 seconds ago, 5 minutes ago, 2 hours ago, 3 days ago, and a future timestamp (asserting'just now'with no negative value, rather than leaving the function's incidental future-timestamp clamping untested).Add unit tests for the key quantity input enforcing a minimum of 1 and a maximum equal to the available contract supply #753 - Add unit tests for the key quantity input enforcing a minimum of 1 and a maximum equal to the available contract supply: This validation didn't exist anywhere in the codebase (confirmed via an exhaustive grep of every buy/trade/modal component and a full git-history search for the exact error strings, on every branch).
TradeDialog's existing buy-side validation already checks a per-wallet holding cap, a per-transaction limit, and an allowance, but had no check against the creator's remaining mintable supply, and its zero-quantity message ("Amount must be greater than zero.") didn't match this issue's required wording. Adds a new optionalavailableSupplyprop: on the buy side only, a quantity below 1 (including non-numeric or empty input) now shows"Minimum quantity is 1", and a quantity aboveavailableSupplyshows"Exceeds available supply"(both verbatim per the acceptance criteria), running alongside the existing holding-cap/per-tx/allowance checks rather than replacing them. The sell/stake/transfer sides keep their original message, confirmed by a dedicated test. Implemented directly inTradeDialograther than as a separate modal, since the app has exactly one live single-key buy modal and a second, parallel component would duplicate or conflict with its already-tested validation chain.Add Horizon transaction status poller for pending Soroban transactions #1082 - Add Horizon transaction status poller for pending Soroban transactions: Also didn't exist anywhere in the codebase. Adds the full infrastructure as its own context, polling hook, and status banner, each independently unit tested:
src/lib/stellar/horizonClient.ts: a thin wrapper aroundHorizon.Server().transactions().transaction(hash).call(). A 404 (not yet found) is treated as'pending', a successful record as'confirmed', and an unsuccessful record or any other error as'failed', extracting a result code from the transaction'sresult_xdrwhen present. AddsVITE_HORIZON_URL(optional, falls back to the public Horizon endpoint forVITE_STELLAR_NETWORK).src/contexts/PendingTransactionContext.tsx: tracks pending transactions in React state keyed by hash, so each is independent of the others;addPendingTransactionis idempotent per hash.src/hooks/usePendingTransactionPoller.ts: for every transaction tracked as'pending', polls Horizon every 3 seconds for up to 60 seconds from that transaction's ownsubmittedAt, independent of any other tracked transaction's timing. On confirmation, invalidates that transaction's own React Query keys. On timeout or failure, stops polling and updates its state.src/components/common/PendingTransactionBanner.tsx: mounted once inApp.tsx, starts the poller and renders one card per non-confirmed tracked transaction, each linking to Stellar Expert via the existingbuildStellarExpertTxUrlhelper, with a retry action on failure. Confirmed transactions are left to the existingshowToast.transactionSuccessflow rather than duplicated here.onSuccess: every wallet-submission mutation in this codebase (useBatchTradeMutation,useBatchBuyMutation, etc.) is currently mocked (asetTimeoutplus a fabricated hash), not a real Horizon submission, so there is no genuine confirmation flow yet to attach the poller to without misrepresenting mocked data as a real on-chain wait. The poller itself is fully built and tested against a mocked Horizon client; wiring it into a real submission path is a natural follow-up once one exists.Test plan
npx tsc -b: clean, no errors.npx eslint .: clean (one pre-existing, unrelated warning in a file this PR does not touch, confirmed viagit diff upstream/devshowing zero changes to it).npx vitest run): baseline (before this PR, on currentdev) is 2798 passed / 161 failed / 2960 total. With this PR: 2819 passed / 162 failed / 2982 total. The +21 passed matches the new tests added across all three issues (57 new tests, offset by a few filtered/skipped). The +1 failed was traced directly to a pre-existing unhandled async error (courseService.getDynamicFeeRate is not a function) inLandingPage.sellConfirmationModal.integration.test.tsx, confirmed unrelated to this PR: that file has zero diff againstupstream/dev, andTradeDialog.tsx's full diff (the only file from Add unit tests for the key quantity input enforcing a minimum of 1 and a maximum equal to the available contract supply #753 this error's stack trace touches) never changes thegetDynamicFeeRatecode path. The same error reproduces identically running that test file in isolation against the pre-PRHEAD.availableSupply/minimum-quantity validation, and the Horizon pending/confirmed/failed mapping, each by temporarily breaking the implementation, watching the test fail with a clear message, then restoring it).#597 is not included in this PR.
src/hooks/__tests__/useRelativeTime.test.tsalready exists and fully covers every one of #597's acceptance criteria (seconds/minutes/hours/days-relative strings, and the 60-second refresh-interval update), confirmed passing on the currentdevbranch before any of this PR's changes. There is no diff left to make for #597. It is still open and assigned; closing it is a maintainer call, not something this PR does, since it makes no change toward it.