Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/persistent-docker-network #734

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
{ "language": "typescriptreact", "autoFix": true }
],
"importSorter.generalConfiguration.sortOnBeforeSave": true,
"importSorter.sortConfiguration.removeUnusedImports": true,
"importSorter.sortConfiguration.removeUnusedImports": false,
"importSorter.importStringConfiguration.tabSize": 2,
"importSorter.importStringConfiguration.trailingComma": "multiLine",
"importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.count": 90,
Expand Down Expand Up @@ -65,8 +65,5 @@
"orderLevel": 80
}
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"typescript.tsdk": "node_modules/typescript/lib"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"prebuild": "tsc -p electron/tsconfig.json && yarn theme",
"prepare": "husky install",
"release": "standard-version --no-verify --skip.commit --skip.tag",
"start": "rescripts start",
"start": "rescripts --openssl-legacy-provider start",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why this change was needed for you, but I got this error when trying to start the app with it:

node: bad option: --openssl-legacy-provider

When I removed this option it worked fine.

"test": "cross-env DEBUG_PRINT_LIMIT=15000 rescripts test --env=jest-environment-jsdom-sixteen",
"test:ci": "cross-env CI=true yarn test --coverage",
"test:all": "yarn test:ci && yarn test:e2e",
Expand Down
118 changes: 118 additions & 0 deletions src/components/common/DockerNetworkName.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React from 'react';
import { act, fireEvent } from '@testing-library/react';
import { Form } from 'antd';
import { injections, renderWithProviders } from 'utils/tests';
import DockerNetworkName from './DockerNetworkName';

// Mock the useStoreActions hook
const mockDockerService = injections.dockerService as jest.Mocked<
typeof injections.dockerService
>;

describe('DockerNetworkName', () => {
let unmount: () => void;
let result: any;
const mockValidateCallback = jest.fn();

const renderComponent = async () => {
await act(async () => {
result = renderWithProviders(
<Form>
<DockerNetworkName name="network" validateCallback={mockValidateCallback} />
</Form>,
);
unmount = result.unmount;
//return result;
});
};

beforeEach(() => {
mockDockerService.getDockerExternalNetworks.mockResolvedValue([
'test-network',
'test-network-2',
]);
});

afterEach(() => unmount());

describe('When the modal renders', () => {
it('should display External Docker Network label', async () => {
await renderComponent();
const { getByText } = result;
expect(getByText('External Docker Network')).toBeInTheDocument();
expect(getByText('Select a network leave blank to clear')).toBeInTheDocument();
});

it('should fetch external docker networks on mount', async () => {
await renderComponent();
expect(mockDockerService.getDockerExternalNetworks).toHaveBeenCalledTimes(1);
});
});

describe('When the form item validates', () => {
it('should show help message for creating an external docker network', async () => {
await renderComponent();

const { getByLabelText, findByText, container } = result;
const input = getByLabelText('External Docker Network') as HTMLInputElement;
fireEvent.change(input, { target: { value: 'test-network-3' } });
expect(
await findByText('Docker External Network will be created'),
).toBeInTheDocument();
expect(mockValidateCallback).toHaveBeenCalledWith(true);
const element = container.querySelector('.ant-select.ant-select-status-warning');
expect(element).not.toBeNull();
});

it('should show help message for attaching to a external docker network', async () => {
await renderComponent();
const { getByLabelText, getByText, findByText, container } = result;

expect(getByText('External Docker Network')).toBeInTheDocument();
expect(getByLabelText('External Docker Network')).toBeInstanceOf(HTMLInputElement);
const input = getByLabelText('External Docker Network') as HTMLInputElement;
fireEvent.change(input, { target: { value: 'test-network' } });
expect(
await findByText('Docker Network will be attached to test-network'),
).toBeInTheDocument();
expect(mockValidateCallback).toHaveBeenCalledWith(true);
fireEvent.change(input, { target: { value: 'test-network-2' } });
expect(
await findByText('Docker Network will be attached to test-network-2'),
).toBeInTheDocument();
expect(mockValidateCallback).toHaveBeenCalledWith(true);
const element = container.querySelector('.ant-select.ant-select-status-success');
expect(element).not.toBeNull();
});

it('should show help message for an invalid docker network name', async () => {
await renderComponent();

const { getByLabelText, findByText, container } = result;
const input = getByLabelText('External Docker Network') as HTMLInputElement;
fireEvent.change(input, { target: { value: '__' } });
expect(await findByText('Invalid Docker Network Name')).toBeInTheDocument();
expect(mockValidateCallback).toHaveBeenCalledWith(false);
const element = container.querySelector('.ant-select.ant-select-status-error');
expect(element).not.toBeNull();
});

it('should show help message for clearing the external network', async () => {
await renderComponent();

const { getByLabelText, findByText, container } = result;
const input = getByLabelText('External Docker Network') as HTMLInputElement;

fireEvent.change(input, { target: { value: 'default' } });
expect(await findByText('Docker Network will be cleared')).toBeInTheDocument();

fireEvent.change(input, { target: { value: '' } });
expect(await findByText('Docker Network will be cleared')).toBeInTheDocument();

const element = container.querySelector('.ant-select.ant-select-status-success');
expect(element).not.toBeNull();
});
});

// Add more test cases as needed
});
77 changes: 77 additions & 0 deletions src/components/common/DockerNetworkName.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useEffect, useState } from 'react';
import { AutoComplete, Form } from 'antd';
import { usePrefixedTranslation } from 'hooks';
import { useStoreActions } from 'store';

