From dfde463c885f6e746e466d2df3727fb37f922894 Mon Sep 17 00:00:00 2001 From: r4bbit <445106+0x-r4bbit@users.noreply.github.com> Date: Sat, 8 Aug 2026 21:09:17 +0200 Subject: [PATCH] test(apps/amm): select the funding account before submitting a swap canSubmit now requires a chosen holding on both swap sides, so the swap e2e test must select them first. The selector auto-selects a single holding, but pick it explicitly (robust to multi-account wallets) via a new selectAccount step that waits for the holdings to load and selects the first match. Expose each side's selector via selectorObjectName (swapSell/BuyAccountSelector) and surface sellHolding/buyHolding in the failure diagnostics. --- apps/amm/qml/components/swap/SwapCard.qml | 2 ++ apps/amm/qml/components/swap/TokenInput.qml | 3 ++ apps/amm/tests/swap.mjs | 38 +++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/apps/amm/qml/components/swap/SwapCard.qml b/apps/amm/qml/components/swap/SwapCard.qml index 597a209..eaf9214 100644 --- a/apps/amm/qml/components/swap/SwapCard.qml +++ b/apps/amm/qml/components/swap/SwapCard.qml @@ -533,6 +533,7 @@ Rectangle { label: "Sell" inputObjectName: "swapSellInput" buttonObjectName: "swapSellTokenButton" + selectorObjectName: "swapSellAccountSelector" amount: root.sellDisplay token: root.sellToken holdings: root.holdings @@ -595,6 +596,7 @@ Rectangle { label: "Buy" inputObjectName: "swapBuyInput" buttonObjectName: "swapBuyTokenButton" + selectorObjectName: "swapBuyAccountSelector" amount: root.buyDisplay token: root.buyToken holdings: root.holdings diff --git a/apps/amm/qml/components/swap/TokenInput.qml b/apps/amm/qml/components/swap/TokenInput.qml index 92cd55e..bb4ecdf 100644 --- a/apps/amm/qml/components/swap/TokenInput.qml +++ b/apps/amm/qml/components/swap/TokenInput.qml @@ -28,6 +28,9 @@ Rectangle { // objectName forwarded to the token-select button, so tests can open the // right picker by objectId rather than fuzzy text. property alias buttonObjectName: tokenButton.objectName + // objectName forwarded to the account selector, so tests can pick the funding + // holding for this side deterministically. + property alias selectorObjectName: accountSelector.objectName signal tokenClicked() signal inputEdited(string newValue) diff --git a/apps/amm/tests/swap.mjs b/apps/amm/tests/swap.mjs index 05fef92..ab9180c 100644 --- a/apps/amm/tests/swap.mjs +++ b/apps/amm/tests/swap.mjs @@ -84,6 +84,38 @@ async function pickToken(app, index) { return symbol; } +// Pick the funding account for a swap side. The selector auto-selects when the +// wallet holds the token in exactly one account, but make the choice explicit (and robust +// to multi-account wallets with zero-balance holdings): wait for the holdings to populate, +// then select the highest-balance match. canSubmit now requires BOTH sides' holdings to be +// set, so a swap can't be submitted until this runs. +async function selectAccount(app, selectorObjectName) { + const id = await idByObjectName(app, selectorObjectName); + await app.waitFor( + async () => { if ((await prop(app, id, "hasFunds")) !== true) throw new Error("no matching holdings yet"); }, + { timeout: 10000, interval: 300, description: `${selectorObjectName} holdings to load` }, + ); + // setSelection/accountIdFor/matchingAccounts/valueFor are members of the selector, so + // evaluate in its own QML context (same pragmatic style as setSellAmount). tokenHoldings can + // include zero-balance holdings for the token, and hasFunds only checks the match COUNT — so + // pick the matching account with the largest balanceRaw rather than matchingAccounts[0], + // which could be an empty holding the swap transfer can't debit. balanceRaw values are + // non-negative base-unit integer strings, so "longer wins, else lexicographic" is an exact + // max without needing BigInt. + await app.inspector.send("evaluate", { + expression: + "(function(){var r=matchingAccounts,b=r[0],bb=String(valueFor(b,'balanceRaw')||'0');" + + "for(var i=1;ibb.length||(v.length===bb.length&&v>bb)){b=r[i];bb=v;}}" + + "setSelection(accountIdFor(b),false);})()", + objectId: id, + }); + await app.waitFor( + async () => { if (!(await prop(app, id, "selectedAccountId"))) throw new Error("holding not selected yet"); }, + { timeout: 5000, interval: 200, description: `${selectorObjectName} holding selected` }, + ); +} + // Enter the sell amount by setting the SwapCard's state directly. Synthesizing // keystrokes needs the TextInput to hold active focus, which the inspector // can't reliably grant headlessly; setting sellInput updates the property (and @@ -118,6 +150,8 @@ async function cardState(app) { swapError: get("swapError"), canSubmit: get("canSubmit"), submitButtonText: get("submitButtonText"), + sellHolding: get("sellHolding"), + buyHolding: get("buyHolding"), }; } @@ -161,6 +195,10 @@ test("amm swap: sell token #1 for token #2", async (app) => { const second = await pickToken(app, 1); console.log(` sell ${first} -> buy ${second}`); + // 4. Pick the funding account for each side (canSubmit needs both selected). + await selectAccount(app, "swapSellAccountSelector"); + await selectAccount(app, "swapBuyAccountSelector"); + // 5. Enter the sell amount. await setSellAmount(app, SELL_AMOUNT); await app.expectTexts([SELL_AMOUNT]); // the amount should now be visible