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.
This commit is contained in:
r4bbit
2026-08-11 11:14:49 +02:00
parent 471846dc01
commit dfde463c88
3 changed files with 43 additions and 0 deletions
@@ -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
@@ -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)
+38
View File
@@ -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;i<r.length;++i){var v=String(valueFor(r[i],'balanceRaw')||'0');"
+ "if(v.length>bb.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