type Status = '' | 'warning' | 'error' | undefined;

interface Props {
name: string;
defaultValue?: string;
validateCallback?: (value: boolean) => void;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of bubbling up the validation status via props, you could use rules prop to specify a custom regex pattern. This would prevent the form from submitting, display the error message inline, and change the border color of the input field.

<Form.Item rules={[{ pattern: /^[a-zA-Z0-9][a-zA-Z0-9_.-]{1,63}$/, message: l('helpInvalid') }]}>

}

const DockerNetworkName: React.FC<Props> = ({ name, defaultValue, validateCallback }) => {
const { l } = usePrefixedTranslation('cmps.common.form.DockerNetworkName');

const { getExternalDockerNetworks } = useStoreActions(s => s.network);

const [dockerNetworks, setDockerNetworks] = useState<string[]>([]);
const [status, setStatus] = useState<Status>(undefined);
const [help, setHelp] = useState<string>('');

const validate = (isValid: boolean) => {
validateCallback && validateCallback(isValid);
};

useEffect(() => {
(async () => {
const networks = await getExternalDockerNetworks();
setDockerNetworks(networks);
})();
}, []);

const validateNetworkName = async (value: string) => {
if (value.length === 0 || value === 'default') {
setHelp(l('helpClear'));
setStatus(undefined);
validate(true);
return;
}
const regex = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{1,63}$/;
if (regex.test(value)) {
if (!dockerNetworks.includes(value)) {
setHelp(l('helpCreate'));
setStatus('warning');
validate(true);
} else {
setHelp(l('helpAttach', { dockerNetwork: value }));
setStatus(undefined);
validate(true);
}
} else {
setHelp(l('helpInvalid'));
setStatus('error');
validate(false);
}
};

return (
<Form.Item name={name} label={l('label')} help={help}>
<AutoComplete
options={dockerNetworks?.map(network => ({
value: network,
}))}
placeholder={l('placeholder')}
filterOption={(inputValue, option) =>
option?.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
}
onChange={validateNetworkName}
status={status}
defaultValue={defaultValue}
/>
</Form.Item>
);
};

export default DockerNetworkName;
28 changes: 16 additions & 12 deletions src/components/common/StatusBadge.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,37 @@ import { Status } from 'shared/types';
import { renderWithProviders } from 'utils/tests';
import StatusTag from './StatusTag';

