diff --git a/js/ui/profileModal/ProfileWalletController.js b/js/ui/profileModal/ProfileWalletController.js index 018f109e..db663aee 100644 --- a/js/ui/profileModal/ProfileWalletController.js +++ b/js/ui/profileModal/ProfileWalletController.js @@ -764,7 +764,11 @@ export class ProfileWalletController { this.mainController.showError(detail); } finally { this.setWalletPaneBusy(false); - this.refreshWalletPaneState(); + // Do NOT refreshWalletPaneState() here: it re-renders the form from SAVED + // settings and would wipe a URI the user typed but hasn't saved yet (then a + // subsequent Save sees an empty field and "removes" the wallet). Testing + // must preserve the entered value — only refresh the button state. + this.applyWalletControlState(); if (finalStatus) { this.updateWalletStatus(finalStatus, finalVariant); } diff --git a/tests/wallet-test-preserves-input.test.mjs b/tests/wallet-test-preserves-input.test.mjs new file mode 100644 index 00000000..ef196f5b --- /dev/null +++ b/tests/wallet-test-preserves-input.test.mjs @@ -0,0 +1,56 @@ +// Regression: testing the wallet connection must NOT wipe a URI the user typed +// but hasn't saved yet. handleWalletTest used to refreshWalletPaneState() in its +// finally, which re-renders the form from SAVED settings (empty for an unsaved +// URI) and cleared the field — so a subsequent Save saw an empty field and +// "removed" the wallet. + +import "./test-helpers/setup-localstorage.mjs"; +import assert from "node:assert/strict"; +import test from "node:test"; +import { ProfileWalletController } from "../js/ui/profileModal/ProfileWalletController.js"; + +const PUBKEY = "a".repeat(64); +const TYPED_URI = "nostr+walletconnect://abc?relay=wss://r.example&secret=def"; + +// isSecretInputElement accepts any object with a string `value`, so a plain +// object is a faithful stand-in for the input (no DOM needed). +function fakeInput(value = "", dataset = {}) { + return { value, dataset }; +} + +test("testing an unsaved typed URI preserves it (does not wipe the field)", async () => { + const controller = new ProfileWalletController({ + normalizeHexPubkey: (v) => (typeof v === "string" ? v : ""), + getActivePubkey: () => PUBKEY, + showSuccess: () => {}, + showStatus: () => {}, + showError: () => {}, + callbacks: { onWalletTest: () => {} }, + services: { + nwcSettings: { + // The wallet has NOT been saved yet — saved settings are empty. + getActiveNwcSettings: () => ({ nwcUri: "" }), + createDefaultNwcSettings: () => ({ nwcUri: "" }), + }, + }, + }); + + // Simulate the user having typed (unmasked) a URI into the field. + controller.walletUriInput = fakeInput(TYPED_URI, { secretValue: TYPED_URI, secretMasked: "false" }); + controller.walletDefaultZapInput = fakeInput("", {}); + + // Stub the network/test + busy state; let the real test() finally run. + controller.isWalletBusy = () => false; + controller.testWalletConnection = async () => ({ ok: true }); + controller.setWalletPaneBusy = () => {}; + controller.updateWalletStatus = () => {}; + controller.applyWalletControlState = () => {}; // not under test + + await controller.handleWalletTest(); + + assert.equal( + controller.getSecretInputValue(controller.walletUriInput), + TYPED_URI, + "the typed URI must survive a Test so the next Save persists it (not 'remove')", + ); +});