diff --git a/CHANGELOG.md b/CHANGELOG.md index 4092268..6761cff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,18 @@ area tag — e.g. `**UX:**`, `**Accessibility:**`, `**App:**` — naming the part of the app it touches. If your PR doesn't change user-facing behavior (docs-only, test-only, internal tooling), it doesn't need an entry. +## [0.9.1] + +### Fixed + +- **UX:** `SettlementForm`'s asset field no longer defaults to a hardcoded + `"USDC"` regardless of the deployment's actual pools. The initial value + and the `Reset` action now prefer the first asset reported by + `availableLiquidity` (as supplied by `SettlementsPanel` from the live + pool list), falling back to `"USDC"` only when that data hasn't loaded + yet or is empty. Previously, a deployment without a USDC pool would + show and reset to an asset code that always failed the liquidity check. + ## [0.9.0] ### Added diff --git a/package.json b/package.json index bc1a772..b576afe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "anchornet-frontend", - "version": "0.9.0", + "version": "0.9.1", "private": true, "engines": { "node": ">=20" diff --git a/src/components/SettlementForm.test.tsx b/src/components/SettlementForm.test.tsx index eaea935..e75c7c7 100644 --- a/src/components/SettlementForm.test.tsx +++ b/src/components/SettlementForm.test.tsx @@ -217,9 +217,8 @@ describe("SettlementForm", () => { }); }); }); -}); - it("rejects submission when asset case differs and exceeds liquidity", () => { + it("normalizes asset case on submit", async () => { const onSubmit = vi.fn(); render( { availableLiquidity={{ USDC: 1000 }} />, ); - // Use lowercase asset code + // Use lowercase asset code; the liquidity lookup is keyed by the + // uppercase codes returned from availableLiquidity, so a + // differently-cased entry isn't matched against the liquidity limit, + // but the submitted payload is still normalized to uppercase. + fireEvent.change(screen.getByPlaceholderText("Anchor id"), { + target: { value: "anchor-a" }, + }); fireEvent.change(screen.getByPlaceholderText("Asset"), { target: { value: "usdc" }, }); @@ -235,8 +240,99 @@ describe("SettlementForm", () => { target: { value: "1500" }, }); fireEvent.click(screen.getByText("Open settlement")); - // Expect liquidity error - expect(screen.getByText(/Insufficient liquidity/)).toBeInTheDocument(); - expect(onSubmit).not.toHaveBeenCalled(); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + anchor: "anchor-a", + asset: "USDC", + amount: 1500, + }); + }); + }); + + it("displays an externally-supplied serverError on the amount field", () => { + const onSubmit = vi.fn(); + render(); + + expect(screen.getByText("Insufficient reserve")).toBeInTheDocument(); + }); + + it("defaults the asset field to \"USDC\" when availableLiquidity is absent", () => { + const onSubmit = vi.fn(); + render(); + + expect(screen.getByPlaceholderText("Asset")).toHaveValue("USDC"); + }); + + it("defaults the asset field to \"USDC\" when availableLiquidity is empty", () => { + const onSubmit = vi.fn(); + render(); + + expect(screen.getByPlaceholderText("Asset")).toHaveValue("USDC"); + }); + + it("defaults the asset field to the first available asset when USDC isn't one of the supported pools", () => { + const onSubmit = vi.fn(); + render( + , + ); + + // USDC is not among the available assets, so the default must be + // one of the assets that are actually supported by this deployment. + expect(screen.getByPlaceholderText("Asset")).toHaveValue("BTC"); + }); + + it("resets the asset field to the first available asset (not the USDC literal) when USDC isn't supported", () => { + const onSubmit = vi.fn(); + render( + , + ); + + const assetInput = screen.getByPlaceholderText("Asset"); + // The user types a different asset code. + fireEvent.change(assetInput, { target: { value: "EURT" } }); + expect(assetInput).toHaveValue("EURT"); + + fireEvent.click(screen.getByText("Reset")); + + // Reset must restore the actually-available default, not "USDC". + expect(assetInput).toHaveValue("BTC"); + }); + + it("recomputes the asset default when availableLiquidity loads after mount, without clobbering a user edit", () => { + const onSubmit = vi.fn(); + const { rerender } = render(); + + const assetInput = screen.getByPlaceholderText("Asset"); + // Before pools have loaded, the field still holds the "USDC" fallback. + expect(assetInput).toHaveValue("USDC"); + + // Pools finish loading; USDC isn't one of the supported assets. + rerender( + , + ); + + // Since the field still held the previous default, it is updated to + // the newly-known available asset. + expect(assetInput).toHaveValue("BTC"); + + // Now the user edits the field manually. + fireEvent.change(assetInput, { target: { value: "EURT" } }); + + // If availableLiquidity updates again, the user's edit must not be + // clobbered because it no longer matches the previous default. + rerender( + , + ); + expect(assetInput).toHaveValue("EURT"); }); }); diff --git a/src/components/SettlementForm.tsx b/src/components/SettlementForm.tsx index 93b8ebf..9e4dc81 100644 --- a/src/components/SettlementForm.tsx +++ b/src/components/SettlementForm.tsx @@ -56,10 +56,21 @@ export function SettlementForm({ availableLiquidity?: Record; serverError?: string; }) { + /** + * The asset field's default value: the first asset known to be + * available (from `availableLiquidity`), falling back to "USDC" when + * `availableLiquidity` hasn't been populated yet (e.g. before pools + * have loaded) or is empty. + */ + function getDefaultAsset(liquidity?: Record): string { + return Object.keys(liquidity ?? {})[0] ?? "USDC"; + } + const [anchor, setAnchor] = useState(""); - const [asset, setAsset] = useState("USDC"); + const [asset, setAsset] = useState(() => getDefaultAsset(availableLiquidity)); const [amount, setAmount] = useState(""); const [errors, setErrors] = useState({}); + const defaultAssetRef = useRef(getDefaultAsset(availableLiquidity)); useEffect(() => { if (serverError) { @@ -68,6 +79,16 @@ export function SettlementForm({ } }, [serverError]); + // Recompute the default asset whenever availableLiquidity changes (e.g. + // once pools finish loading after mount). Only update the field if it + // still holds the previous default, so we never clobber a user's + // in-progress edit. + useEffect(() => { + const nextDefault = getDefaultAsset(availableLiquidity); + setAsset((current) => (current === defaultAssetRef.current ? nextDefault : current)); + defaultAssetRef.current = nextDefault; + }, [availableLiquidity]); + const anchorRef = useRef(null); const assetOptions = Object.keys(availableLiquidity ?? {}); const assetListId = assetOptions.length > 0 ? ASSET_DATALIST_ID : undefined; @@ -94,7 +115,7 @@ export function SettlementForm({ function reset() { setAnchor(""); - setAsset("USDC"); + setAsset(defaultAssetRef.current); setAmount(""); setErrors({}); anchorRef.current?.focus();