describe('StatusBadge Component', () => {
describe('StatusTag Component', () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change this StatusBadge.tsx file to test the StatusTag component? Did you mean to create a new test file?

const renderComponent = (status: Status) => {
return renderWithProviders(<StatusTag status={status} />);
const result = renderWithProviders(<StatusTag status={status} />);
return {
...result,
dot: result.container.querySelector('.ant-tag'),
};
};

it('should render the Starting status', () => {
const { getByText } = renderComponent(Status.Starting);
expect(getByText('Starting')).toBeInTheDocument();
const { dot } = renderComponent(Status.Starting);
expect(dot).toHaveClass('ant-tag-blue');
});

it('should render the Started status', () => {
const { getByText } = renderComponent(Status.Started);
expect(getByText('Started')).toBeInTheDocument();
const { dot } = renderComponent(Status.Started);
expect(dot).toHaveClass('ant-tag-green');
});

it('should render the Stopping status', () => {
const { getByText } = renderComponent(Status.Stopping);
expect(getByText('Stopping')).toBeInTheDocument();
const { dot } = renderComponent(Status.Stopping);
expect(dot).toHaveClass('ant-tag-blue');
});

it('should render the Stopped status', () => {
const { getByText } = renderComponent(Status.Stopped);
expect(getByText('Stopped')).toBeInTheDocument();
const { dot } = renderComponent(Status.Stopped);
expect(dot).toHaveClass('ant-tag-red');
});

it('should render the Error status', () => {
const { getByText } = renderComponent(Status.Error);
expect(getByText('Error')).toBeInTheDocument();
const { dot } = renderComponent(Status.Error);
expect(dot).toHaveClass('ant-tag-red');
});
});
5 changes: 1 addition & 4 deletions src/components/common/StatusTag.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Tag } from 'antd';
import { useTheme } from 'hooks/useTheme';
import { Status } from 'shared/types';

export interface StatusTagProps {
Expand All @@ -11,13 +10,11 @@ export interface StatusTagProps {
const StatusTag: React.FC<StatusTagProps> = ({ status }) => {
const { t } = useTranslation();

const { statusTag } = useTheme();

const statusColors = {
[Status.Starting]: 'blue',
[Status.Started]: 'green',
[Status.Stopping]: 'blue',
[Status.Stopped]: statusTag.stopped,
[Status.Stopped]: 'red',
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be changed to red because stopped is not an error state.

[Status.Error]: 'red',
};

Expand Down
10 changes: 10 additions & 0 deletions src/components/designer/NetworkDesigner.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,16 @@ describe('NetworkDesigner Component', () => {
fireEvent.click(getByText('Cancel'));
});

it('should display the Docker Network modal', async () => {
const { getByText, findByText, store } = renderComponent();
expect(await findByText('backend1')).toBeInTheDocument();
act(() => {
store.getActions().modals.showDockerNetwork({});
});
expect(await findByText('Docker Network Options')).toBeInTheDocument();
fireEvent.click(getByText('Cancel'));
});

it('should remove a node from the network', async () => {
const { getByText, findByText, queryByText } = renderComponent();
expect(await findByText('alice')).toBeInTheDocument();
Expand Down
3 changes: 3 additions & 0 deletions src/components/designer/NetworkDesigner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useStoreActions, useStoreState } from 'store';
import { Network } from 'types';
import { Loader } from 'components/common';
import AdvancedOptionsModal from 'components/common/AdvancedOptionsModal';
import DockerNetworkModal from 'components/network/DockerNetworkModal';
import SendOnChainModal from './bitcoind/actions/SendOnChainModal';
import { CanvasOuterDark, Link, NodeInner, Port, Ports } from './custom';
import {
Expand Down Expand Up @@ -61,6 +62,7 @@ const NetworkDesigner: React.FC<Props> = ({ network, updateStateDelay = 3000 })
sendOnChain,
advancedOptions,
changeTapBackend,
dockerNetwork,
} = useStoreState(s => s.modals);

const { save } = useStoreActions(s => s.network);
Expand Down Expand Up @@ -108,6 +110,7 @@ const NetworkDesigner: React.FC<Props> = ({ network, updateStateDelay = 3000 })
{newAddress.visible && <NewAddressModal network={network} />}
{changeTapBackend.visible && <ChangeTapBackendModal network={network} />}
{sendAsset.visible && <SendAssetModal network={network} />}
{dockerNetwork.visible && <DockerNetworkModal network={network} />}
</Styled.Designer>
);
};
Expand Down
2 changes: 2 additions & 0 deletions src/components/designer/SidebarCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @jest-ignore

Comment on lines +1 to +2
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These empty lines aren't necessary.

import styled from '@emotion/styled';
import { Card } from 'antd';

Expand Down
Loading