fix: disable freeze/unfreeze when maker/taker/rescan is active (#1379)
Some checks are pending
Build / build (v26.5.0) (push) Waiting to run
CodeQL / Analyze (push) Waiting to run
Deploy Storybook / deploy (push) Waiting to run

This commit is contained in:
Thebora Kompanioni 2026-07-29 21:39:50 +02:00 committed by GitHub
parent 864e976772
commit d5b38740c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 98 additions and 14 deletions

View file

@ -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)),
})

View file

@ -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 }) => <span>{valueString}</span>,
}))
vi.mock('@/context/JamSessionInfoContext', () => ({
useJamSessionInfoContext: () => ({
takerInfo: { running: mocks.takerRunning },
rescanInfo: { rescanning: mocks.rescanning },
makerInfo: { running: mocks.makerRunning },
}),
}))
const makeUtxo = (overrides: Partial<Utxo>): 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(<WalletJarsDetailsContent enabled walletFileName="wallet.jmdat" />)
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(<WalletJarsDetailsContent enabled walletFileName="wallet.jmdat" selectedJarIndex={1} debug />)
render(<WalletJarsDetailsContent enabled walletFileName="wallet.jmdat" selectedJarIndex={jar.jarIndex} debug />)
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(<WalletJarsDetailsContent enabled walletFileName="wallet.jmdat" selectedJarIndex={1} />)
render(<WalletJarsDetailsContent enabled walletFileName="wallet.jmdat" selectedJarIndex={jar.jarIndex} />)
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(<WalletJarsDetailsContent enabled={false} walletFileName="wallet.jmdat" selectedJarIndex={0} />)
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(<WalletJarsDetailsContent enabled walletFileName="wallet.jmdat" selectedJarIndex={1} />)
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(<WalletJarsDetailsContent enabled walletFileName="wallet.jmdat" selectedJarIndex={1} />)
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(<WalletJarsDetailsContent enabled walletFileName="wallet.jmdat" selectedJarIndex={2} />)
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(<WalletJarsDetailsContent enabled walletFileName="wallet.jmdat" selectedJarIndex={1} />)
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()
})
})

View file

@ -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 () => {