Browse Source

test(ui): add unit tests for new components

master
jamaljsr 5 years ago
parent
commit
9684d2ae95
  1. 120
      src/components/nodeImages/CustomImageModal.spec.tsx
  2. 6
      src/components/nodeImages/CustomImageModal.tsx
  3. 127
      src/components/nodeImages/CustomImagesTable.spec.tsx
  4. 6
      src/components/nodeImages/CustomImagesTable.tsx
  5. 97
      src/components/nodeImages/ManagedImageModal.spec.tsx
  6. 80
      src/components/nodeImages/ManagedImagesTable.spec.tsx
  7. 91
      src/components/nodeImages/NodeImagesView.spec.tsx
  8. 9
      src/i18n/locales/en-US.json
  9. 21
      src/utils/tests/helpers.ts

120
src/components/nodeImages/CustomImageModal.spec.tsx

@ -0,0 +1,120 @@
import React from 'react';
import { fireEvent, wait, waitForElementToBeRemoved } from '@testing-library/react';
import { CustomImage } from 'types';
import { DOCKER_REPO, dockerConfigs } from 'utils/constants';
import { injections, renderWithProviders, testCustomImages } from 'utils/tests';
import CustomImageModal from './CustomImageModal';
const dockerServiceMock = injections.dockerService as jest.Mocked<
typeof injections.dockerService
>;
describe('CustomImageModal Component', () => {
const onClose = jest.fn();
const newImage: CustomImage = {
id: '',
name: '',
implementation: 'LND',
dockerImage: '',
command: dockerConfigs.LND.command,
};
const renderComponent = async (customImage?: CustomImage) => {
const nodeImages = {
custom: testCustomImages,
};
const initialState = {
app: {
settings: {
nodeImages,
},
},
};
const image = customImage || nodeImages.custom[0];
const result = renderWithProviders(
<CustomImageModal image={image} onClose={onClose} />,
{ initialState },
);
// wait for the loader to go away
await waitForElementToBeRemoved(() => result.getByLabelText('loading'));
return {
...result,
image,
};
};
beforeEach(() => {
dockerServiceMock.getImages.mockResolvedValue(['aaa', 'bbb', `${DOCKER_REPO}/lnd`]);
});
it('should display title and notice', async () => {
const { getByText } = await renderComponent();
expect(getByText('Custom Node Details')).toBeInTheDocument();
expect(
getByText(
'Editing this information will not modify any nodes in existing networks',
),
).toBeInTheDocument();
});
it('should display form fields', async () => {
const { getByText } = await renderComponent();
expect(getByText('Name')).toBeInTheDocument();
expect(getByText('Docker Image')).toBeInTheDocument();
expect(getByText('Command')).toBeInTheDocument();
});
it('should display the command variables', async () => {
const { getByText, image } = await renderComponent();
expect(getByText('Command Variable Substitutions')).toBeInTheDocument();
fireEvent.click(getByText('Command Variable Substitutions'));
const vars = dockerConfigs[image.implementation].variables;
vars.forEach(v => {
expect(getByText(v)).toBeInTheDocument();
});
});
it('should have form fields populated', async () => {
const { getByDisplayValue, image } = await renderComponent();
expect(getByDisplayValue(image.dockerImage)).toBeInTheDocument();
expect(getByDisplayValue(image.command)).toBeInTheDocument();
});
it('should update the command field when the implementation is changed', async () => {
const { getByLabelText, changeSelect } = await renderComponent(newImage);
const impl = getByLabelText('Command') as HTMLTextAreaElement;
expect(impl.value).toContain('lnd');
changeSelect('Implementation', 'c-lightning');
expect(impl.value).toContain('lightningd');
});
it('should display an error notification if fetching docker images fails', async () => {
dockerServiceMock.getImages.mockRejectedValue(new Error('test-error'));
const { findByText } = await renderComponent();
expect(
await findByText('Failed to fetch the list of docker images'),
).toBeInTheDocument();
expect(await findByText('test-error')).toBeInTheDocument();
});
it('should save the managed image', async () => {
const { getByText, getByLabelText, store } = await renderComponent();
fireEvent.change(getByLabelText('Command'), { target: { value: 'a' } });
fireEvent.click(getByText('Save'));
await wait(() => {
expect(store.getState().app.settings.nodeImages.custom[0].command).toBe('a');
});
expect(onClose).toHaveBeenCalled();
});
it('should display an error notification if saving fails', async () => {
onClose.mockImplementation(() => {
throw new Error('test-error');
});
const { getByText, findByText } = await renderComponent();
fireEvent.click(getByText('Save'));
expect(await findByText('Failed to update the Node Image')).toBeInTheDocument();
expect(await findByText('test-error')).toBeInTheDocument();
});
});

