diff --git a/src/components/designer/bitcoind/BitcoindDetails.spec.tsx b/src/components/designer/bitcoind/BitcoindDetails.spec.tsx
new file mode 100644
index 0000000..2a9b943
--- /dev/null
+++ b/src/components/designer/bitcoind/BitcoindDetails.spec.tsx
@@ -0,0 +1,99 @@
+import React from 'react';
+import { Status } from 'types';
+import { getNetwork, injections, renderWithProviders } from 'utils/tests';
+import BitcoindDetails from './BitcoindDetails';
+
+describe('BitcoindDetails', () => {
+ const renderComponent = (status?: Status) => {
+ const network = getNetwork(1, 'test network', status);
+ const initialState = {
+ network: {
+ networks: [network],
+ },
+ };
+ const node = network.nodes.bitcoin[0];
+ const cmp = ;
+ const result = renderWithProviders(cmp, { initialState });
+ return {
+ ...result,
+ node,
+ };
+ };
+
+ describe('with node Stopped', () => {
+ it('should display Node Type', () => {
+ const { getByText, node } = renderComponent();
+ expect(getByText('Node Type')).toBeInTheDocument();
+ expect(getByText(node.type)).toBeInTheDocument();
+ });
+
+ it('should display Implementation', () => {
+ const { getByText, node } = renderComponent();
+ expect(getByText('Implementation')).toBeInTheDocument();
+ expect(getByText(node.implementation)).toBeInTheDocument();
+ });
+
+ it('should display Version', () => {
+ const { getByText, node } = renderComponent();
+ expect(getByText('Version')).toBeInTheDocument();
+ expect(getByText(`v${node.version}`)).toBeInTheDocument();
+ });
+
+ it('should display Status', () => {
+ const { getByText, node } = renderComponent();
+ expect(getByText('Status')).toBeInTheDocument();
+ expect(getByText(Status[node.status])).toBeInTheDocument();
+ });
+
+ it('should not display Block Height', () => {
+ const { queryByText } = renderComponent();
+ expect(queryByText('Block Height')).toBeNull();
+ });
+ });
+
+ describe('with node Started', () => {
+ const chainMock = injections.bitcoindService.getBlockchainInfo as jest.Mock;
+ const walletMock = injections.bitcoindService.getWalletInfo as jest.Mock;
+
+ beforeEach(() => {
+ chainMock.mockResolvedValue({ blocks: 123, bestblockhash: 'abcdef' });
+ walletMock.mockResolvedValue({ balance: 10 });
+ });
+
+ it('should display correct Status', async () => {
+ const { findByText, node } = renderComponent(Status.Started);
+ expect(await findByText('Status')).toBeInTheDocument();
+ expect(await findByText(Status[node.status])).toBeInTheDocument();
+ });
+
+ it('should display RPC Host', async () => {
+ const { findByText, node } = renderComponent(Status.Started);
+ expect(await findByText('RPC Host')).toBeInTheDocument();
+ expect(await findByText(`127.0.0.1:${node.ports.rpc}`)).toBeInTheDocument();
+ });
+
+ it('should display Wallet Balance', async () => {
+ const { findByText } = renderComponent(Status.Started);
+ expect(await findByText('Wallet Balance')).toBeInTheDocument();
+ expect(await findByText('10 BTC')).toBeInTheDocument();
+ });
+
+ it('should display Block Height', async () => {
+ const { findByText } = renderComponent(Status.Started);
+ expect(await findByText('Block Height')).toBeInTheDocument();
+ expect(await findByText('123')).toBeInTheDocument();
+ });
+
+ it('should display Block Hash', async () => {
+ const { findByText } = renderComponent(Status.Started);
+ expect(await findByText('Block Hash')).toBeInTheDocument();
+ expect(await findByText('abcdef')).toBeInTheDocument();
+ });
+
+ it('should display an error if data fetching fails', async () => {
+ walletMock.mockRejectedValue(new Error('connection failed'));
+ const { findByText } = renderComponent(Status.Started);
+ expect(await findByText('connection failed')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/components/designer/bitcoind/BitcoindDetails.tsx b/src/components/designer/bitcoind/BitcoindDetails.tsx
index 4b93533..4069ef0 100644
--- a/src/components/designer/bitcoind/BitcoindDetails.tsx
+++ b/src/components/designer/bitcoind/BitcoindDetails.tsx
@@ -24,7 +24,7 @@ const BitcoindDetails: React.FC<{ node: BitcoinNode }> = ({ node }) => {
const details: DetailValues = [
{ label: 'Node Type', value: node.type },
{ label: 'Implementation', value: node.implementation },
- { label: 'Version', value: 'v0.18.1' },
+ { label: 'Version', value: `v${node.version}` },
{
label: 'Status',
value: ,
diff --git a/src/components/designer/bitcoind/MineBlocksInput.spec.tsx b/src/components/designer/bitcoind/MineBlocksInput.spec.tsx
new file mode 100644
index 0000000..505d35e
--- /dev/null
+++ b/src/components/designer/bitcoind/MineBlocksInput.spec.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import { fireEvent } from '@testing-library/dom';
+import { getNetwork, injections, renderWithProviders } from 'utils/tests';
+import MineBlocksInput from './MineBlocksInput';
+
+describe('MineBlocksInput', () => {
+ const renderComponent = () => {
+ const network = getNetwork(1, 'test network');
+ const initialState = {
+ network: {
+ networks: [network],
+ },
+ };
+ const cmp = ;
+ const result = renderWithProviders(cmp, { initialState });
+ return {
+ ...result,
+ input: result.container.querySelector('input') as HTMLInputElement,
+ btn: result.getByText('Mine').parentElement as HTMLElement,
+ };
+ };
+
+ it('should render label', () => {
+ const { getByText } = renderComponent();
+ expect(getByText('Manually Mine Blocks')).toBeInTheDocument();
+ });
+
+ it('should render button', () => {
+ const { btn } = renderComponent();
+ expect(btn).toBeInTheDocument();
+ expect(btn).toBeInstanceOf(HTMLButtonElement);
+ });
+
+ it('should render input field', () => {
+ const { input } = renderComponent();
+ expect(input).toBeInTheDocument();
+ expect(input).toBeInstanceOf(HTMLInputElement);
+ });
+
+ it('should use a default value of 3 for the input', () => {
+ const { input } = renderComponent();
+ expect(input.value).toEqual('3');
+ });
+
+ it('should mine a block when the button is clicked', () => {
+ const mineMock = injections.bitcoindService.mine as jest.Mock;
+ mineMock.mockResolvedValue(true);
+ const { input, btn } = renderComponent();
+ const numBlocks = 5;
+ fireEvent.change(input, { target: { value: numBlocks } });
+ fireEvent.click(btn);
+ expect(mineMock).toBeCalledWith(numBlocks);
+ });
+
+ it('should display an error if mining fails', async () => {
+ const mineMock = injections.bitcoindService.mine as jest.Mock;
+ mineMock.mockRejectedValue(new Error('connection failed'));
+ const { input, btn, findByText } = renderComponent();
+ const numBlocks = 5;
+ fireEvent.change(input, { target: { value: numBlocks } });
+ fireEvent.click(btn);
+ expect(await findByText(/connection failed/)).toBeInTheDocument();
+ });
+
+ it('should display an error if blocks is below 1', async () => {
+ const { input, btn, findByText } = renderComponent();
+ const numBlocks = -5;
+ fireEvent.change(input, { target: { value: numBlocks } });
+ fireEvent.click(btn);
+ expect(await findByText(/must be a positve number/)).toBeInTheDocument();
+ });
+});
diff --git a/src/store/models/bitcoind.ts b/src/store/models/bitcoind.ts
index df46702..7c67a77 100644
--- a/src/store/models/bitcoind.ts
+++ b/src/store/models/bitcoind.ts
@@ -25,6 +25,9 @@ const bitcoindModel: BitcoindModel = {
actions.setWalletinfo(await injections.bitcoindService.getWalletInfo());
}),
mine: thunk(async (actions, { blocks, node }, { injections }) => {
+ if (blocks < 0) {
+ throw new Error('The number of blocks to mine must be a positve number');
+ }
await injections.bitcoindService.mine(blocks);
await actions.getInfo(node);
}),