From d5b38740c7e18138b3036a24aa901937b6d2dc7d Mon Sep 17 00:00:00 2001 From: Thebora Kompanioni Date: Wed, 29 Jul 2026 21:39:50 +0200 Subject: [PATCH] fix: disable freeze/unfreeze when maker/taker/rescan is active (#1379) --- .../fidelity-bond/useFidelityBondMutations.ts | 3 + .../wallet/WalletJarsDetailsContent.test.tsx | 99 ++++++++++++++++--- .../wallet/WalletJarsDetailsContent.tsx | 10 +- 3 files changed, 98 insertions(+), 14 deletions(-) diff --git a/src/components/earn/fidelity-bond/useFidelityBondMutations.ts b/src/components/earn/fidelity-bond/useFidelityBondMutations.ts index 51249c08..c47f4d5a 100644 --- a/src/components/earn/fidelity-bond/useFidelityBondMutations.ts +++ b/src/components/earn/fidelity-bond/useFidelityBondMutations.ts @@ -26,16 +26,19 @@ export function useFidelityBondMutations({ const freezeUtxo = useMutation({ ...freezeMutation({ client }), + retry: false, onError: (error) => setErrorWithReason(error, t('global.errors.error_freezing_utxos')), }) const unfreezeUtxo = useMutation({ ...freezeMutation({ client }), + retry: false, onError: (error) => setErrorWithReason(error, t(unfreezeErrorKey)), }) const directSend = useMutation({ ...directsendMutation({ client }), + retry: false, onError: (error) => setErrorWithReason(error, t(sendErrorKey)), }) diff --git a/src/components/wallet/WalletJarsDetailsContent.test.tsx b/src/components/wallet/WalletJarsDetailsContent.test.tsx index b9559bcf..0059db97 100644 --- a/src/components/wallet/WalletJarsDetailsContent.test.tsx +++ b/src/components/wallet/WalletJarsDetailsContent.test.tsx @@ -5,8 +5,13 @@ import type { AccountMeta, AccountSummary, AddressMeta, AddressSummary, Jar } fr import type { Utxo } from '@/hooks/useQueryUtxos' import { WalletJarsDetailsContent } from './WalletJarsDetailsContent' -const walletInfoRefetch = vi.hoisted(() => vi.fn()) -const toastMocks = vi.hoisted(() => ({ success: vi.fn(), warning: vi.fn() })) +const { walletInfoRefetch, toastMocks, ...mocks } = vi.hoisted(() => ({ + walletInfoRefetch: vi.fn(), + toastMocks: { success: vi.fn(), warning: vi.fn() }, + rescanning: false, + takerRunning: false, + makerRunning: false, +})) vi.mock('sonner', () => ({ toast: { @@ -50,6 +55,14 @@ vi.mock('../ui/jam/Balance', () => ({ Balance: ({ valueString }: { valueString: string }) => {valueString}, })) +vi.mock('@/context/JamSessionInfoContext', () => ({ + useJamSessionInfoContext: () => ({ + takerInfo: { running: mocks.takerRunning }, + rescanInfo: { rescanning: mocks.rescanning }, + makerInfo: { running: mocks.makerRunning }, + }), +})) + const makeUtxo = (overrides: Partial): Utxo => ({ address: 'bc1qwallet-a', @@ -146,32 +159,51 @@ describe('WalletJarsDetailsContent', () => { walletInfoRefetch.mockResolvedValue({}) toastMocks.success.mockReset() toastMocks.warning.mockReset() + mocks.rescanning = false + mocks.takerRunning = false + mocks.makerRunning = false + // vi.resetAllMocks() + }) + + it('renders successfully', () => { + render() + + const jarTab = screen.getByRole('tab', { name: jars[0].name }) + expect(jarTab).toHaveAttribute('data-state', 'active') + expect(screen.getByText('bc1qwallet-a')).toBeInTheDocument() }) it('renders selected jar UTXOs and debug details', async () => { const user = userEvent.setup() + const jar = jars[1] - render() + render() - expect(screen.getByRole('tab', { name: 'One' })).toHaveAttribute('data-state', 'active') + const jarTab = screen.getByRole('tab', { name: jar.name }) + expect(jarTab).toHaveAttribute('data-state', 'active') expect(screen.getByText('bc1qwallet-b')).toBeInTheDocument() - await user.click(screen.getByRole('tab', { name: /Dev/u })) + const devTab = screen.getByRole('tab', { name: /Dev/u }) + await user.click(devTab) expect(screen.getByText('activeJar:')).toBeInTheDocument() }) it('switches jars with keyboard shortcuts and shows missing account information', async () => { const user = userEvent.setup() + const jar = jars[1] - render() + render() expect(screen.getByText('bc1qwallet-b')).toBeInTheDocument() await user.keyboard('{ArrowLeft}') + expect(screen.getByText('bc1qwallet-a')).toBeInTheDocument() - await user.click(screen.getByRole('tab', { name: 'One' })) + await user.click(screen.getByRole('tab', { name: jar.name })) + await user.click(screen.getByRole('tab', { name: 'jar_details.title_tab_jar_details' })) + expect(screen.getByText('jar_details.utxo_list.alert_no_account_info_title')).toBeInTheDocument() }) @@ -180,7 +212,10 @@ describe('WalletJarsDetailsContent', () => { render() + expect(screen.getByText('bc1qwallet-a')).toBeInTheDocument() + await user.keyboard('{ArrowRight}') + expect(screen.getByText('bc1qwallet-a')).toBeInTheDocument() }) @@ -192,7 +227,11 @@ describe('WalletJarsDetailsContent', () => { it('refreshes wallet info from the utxos tab', async () => { const user = userEvent.setup() render() - await user.click(screen.getByRole('button', { name: 'global.refresh' })) + + const refreshButton = screen.getByRole('button', { name: 'global.refresh' }) + expect(refreshButton).toBeEnabled() + + await user.click(refreshButton) expect(walletInfoRefetch).toHaveBeenCalled() }) @@ -200,11 +239,18 @@ describe('WalletJarsDetailsContent', () => { const user = userEvent.setup() render() + const freezeButton = screen.getByRole('button', { name: 'jar_details.utxo_list.button_freeze' }) + expect(freezeButton).toBeDisabled() + const dataRow = screen.getAllByRole('row').find((row) => within(row).queryByText('bc1qwallet-b'))! await user.click(within(dataRow).getByRole('checkbox')) - await user.click(screen.getByRole('button', { name: 'jar_details.utxo_list.button_freeze' })) - await waitFor(() => expect(toastMocks.success).toHaveBeenCalled()) + expect(freezeButton).toBeEnabled() + + await user.click(freezeButton) + await waitFor(() => + expect(toastMocks.success).toHaveBeenCalledWith('jar_details.utxo_list.toast_freeze_success:{"count":1}'), + ) expect(walletInfoRefetch).toHaveBeenCalled() }) @@ -212,10 +258,39 @@ describe('WalletJarsDetailsContent', () => { const user = userEvent.setup() render() + const unfreezeButton = screen.getByRole('button', { name: 'jar_details.utxo_list.button_unfreeze' }) + expect(unfreezeButton).toBeDisabled() + const dataRow = screen.getAllByRole('row').find((row) => within(row).queryByText('bc1qwallet-c'))! await user.click(within(dataRow).getByRole('checkbox')) - await user.click(screen.getByRole('button', { name: 'jar_details.utxo_list.button_unfreeze' })) - await waitFor(() => expect(toastMocks.success).toHaveBeenCalled()) + expect(unfreezeButton).toBeEnabled() + + await user.click(unfreezeButton) + await waitFor(() => + expect(toastMocks.success).toHaveBeenCalledWith('jar_details.utxo_list.toast_unfreeze_success:{"count":1}'), + ) + expect(walletInfoRefetch).toHaveBeenCalled() + }) + + it.each(['taker', 'maker', 'rescan'])('disables freeze/unfreeze if rescan/maker/taker is running', async (value) => { + const user = userEvent.setup() + mocks.takerRunning = value === 'taker' + mocks.makerRunning = value === 'maker' + mocks.rescanning = value === 'rescan' + + render() + + const freezeButton = screen.getByRole('button', { name: 'jar_details.utxo_list.button_freeze' }) + expect(freezeButton).toBeDisabled() + + const unfreezeButton = screen.getByRole('button', { name: 'jar_details.utxo_list.button_unfreeze' }) + expect(unfreezeButton).toBeDisabled() + + const dataRow = screen.getAllByRole('row').find((row) => within(row).queryByText('bc1qwallet-b'))! + await user.click(within(dataRow).getByRole('checkbox')) + + expect(freezeButton).toBeDisabled() + expect(unfreezeButton).toBeDisabled() }) }) diff --git a/src/components/wallet/WalletJarsDetailsContent.tsx b/src/components/wallet/WalletJarsDetailsContent.tsx index 595f15f3..e145599b 100644 --- a/src/components/wallet/WalletJarsDetailsContent.tsx +++ b/src/components/wallet/WalletJarsDetailsContent.tsx @@ -6,6 +6,7 @@ import type { TFunction } from 'i18next' import { AlertTriangleIcon, RefreshCwIcon, ThermometerSnowflakeIcon, ThermometerSunIcon } from 'lucide-react' import { Trans, useTranslation } from 'react-i18next' import { toast } from 'sonner' +import { useJamSessionInfoContext } from '@/context/JamSessionInfoContext' import { useAccountSummary, useAddressSummary, @@ -57,6 +58,7 @@ interface UtxosContentProps { export const UtxosContent = ({ enabled, walletFileName, addressSummary, jar }: UtxosContentProps) => { const { t } = useTranslation() + const { takerInfo, rescanInfo, makerInfo } = useJamSessionInfoContext() const { refetch: walletInfoRefetch, isFetching: walletInfoIsFetching } = useJamWalletInfoContext() const client = useApiClient() @@ -127,8 +129,12 @@ export const UtxosContent = ({ enabled, walletFileName, addressSummary, jar }: U return tableEntries.filter((entry) => entry.tags.some((tag) => tag.value === 'reused')).length }, [tableEntries]) - // TODO: makerRunning, takerRunner, rescanRunning, etc. - const operationsEnabled = enabled && !(walletInfoIsFetching || freezeUtxos.isPending || unfreezeUtxos.isPending) + const operationsEnabled = + enabled && + !makerInfo.running && + !takerInfo.running && + !rescanInfo.rescanning && + !(walletInfoIsFetching || freezeUtxos.isPending || unfreezeUtxos.isPending) const enableRowSelection = enabled && !(freezeUtxos.isPending || unfreezeUtxos.isPending) const onFreezeClick = async () => {