diff --git a/src/popup/views/settings.js b/src/popup/views/settings.js index f1cfa9f..4b52632 100644 --- a/src/popup/views/settings.js +++ b/src/popup/views/settings.js @@ -334,6 +334,11 @@ function showNewSettings() { // src/popup/index.js) between setting the URL and writing it. If the active // network is no longer `networkId`, on this page or in the stored record, // nothing is saved. The flash message then says that the network changed. +// +// The save writes only the fields that differ from what this page last saved. +// Another page may have stored a different URL since then. So `url` is set in +// the stored record too. "Saved." then means that the stored record holds +// `url`. async function saveEndpoint(field, url, networkId) { let refused = false; await saveState((stored) => { @@ -341,6 +346,7 @@ async function saveEndpoint(field, url, networkId) { state.networkId !== networkId || stored.networkId !== networkId; if (refused) return false; state[field] = url; + stored[field] = url; }); if (refused) { showFlash("The URL was not saved because the network changed."); diff --git a/tests/settingsEndpointSave.test.js b/tests/settingsEndpointSave.test.js new file mode 100644 index 0000000..066a06f --- /dev/null +++ b/tests/settingsEndpointSave.test.js @@ -0,0 +1,49 @@ +// Another page stores a different RPC or Blockscout URL for the same network. +// Then the user taps "Save" in Settings on this page, on the URL that this page +// loaded. The URL on screen is stored, and the flash message reads "Saved." +// (https://git.eeqj.de/sneak/AutistMask/issues/688). Driven through the real +// popup over a stored profile on Ethereum Mainnet. + +const { + bootPopup, + cleanupPopup, + unversionedValidProfile, +} = require("./support/popupBoot"); +const { NETWORKS } = require("../src/shared/networks"); + +afterEach(() => { + cleanupPopup(); +}); + +test.each([ + { button: "btn-save-rpc", input: "settings-rpc", field: "rpcUrl" }, + { + button: "btn-save-blockscout", + input: "settings-blockscout", + field: "blockscoutUrl", + }, +])( + "$button stores the URL on screen after another page stored a different one", + async ({ button, input, field }) => { + const page = await bootPopup(unversionedValidProfile()); + await page.click("btn-settings"); + const url = page.value(input); + // This write sends this page no storage change event. + page.storage.write("autistmask", { + ...page.storage.read("autistmask"), + [field]: "https://other-page.example.invalid", + }); + // This answer passes both endpoint checks on Ethereum Mainnet. The RPC + // check reads Ethereum Mainnet's chain id from it. The Blockscout check + // reads `ok`. + globalThis.fetch.mockResolvedValue({ + ok: true, + json: async () => ({ result: NETWORKS.mainnet.chainId }), + }); + + await page.click(button); + + expect(page.storage.read("autistmask")[field]).toBe(url); + expect(page.text("flash-msg")).toBe("Saved."); + }, +);