From 01d5f706d2b619f15381f082ded9ea9978021c41 Mon Sep 17 00:00:00 2001 From: Jemimah Nagasha Date: Sun, 19 Oct 2025 16:54:45 +0300 Subject: [PATCH 1/3] feat(networks): retain custom block count input --- .../bitcoin/actions/MineBlocksInput.tsx | 15 ++++++++---- src/store/models/network.ts | 23 +++++++++++++++++++ src/types/index.ts | 1 + src/utils/network.ts | 3 +++ src/utils/tests/helpers.ts | 1 + 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/components/designer/bitcoin/actions/MineBlocksInput.tsx b/src/components/designer/bitcoin/actions/MineBlocksInput.tsx index c8bb21e7f8..2bf25fc63a 100644 --- a/src/components/designer/bitcoin/actions/MineBlocksInput.tsx +++ b/src/components/designer/bitcoin/actions/MineBlocksInput.tsx @@ -5,7 +5,7 @@ import styled from '@emotion/styled'; import { Button, Form, Input, InputNumber } from 'antd'; import { usePrefixedTranslation } from 'hooks'; import { BitcoinNode } from 'shared/types'; -import { useStoreActions } from 'store'; +import { useStoreActions, useStoreState } from 'store'; const InputGroup = Input.Group; @@ -20,12 +20,17 @@ const Styled = { const MineBlocksInput: React.FC<{ node: BitcoinNode }> = ({ node }) => { const { l } = usePrefixedTranslation('cmps.designer.bitcoind.MineBlocksInput'); - const [value, setValue] = useState(6); + const network = useStoreState(s => s.network.networkById(node.networkId)); const { notify } = useStoreActions(s => s.app); const { mine } = useStoreActions(s => s.bitcoin); + const { updateManualMineCount } = useStoreActions(actions => actions.network); + + const [count, setCount] = useState(network.manualMineCount); + const mineAsync = useAsyncCallback(async () => { try { - await mine({ blocks: value, node }); + await mine({ blocks: count, node }); + await updateManualMineCount({ id: node.networkId, count }); } catch (error: any) { notify({ message: l('error'), error }); } @@ -35,9 +40,9 @@ const MineBlocksInput: React.FC<{ node: BitcoinNode }> = ({ node }) => { setValue(parseInt(v as any))} + onChange={v => setCount(parseInt(v as any))} disabled={mineAsync.loading} /> ; + manualMineCount: number; } export interface AutoMinerModel { @@ -192,6 +193,14 @@ export interface NetworkModel { setAutoMineMode: Action; setMiningState: Action; mineBlock: Thunk; + setManualMineCount: Action; + updateManualMineCount: Thunk< + NetworkModel, + { id: number; count: number }, + StoreInjections, + RootModel, + Promise + >; } const networkModel: NetworkModel = { @@ -286,6 +295,7 @@ const networkModel: NetworkModel = { managedImages: computedManagedImages, customImages, basePorts: settings.basePorts, + manualMineCount: 6, }); actions.add(network); const { networks } = getState(); @@ -1074,6 +1084,19 @@ const networkModel: NetworkModel = { } }, ), + setManualMineCount: action((state, { id, count }) => { + const network = state.networks.find(n => n.id === id); + if (!network) throw new Error(l('networkByIdErr', { networkId: id })); + network.manualMineCount = count; + }), + updateManualMineCount: thunk(async (actions, { id, count }, { getState }) => { + const networks = getState().networks; + const network = networks.find(n => n.id === id); + if (!network) throw new Error(l('networkByIdErr', { networkId: id })); + + actions.setManualMineCount({ id, count }); + await actions.save(); + }), }; export default networkModel; diff --git a/src/types/index.ts b/src/types/index.ts index a70c89830b..ee01a5d322 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -31,6 +31,7 @@ export interface Network { lightning: LightningNode[]; tap: TapNode[]; }; + manualMineCount: number; } /** diff --git a/src/utils/network.ts b/src/utils/network.ts index d8106052f1..5f826c7b5f 100644 --- a/src/utils/network.ts +++ b/src/utils/network.ts @@ -456,6 +456,7 @@ export const createNetwork = (config: { customImages: { image: CustomImage; count: number }[]; status?: Status; basePorts?: NodeBasePorts; + manualMineCount: number; }): Network => { const { id, @@ -471,6 +472,7 @@ export const createNetwork = (config: { managedImages, customImages, basePorts, + manualMineCount, } = config; // need explicit undefined check because Status.Starting is 0 const status = config.status !== undefined ? config.status : Status.Stopped; @@ -487,6 +489,7 @@ export const createNetwork = (config: { tap: [], }, autoMineMode: AutoMineMode.AutoOff, + manualMineCount, }; const { bitcoin, lightning } = network.nodes; diff --git a/src/utils/tests/helpers.ts b/src/utils/tests/helpers.ts index 456214d71c..537e1cbc1f 100644 --- a/src/utils/tests/helpers.ts +++ b/src/utils/tests/helpers.ts @@ -240,6 +240,7 @@ export const getNetwork = ( repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }; if (tapNodeCount > 0) { config.lndNodes = 0; From d23dcbf96867bafb06a09097245385b8b7d8adfd Mon Sep 17 00:00:00 2001 From: Jemimah Nagasha Date: Sun, 19 Oct 2025 20:45:07 +0300 Subject: [PATCH 2/3] test(network): adding missing tests --- src/store/models/network.spec.ts | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/store/models/network.spec.ts b/src/store/models/network.spec.ts index 745d9a2276..3b70fa47c8 100644 --- a/src/store/models/network.spec.ts +++ b/src/store/models/network.spec.ts @@ -65,6 +65,7 @@ describe('Network model', () => { tapdNodes: 0, litdNodes: 1, customNodes: {}, + manualMineCount: 6, }; beforeEach(() => { @@ -1023,6 +1024,42 @@ describe('Network model', () => { }); }); + describe('ManualMineCount', () => { + beforeEach(async () => { + await store.getActions().network.addNetwork(addNetworkArgs); + }); + + it('should set manual mine count for a network', () => { + const { setManualMineCount } = store.getActions().network; + const networkId = firstNetwork().id; + + expect(firstNetwork().manualMineCount).toBe(6); + setManualMineCount({ id: networkId, count: 10 }); + expect(firstNetwork().manualMineCount).toBe(10); + }); + + it('should fail to set manual mine count with invalid network id', () => { + const { setManualMineCount } = store.getActions().network; + expect(() => setManualMineCount({ id: 999, count: 10 })).toThrow( + "Network with the id '999' was not found.", + ); + }); + + it('should update manual mine count and persist changes', async () => { + const { updateManualMineCount } = store.getActions().network; + const networkId = firstNetwork().id; + await updateManualMineCount({ id: networkId, count: 15 }); + expect(firstNetwork().manualMineCount).toBe(15); + }); + + it('should fail to update manual mine count with invalid network id', () => { + const { updateManualMineCount } = store.getActions().network; + expect(() => updateManualMineCount({ id: 999, count: 10 })).rejects.toThrow( + "Network with the id '999' was not found.", + ); + }); + }); + describe('Other actions', () => { it('should remove a network', async () => { expect(store.getState().network.networks).toHaveLength(0); From 6dc747943ae5e65d4d3eb3ede0d3664d69adf8a9 Mon Sep 17 00:00:00 2001 From: Jemimah Nagasha Date: Mon, 20 Oct 2025 18:04:30 +0300 Subject: [PATCH 3/3] fix: missing type property --- src/components/common/RenameNodeModal.spec.tsx | 3 ++- .../designer/bitcoin/actions/SendOnChainModal.spec.tsx | 3 ++- .../lightning/actions/CreateInvoiceModal.spec.tsx | 1 + .../designer/lightning/actions/OpenChannelModal.spec.tsx | 1 + .../designer/lightning/actions/PayInvoiceModal.spec.tsx | 1 + src/components/network/ImportNetwork.spec.tsx | 1 + src/lib/docker/composeFile.spec.ts | 1 + src/lib/docker/dockerService.spec.ts | 9 +++++++++ src/lib/litd/litdProxyClient.spec.ts | 1 + src/lib/litd/litdService.spec.ts | 1 + src/store/models/designer.spec.ts | 4 ++++ src/store/models/lit.spec.ts | 1 + src/utils/network.spec.ts | 2 ++ 13 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/components/common/RenameNodeModal.spec.tsx b/src/components/common/RenameNodeModal.spec.tsx index c77a128005..5a8f14739b 100644 --- a/src/components/common/RenameNodeModal.spec.tsx +++ b/src/components/common/RenameNodeModal.spec.tsx @@ -6,9 +6,9 @@ import { initChartFromNetwork } from 'utils/chart'; import { defaultRepoState } from 'utils/constants'; import { createNetwork } from 'utils/network'; import { + bitcoinServiceMock, injections, lightningServiceMock, - bitcoinServiceMock, litdServiceMock, renderWithProviders, tapServiceMock, @@ -41,6 +41,7 @@ describe('RenameNodeModal', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const initialState = { network: { diff --git a/src/components/designer/bitcoin/actions/SendOnChainModal.spec.tsx b/src/components/designer/bitcoin/actions/SendOnChainModal.spec.tsx index 73d3d63451..78531a0c70 100644 --- a/src/components/designer/bitcoin/actions/SendOnChainModal.spec.tsx +++ b/src/components/designer/bitcoin/actions/SendOnChainModal.spec.tsx @@ -6,10 +6,10 @@ import { initChartFromNetwork } from 'utils/chart'; import { defaultRepoState } from 'utils/constants'; import { createNetwork } from 'utils/network'; import { + bitcoinServiceMock, renderWithProviders, suppressConsoleErrors, testManagedImages, - bitcoinServiceMock, } from 'utils/tests'; import SendOnChainModal from './SendOnChainModal'; @@ -31,6 +31,7 @@ describe('SendOnChainModal', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const initialState = { diff --git a/src/components/designer/lightning/actions/CreateInvoiceModal.spec.tsx b/src/components/designer/lightning/actions/CreateInvoiceModal.spec.tsx index c092bedda4..7a1939ee6b 100644 --- a/src/components/designer/lightning/actions/CreateInvoiceModal.spec.tsx +++ b/src/components/designer/lightning/actions/CreateInvoiceModal.spec.tsx @@ -150,6 +150,7 @@ describe('CreateInvoiceModal', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const asset: LightningNodeChannelAsset = { id: 'abcd', diff --git a/src/components/designer/lightning/actions/OpenChannelModal.spec.tsx b/src/components/designer/lightning/actions/OpenChannelModal.spec.tsx index 8e1a4ac755..d76da8723d 100644 --- a/src/components/designer/lightning/actions/OpenChannelModal.spec.tsx +++ b/src/components/designer/lightning/actions/OpenChannelModal.spec.tsx @@ -288,6 +288,7 @@ describe('OpenChannelModal', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const asset: LightningNodeChannelAsset = { id: 'abcd', diff --git a/src/components/designer/lightning/actions/PayInvoiceModal.spec.tsx b/src/components/designer/lightning/actions/PayInvoiceModal.spec.tsx index b9bf825f55..7d3c97c79f 100644 --- a/src/components/designer/lightning/actions/PayInvoiceModal.spec.tsx +++ b/src/components/designer/lightning/actions/PayInvoiceModal.spec.tsx @@ -149,6 +149,7 @@ describe('PayInvoiceModal', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const asset: LightningNodeChannelAsset = { id: 'abcd', diff --git a/src/components/network/ImportNetwork.spec.tsx b/src/components/network/ImportNetwork.spec.tsx index ded5bbb11d..9a7bf054fa 100644 --- a/src/components/network/ImportNetwork.spec.tsx +++ b/src/components/network/ImportNetwork.spec.tsx @@ -72,6 +72,7 @@ describe('ImportNetwork component', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); chart = initChartFromNetwork(network); filesMock.read.mockResolvedValue(JSON.stringify({ network, chart })); diff --git a/src/lib/docker/composeFile.spec.ts b/src/lib/docker/composeFile.spec.ts index d9bfb59f46..c9605ae0e9 100644 --- a/src/lib/docker/composeFile.spec.ts +++ b/src/lib/docker/composeFile.spec.ts @@ -19,6 +19,7 @@ describe('ComposeFile', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const btcNode = network.nodes.bitcoin[0]; const lndNode = network.nodes.lightning[0] as LndNode; diff --git a/src/lib/docker/dockerService.spec.ts b/src/lib/docker/dockerService.spec.ts index ace1c4520d..44b7e49ff7 100644 --- a/src/lib/docker/dockerService.spec.ts +++ b/src/lib/docker/dockerService.spec.ts @@ -192,6 +192,7 @@ describe('DockerService', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); net.nodes.lightning[0].backendName = 'invalid'; dockerService.saveComposeFile(net); @@ -217,6 +218,7 @@ describe('DockerService', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); net.nodes.lightning[0].backendName = 'invalid'; dockerService.saveComposeFile(net); @@ -242,6 +244,7 @@ describe('DockerService', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); net.nodes.lightning[0].backendName = 'invalid'; dockerService.saveComposeFile(net); @@ -308,6 +311,7 @@ describe('DockerService', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); dockerService.saveComposeFile(net); const { backendName } = net.nodes.lightning[0] as LitdNode; @@ -331,6 +335,7 @@ describe('DockerService', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const litdNode = net.nodes.lightning[0] as LitdNode; litdNode.backendName = 'invalid'; @@ -357,6 +362,7 @@ describe('DockerService', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); net.nodes.lightning[0].implementation = 'unknown' as any; dockerService.saveComposeFile(net); @@ -392,6 +398,7 @@ describe('DockerService', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const chart = initChartFromNetwork(net); // return 'any' to suppress "The operand of a 'delete' operator must be optional.ts(2790)" error @@ -522,6 +529,7 @@ describe('DockerService', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const chart = initChartFromNetwork(net); const fileData: NetworksFile = { @@ -716,6 +724,7 @@ describe('DockerService', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); composeMock.upAll.mockResolvedValue(mockResult); await dockerService.start(net); diff --git a/src/lib/litd/litdProxyClient.spec.ts b/src/lib/litd/litdProxyClient.spec.ts index 608b2188c4..ef8d619572 100644 --- a/src/lib/litd/litdProxyClient.spec.ts +++ b/src/lib/litd/litdProxyClient.spec.ts @@ -20,6 +20,7 @@ describe('LitdProxyClient', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const node = network.nodes.lightning[0] as LitdNode; let actualIpc: IpcSender; diff --git a/src/lib/litd/litdService.spec.ts b/src/lib/litd/litdService.spec.ts index 3760134547..883a886bb7 100644 --- a/src/lib/litd/litdService.spec.ts +++ b/src/lib/litd/litdService.spec.ts @@ -24,6 +24,7 @@ describe('LitdService', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const node = network.nodes.lightning[0] as LitdNode; diff --git a/src/store/models/designer.spec.ts b/src/store/models/designer.spec.ts index 7d42244824..7139b18805 100644 --- a/src/store/models/designer.spec.ts +++ b/src/store/models/designer.spec.ts @@ -133,6 +133,7 @@ describe('Designer model', () => { tapdNodes: 0, litdNodes: 0, customNodes: {}, + manualMineCount: 6, }); store.getActions().designer.setActiveId(firstNetwork().id); const { removeChart } = store.getActions().designer; @@ -581,6 +582,7 @@ describe('Designer model', () => { tapdNodes: 0, litdNodes: 0, customNodes: {}, + manualMineCount: 6, }); const newId = store.getState().network.networks[1].id; setActiveId(newId); @@ -610,6 +612,7 @@ describe('Designer model', () => { tapdNodes: 0, litdNodes: 0, customNodes: {}, + manualMineCount: 6, }); const newId = store.getState().network.networks[1].id; setActiveId(newId); @@ -647,6 +650,7 @@ describe('Designer model', () => { tapdNodes: 0, litdNodes: 0, customNodes: {}, + manualMineCount: 0, }); const newId = store.getState().network.networks[1].id; setActiveId(newId); diff --git a/src/store/models/lit.spec.ts b/src/store/models/lit.spec.ts index d808ba928c..10f7822de3 100644 --- a/src/store/models/lit.spec.ts +++ b/src/store/models/lit.spec.ts @@ -44,6 +44,7 @@ describe('LIT Model', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); const initialState = { network: { diff --git a/src/utils/network.spec.ts b/src/utils/network.spec.ts index 05efdf07dd..c7024963a1 100644 --- a/src/utils/network.spec.ts +++ b/src/utils/network.spec.ts @@ -163,6 +163,7 @@ describe('Network Utils', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); }); @@ -507,6 +508,7 @@ describe('Network Utils', () => { repoState: defaultRepoState, managedImages: testManagedImages, customImages: [], + manualMineCount: 6, }); });