From 3be6f45fe860622ff129ff9ba368f3f159f8dcb6 Mon Sep 17 00:00:00 2001 From: ssmithx Date: Tue, 8 Sep 2026 15:46:12 +0000 Subject: [PATCH] test(ui): guard the ecash-tab-click path in ReceiveBitcoinModal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operator report (2026-09-08): clicking the Ecash tab appeared to close the whole Receive modal. Added a regression test simulating the exact click, both for wallet.ecash-lnaddress succeeding and failing — the tab switch alone never emits `close` or unmounts the dialog in either case, so this isn't reproduced by a plain component-level click; the investigation continues with the reporter for a browser-console repro. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EawZPP9iidXj6Tvg3EpG3a --- .../__tests__/ReceiveBitcoinModal.test.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 neode-ui/src/components/__tests__/ReceiveBitcoinModal.test.ts diff --git a/neode-ui/src/components/__tests__/ReceiveBitcoinModal.test.ts b/neode-ui/src/components/__tests__/ReceiveBitcoinModal.test.ts new file mode 100644 index 00000000..3a9b4986 --- /dev/null +++ b/neode-ui/src/components/__tests__/ReceiveBitcoinModal.test.ts @@ -0,0 +1,73 @@ +import { flushPromises, mount } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' +import ReceiveBitcoinModal from '../ReceiveBitcoinModal.vue' +import { rpcClient } from '@/api/rpc-client' + +vi.mock('vue-router', () => ({ + useRoute: () => ({ fullPath: '/dashboard' }), + useRouter: () => ({ push: vi.fn() }), +})) + +vi.mock('vue-i18n', () => ({ + useI18n: () => ({ t: (key: string, params?: Record) => (params ? `${key}:${JSON.stringify(params)}` : key) }), +})) + +vi.mock('@/api/rpc-client', () => ({ + rpcClient: { call: vi.fn() }, +})) + +vi.mock('@/composables/useLightningRequired', () => ({ + useLightningRequired: () => ({ + requireLightningReady: vi.fn().mockResolvedValue(true), + handleLightningFailure: vi.fn().mockReturnValue(false), + }), +})) + +// Guards an operator report (2026-09-08): clicking the Ecash tab appeared to +// close the whole Receive modal. Not reproduced here — the tab switch alone +// (success or failure of wallet.ecash-lnaddress) never emits `close` or +// unmounts the dialog — but the RPC-eager tab switch is exactly the kind of +// path a future change could regress, so it's worth pinning down. +describe('ReceiveBitcoinModal — ecash tab click', () => { + it('does not close/emit when the ecash tab is clicked and the RPC succeeds', async () => { + vi.mocked(rpcClient.call).mockResolvedValue({ address: 'someone@minibits.cash' } as never) + + const wrapper = mount(ReceiveBitcoinModal, { + props: { show: true }, + attachTo: document.body, + }) + await flushPromises() + + const tabs = Array.from(document.body.querySelectorAll('button')) + const ecashTab = tabs.find((b) => b.textContent?.toLowerCase().includes('ecash')) + expect(ecashTab).toBeTruthy() + + ecashTab!.dispatchEvent(new Event('click', { bubbles: true })) + await flushPromises() + + expect(wrapper.emitted('close')).toBeFalsy() + expect(document.body.querySelector('[role="dialog"]')).toBeTruthy() + wrapper.unmount() + }) + + it('does not close/emit when the ecash tab is clicked and the RPC fails', async () => { + vi.mocked(rpcClient.call).mockRejectedValue(new Error('boom')) + + const wrapper = mount(ReceiveBitcoinModal, { + props: { show: true }, + attachTo: document.body, + }) + await flushPromises() + + const tabs = Array.from(document.body.querySelectorAll('button')) + const ecashTab = tabs.find((b) => b.textContent?.toLowerCase().includes('ecash')) + expect(ecashTab).toBeTruthy() + + ecashTab!.dispatchEvent(new Event('click', { bubbles: true })) + await flushPromises() + + expect(wrapper.emitted('close')).toBeFalsy() + expect(document.body.querySelector('[role="dialog"]')).toBeTruthy() + wrapper.unmount() + }) +})