6
src/components/nodeImages/CustomImageModal.tsx

@ -22,11 +22,11 @@ const CustomImageModal: React.FC<Props> = ({ image, onClose }) => {
const isEditing = !!image.id;
// get an updated list of docker images in case it's changed since launching the app
useAsync(async () => {
const fetchImagesAsync = useAsync(async () => {
try {
await getDockerImages();
} catch (error) {
notify({ message: l('saveError'), error });
notify({ message: l('loadImagesError'), error });
}
}, [image]);
@ -65,7 +65,7 @@ const CustomImageModal: React.FC<Props> = ({ image, onClose }) => {
onOk={form.submit}
okText={l('okBtn')}
okButtonProps={{
loading: saveAsync.loading,
loading: fetchImagesAsync.loading || saveAsync.loading,
}}
>
<Form

127
src/components/nodeImages/CustomImagesTable.spec.tsx

@ -0,0 +1,127 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
import os from 'os';
import { CustomImage } from 'types';
import { DOCKER_REPO } from 'utils/constants';
import {
injections,
renderWithProviders,
suppressConsoleErrors,
testCustomImages,
} from 'utils/tests';
import CustomImagesTable from './CustomImagesTable';
jest.mock('os');
const mockOS = os as jest.Mocked<typeof os>;
const dockerServiceMock = injections.dockerService as jest.Mocked<
typeof injections.dockerService
>;
const settingsServiceMock = injections.settingsService as jest.Mocked<
typeof injections.settingsService
>;
describe('CustomImagesTable Component', () => {
const renderComponent = (images?: CustomImage[]) => {
const nodeImages = {
custom: images || testCustomImages,
};
const initialState = {
app: {
settings: {
nodeImages,
},
},
};
const result = renderWithProviders(<CustomImagesTable images={nodeImages.custom} />, {
initialState,
});
return {
...result,
nodeImages,
};
};
beforeEach(() => {
mockOS.platform.mockReturnValue('darwin');
dockerServiceMock.getImages.mockResolvedValue(['aaa', 'bbb', `${DOCKER_REPO}/lnd`]);
});
it('should display title', () => {
const { getByText } = renderComponent();
expect(getByText('Custom Nodes')).toBeInTheDocument();
});
it('should display all custom images', () => {
const { getByText, nodeImages } = renderComponent();
nodeImages.custom.forEach(i => {
expect(getByText(i.name)).toBeInTheDocument();
expect(getByText(i.dockerImage)).toBeInTheDocument();
});
});
it('should not render anything if there are no custom nodes', () => {
const { queryByText } = renderComponent([]);
expect(queryByText('Custom Nodes')).not.toBeInTheDocument();
});
it('should not display incompatible custom images', () => {
mockOS.platform.mockReturnValueOnce('win32');
const { queryByText, nodeImages } = renderComponent();
expect(queryByText(nodeImages.custom[1].name)).not.toBeInTheDocument();
});
it('should show the Custom Node Details modal', async () => {
const { getAllByText, findByText } = renderComponent();
// click on the first Edit link
fireEvent.click(getAllByText('Edit')[0]);
expect(await findByText('Custom Node Details')).toBeInTheDocument();
});
it('should hide the Custom Node Details modal', async () => {
const { getAllByText, getByLabelText, queryByText, findByText } = renderComponent();
// click on the first Edit link
fireEvent.click(getAllByText('Edit')[0]);
expect(await findByText('Custom Node Details')).toBeInTheDocument();
fireEvent.click(getByLabelText('close'));
expect(queryByText('Custom Node Details')).not.toBeInTheDocument();
});
it('should remove a custom node', async () => {
const {
getByText,
getAllByLabelText,
findByText,
nodeImages,
store,
} = renderComponent();
const { name } = nodeImages.custom[0];
expect(getByText(name)).toBeInTheDocument();
// click on the first Delete icon
fireEvent.click(getAllByLabelText('delete')[0]);
const title = `Are you sure you want to remove the custom image '${nodeImages.custom[0].name}'?`;
expect(await findByText(title)).toBeInTheDocument();
fireEvent.click(getByText('Yes'));
expect(
await findByText(`The custom image '${name}' has been removed`),
).toBeInTheDocument();
expect(store.getState().app.settings.nodeImages.custom.length).toBe(1);
});
it('should display an error if removing a custom node fails', async () => {
settingsServiceMock.save.mockRejectedValue(new Error('test-error'));
await suppressConsoleErrors(async () => {
const { getByText, getAllByLabelText, findByText, nodeImages } = renderComponent();
const { name } = nodeImages.custom[0];
expect(getByText(name)).toBeInTheDocument();
// click on the first Delete icon
fireEvent.click(getAllByLabelText('delete')[0]);
const title = `Are you sure you want to remove the custom image '${nodeImages.custom[0].name}'?`;
expect(await findByText(title)).toBeInTheDocument();
fireEvent.click(getByText('Yes'));
expect(await findByText('Unable to remove the custom node')).toBeInTheDocument();
expect(await findByText('test-error')).toBeInTheDocument();
});
});
});

6
src/components/nodeImages/CustomImagesTable.tsx

@ -54,9 +54,9 @@ const CustomImagesTable: React.FC<Props> = ({ images }) => {
let modal: any;
const showRemoveModal = (image: CustomImageView) => {
const { dockerImage } = image;
const { name } = image;
modal = Modal.confirm({
title: l('confirmTitle', { dockerImage }),
title: l('confirmTitle', { name }),
content: l('confirmText'),
okText: l('confirmBtn'),
okType: 'danger',
@ -64,7 +64,7 @@ const CustomImagesTable: React.FC<Props> = ({ images }) => {
onOk: async () => {
try {
await removeCustomImage(image);
notify({ message: l('success', { dockerImage }) });
notify({ message: l('success', { name }) });
} catch (error) {
notify({ message: l('error'), error });
throw error;

97
src/components/nodeImages/ManagedImageModal.spec.tsx

@ -0,0 +1,97 @@
import React from 'react';
import { fireEvent, wait } from '@testing-library/react';
import { dockerConfigs } from 'utils/constants';
import { renderWithProviders, testManagedImages } from 'utils/tests';
import ManagedImageModal from './ManagedImageModal';
describe('ManagedImageModal Component', () => {
const onClose = jest.fn();
const renderComponent = () => {
const nodeImages = {
managed: testManagedImages,
};
const initialState = {
app: {
settings: {
nodeImages,
},
},
};
const image = nodeImages.managed[2];
const result = renderWithProviders(
<ManagedImageModal image={image} onClose={onClose} />,
{ initialState },
);
return {
...result,
image,
};
};
it('should display title', () => {
const { getByText } = renderComponent();
expect(getByText(/Customize Managed Node - */)).toBeInTheDocument();
});
it('should display form fields', () => {
const { getByText } = renderComponent();
expect(getByText('Docker Image')).toBeInTheDocument();
expect(getByText('Command')).toBeInTheDocument();
});
it('should display footer buttons', () => {
const { getByText } = renderComponent();
expect(getByText('Reset to Default')).toBeInTheDocument();
expect(getByText('Cancel')).toBeInTheDocument();
expect(getByText('Save')).toBeInTheDocument();
});
it('should display the command variables', () => {
const { getByText, image } = renderComponent();
expect(getByText('Command Variable Substitutions')).toBeInTheDocument();
fireEvent.click(getByText('Command Variable Substitutions'));
const vars = dockerConfigs[image.implementation].variables;
vars.forEach(v => {
expect(getByText(v)).toBeInTheDocument();
});
});
it('should have form fields populated', () => {
const { getByDisplayValue, image } = renderComponent();
const { implementation, version } = image;
const { imageName } = dockerConfigs[implementation];
expect(getByDisplayValue(`${imageName}:${version}`)).toBeInTheDocument();
expect(getByDisplayValue(image.command)).toBeInTheDocument();
});
it('should save the managed image', async () => {
const { getByText, getByLabelText, store } = renderComponent();
fireEvent.change(getByLabelText('Command'), { target: { value: 'a' } });
fireEvent.click(getByText('Save'));
await wait(() => {
expect(store.getState().app.settings.nodeImages.managed[2].command).toBe('a');
});
expect(onClose).toHaveBeenCalled();
});
it('should reset the managed image to default', async () => {
const { getByText, store } = renderComponent();
fireEvent.click(getByText('Reset to Default'));
await wait(() => {
expect(store.getState().app.settings.nodeImages.managed[2]).toBeUndefined();
});
expect(onClose).toHaveBeenCalled();
});
it('should display an error notification if saving fails', async () => {
onClose.mockImplementation(() => {
throw new Error('test-error');
});
const { getByText, findByText } = renderComponent();
fireEvent.click(getByText('Save'));
expect(await findByText('Failed to update the Node Image')).toBeInTheDocument();
expect(await findByText('test-error')).toBeInTheDocument();
});
});

80
src/components/nodeImages/ManagedImagesTable.spec.tsx

@ -0,0 +1,80 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
import os from 'os';
import { DOCKER_REPO } from 'utils/constants';
import { injections, renderWithProviders, testManagedImages } from 'utils/tests';
import ManagedImagesTable from './ManagedImagesTable';
jest.mock('os');
const mockOS = os as jest.Mocked<typeof os>;
const dockerServiceMock = injections.dockerService as jest.Mocked<
typeof injections.dockerService
>;
describe('ManagedImagesTable Component', () => {
const renderComponent = () => {
const nodeImages = {
managed: testManagedImages,
};
const initialState = {
app: {
settings: {
nodeImages,
},
},
};
const result = renderWithProviders(
<ManagedImagesTable images={nodeImages.managed} />,
{ initialState },
);
return {
...result,
nodeImages,
};
};
beforeEach(() => {
mockOS.platform.mockReturnValue('darwin');
dockerServiceMock.getImages.mockResolvedValue(['aaa', 'bbb', `${DOCKER_REPO}/lnd`]);
});
it('should display title', () => {
const { getByText } = renderComponent();
expect(getByText('Nodes Managed by Polar')).toBeInTheDocument();
});
it('should display all managed images', () => {
const { getAllByText } = renderComponent();
// 1 is the number of each implementation in testManagedImages
expect(getAllByText('polarlightning/lnd')).toHaveLength(1);
expect(getAllByText('polarlightning/clightning')).toHaveLength(1);
expect(getAllByText('polarlightning/bitcoind')).toHaveLength(1);
});
it('should not display incompatible managed images', () => {
mockOS.platform.mockReturnValueOnce('win32');
const { queryAllByText } = renderComponent();
// 1 is the number of each implementation in testManagedImages
expect(queryAllByText('polarlightning/lnd')).toHaveLength(1);
expect(queryAllByText('polarlightning/clightning')).toHaveLength(0);
expect(queryAllByText('polarlightning/bitcoind')).toHaveLength(1);
});
it('should show the Customize Managed Node modal', async () => {
const { getAllByText, findByText } = renderComponent();
// click on the first Edit link
fireEvent.click(getAllByText('Edit')[0]);
expect(await findByText(/Customize Managed Node - */)).toBeInTheDocument();
});
it('should hide the Customize Managed Node modal', async () => {
const { getAllByText, getByLabelText, queryByText, findByText } = renderComponent();
// click on the first Edit link
fireEvent.click(getAllByText('Edit')[0]);
expect(await findByText(/Customize Managed Node - */)).toBeInTheDocument();
fireEvent.click(getByLabelText('close'));
expect(queryByText(/Customize Managed Node - */)).not.toBeInTheDocument();
});
});

91
src/components/nodeImages/NodeImagesView.spec.tsx

@ -0,0 +1,91 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
import { defaultRepoState, DOCKER_REPO } from 'utils/constants';
import {
injections,
renderWithProviders,
testCustomImages,
testManagedImages,
} from 'utils/tests';
import NodeImagesView from './NodeImagesView';
const dockerServiceMock = injections.dockerService as jest.Mocked<
typeof injections.dockerService
>;
describe('NodeImagesView Component', () => {
const renderComponent = () => {
const nodeImages = {
managed: testManagedImages,
custom: testCustomImages,
};
const initialState = {
app: {
settings: {
nodeImages,
},
},
};
const result = renderWithProviders(<NodeImagesView />, {
initialState,
});
return {
...result,
nodeImages,
};
};
beforeEach(() => {
dockerServiceMock.getImages.mockResolvedValue(['aaa', 'bbb', `${DOCKER_REPO}/lnd`]);
});
it('should display titles', () => {
const { getByText } = renderComponent();
expect(getByText('Customize Node Docker Images')).toBeInTheDocument();
expect(getByText('Custom Nodes')).toBeInTheDocument();
expect(getByText('Nodes Managed by Polar')).toBeInTheDocument();
});
it('should display all managed images', () => {
const { getAllByText } = renderComponent();
expect(getAllByText('polarlightning/lnd')).toHaveLength(
defaultRepoState.images.LND.versions.length,
);
expect(getAllByText('polarlightning/clightning')).toHaveLength(
defaultRepoState.images['c-lightning'].versions.length,
);
expect(getAllByText('polarlightning/bitcoind')).toHaveLength(
defaultRepoState.images.bitcoind.versions.length,
);
});
it('should display custom images', () => {
const { getByText, nodeImages } = renderComponent();
const custom = nodeImages.custom[0];
expect(getByText(custom.name)).toBeInTheDocument();
expect(getByText(custom.dockerImage)).toBeInTheDocument();
});
it('should navigate home when back button clicked', () => {
const { getByLabelText, history } = renderComponent();
const backBtn = getByLabelText('Back');
expect(backBtn).toBeInTheDocument();
fireEvent.click(backBtn);
expect(history.location.pathname).toEqual('/');
});
it('should open the Add Custom Image modal', async () => {
const { getByText, findByText } = renderComponent();
fireEvent.click(getByText('Add a Custom Node'));
expect(await findByText('Custom Node Details')).toBeInTheDocument();
});
it('should close the Add Custom Image modal', async () => {
const { getByText, getByLabelText, queryByText, findByText } = renderComponent();
fireEvent.click(getByText('Add a Custom Node'));
expect(await findByText('Custom Node Details')).toBeInTheDocument();
fireEvent.click(getByLabelText('close'));
expect(queryByText('Custom Node Details')).not.toBeInTheDocument();
});
});

9
src/i18n/locales/en-US.json

@ -282,7 +282,8 @@
"cmps.nodeImages.CustomImageModal.cancelBtn": "Cancel",
"cmps.nodeImages.CustomImageModal.okBtn": "Save",
"cmps.nodeImages.CustomImageModal.editingInfo": "Editing this information will not modify any nodes in existing networks",
"cmps.nodeImages.CustomImageModal.saveError": "Failed to update the Node",
"cmps.nodeImages.CustomImageModal.loadImagesError": "Failed to fetch the list of docker images",
"cmps.nodeImages.CustomImageModal.saveError": "Failed to update the Node Image",
"cmps.nodeImages.CustomImagesTable.title": "Custom Nodes",
"cmps.nodeImages.CustomImagesTable.implementation": "Implementation",
"cmps.nodeImages.CustomImagesTable.name": "Name",
@ -290,11 +291,11 @@
"cmps.nodeImages.CustomImagesTable.command": "Docker Command",
"cmps.nodeImages.CustomImagesTable.manage": "Manage",
"cmps.nodeImages.CustomImagesTable.edit": "Edit",
"cmps.nodeImages.CustomImagesTable.confirmTitle": "Are you sure you want to remove the custom image '{{dockerImage}}'?",
"cmps.nodeImages.CustomImagesTable.confirmTitle": "Are you sure you want to remove the custom image '{{name}}'?",
"cmps.nodeImages.CustomImagesTable.confirmText": "This image will no longer be displayed in the network sidebar. Any nodes in existing networks will continue to use this image until they are removed.",
"cmps.nodeImages.CustomImagesTable.confirmBtn": "Yes",
"cmps.nodeImages.CustomImagesTable.cancelBtn": "Cancel",
"cmps.nodeImages.CustomImagesTable.success": "The custom image '{{dockerImage}}' has been removed",
"cmps.nodeImages.CustomImagesTable.success": "The custom image '{{name}}' has been removed",
"cmps.nodeImages.CustomImagesTable.error": "Unable to remove the custom node",
"cmps.nodeImages.ManagedImageModal.title": "Customize Managed Node - {{implementation}} v{{version}}",
"cmps.nodeImages.ManagedImageModal.summary": "This form allows you to customize the command used to start nodes. All nodes that use the specified Docker Image will use this command to startup.",
@ -303,7 +304,7 @@
"cmps.nodeImages.ManagedImageModal.resetBtn": "Reset to Default",
"cmps.nodeImages.ManagedImageModal.cancelBtn": "Cancel",
"cmps.nodeImages.ManagedImageModal.okBtn": "Save",
"cmps.nodeImages.ManagedImageModal.saveError": "Failed to update the Node",
"cmps.nodeImages.ManagedImageModal.saveError": "Failed to update the Node Image",
"cmps.nodeImages.ManagedImagesTable.title": "Nodes Managed by Polar",
"cmps.nodeImages.ManagedImagesTable.implementation": "Implementation",
"cmps.nodeImages.ManagedImagesTable.dockerImage": "Docker Image",

21
src/utils/tests/helpers.ts

@ -1,5 +1,5 @@
import { Status } from 'shared/types';
import { ManagedImage, Network } from 'types';
import { CustomImage, ManagedImage, Network } from 'types';
import { defaultRepoState } from 'utils/constants';
import { createNetwork } from '../network';
@ -13,7 +13,24 @@ export const testManagedImages: ManagedImage[] = [
{
implementation: 'bitcoind',
version: defaultRepoState.images.bitcoind.latest,
command: '',
command: 'test-bitcoind-command',
},
];
export const testCustomImages: CustomImage[] = [
{
id: '123',
name: 'My Custom Image',
implementation: 'LND',
dockerImage: 'lnd:master',
command: 'test-command',
},
{
id: '456',
name: 'Another Custom Image',
implementation: 'c-lightning',
dockerImage: 'my-clightning:latest',
command: 'another-command',
},
];

Loading…
Cancel
Save