Browse Source

test(unit): add global afterEach to cleanup antd components

master
jamaljsr 5 years ago
parent
commit
5284ec87a3
  1. 9
      src/components/designer/NetworkDesigner.spec.tsx
  2. 10
      src/components/designer/bitcoind/actions/RemoveNode.spec.tsx
  3. 7
      src/components/designer/lightning/actions/CreateInvoiceModal.spec.tsx
  4. 67
      src/components/designer/lightning/actions/OpenChannelModal.spec.tsx
  5. 6
      src/components/designer/lightning/actions/PayInvoiceModal.spec.tsx
  6. 16
      src/components/designer/lightning/actions/RemoveNode.spec.tsx
  7. 27
      src/components/network/NetworkView.spec.tsx
  8. 14
      src/setupTests.js

9
src/components/designer/NetworkDesigner.spec.tsx

@ -117,6 +117,15 @@ describe('NetworkDesigner Component', () => {
expect(await findByText('BOLT 11 Invoice')).toBeInTheDocument();
});
it('should display the ChangeBackend modal', async () => {
const { findByText, store } = renderComponent();
expect(await findByText('backend1')).toBeInTheDocument();
act(() => {
store.getActions().modals.showChangeBackend({});
});
expect(await findByText('Lightning Node')).toBeInTheDocument();
});
it('should remove a node from the network', async () => {
const { getByText, findByText, queryByText } = renderComponent();
expect(await findByText('alice')).toBeInTheDocument();

10
src/components/designer/bitcoind/actions/RemoveNode.spec.tsx

@ -1,7 +1,5 @@
import React from 'react';
import { fireEvent, waitForElement } from '@testing-library/dom';
import { waitForElementToBeRemoved } from '@testing-library/react';
import { Modal, notification } from 'antd';
import { BitcoindVersion, Status } from 'shared/types';
import { BitcoindLibrary, DockerLibrary } from 'types';
import { initChartFromNetwork } from 'utils/chart';
@ -53,14 +51,6 @@ describe('RemoveNode', () => {
bitcoindServiceMock.waitUntilOnline.mockResolvedValue(Promise.resolve());
});
afterEach(async () => {
Modal.destroyAll();
notification.destroy();
// wait for the modal to be removed before starting the next test
const getModal = () => document.querySelector('.ant-modal-root');
if (getModal()) await waitForElementToBeRemoved(getModal);
});
it('should show the remove node modal', async () => {
const { getByText } = renderComponent(Status.Started);
expect(getByText('Remove')).toBeInTheDocument();

7
src/components/designer/lightning/actions/CreateInvoiceModal.spec.tsx

@ -1,6 +1,5 @@
import React from 'react';
import { fireEvent, wait } from '@testing-library/dom';
import { message, Modal, notification } from 'antd';
import { Status } from 'shared/types';
import { initChartFromNetwork } from 'utils/chart';
import {
@ -39,12 +38,6 @@ describe('CreateInvoiceModal', () => {
};
};
afterEach(() => {
message.destroy();
notification.destroy();
Modal.destroyAll();
});
it('should render labels', async () => {
const { getByText } = await renderComponent();
expect(getByText('Node')).toBeInTheDocument();

67
src/components/designer/lightning/actions/OpenChannelModal.spec.tsx

@ -1,7 +1,6 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { fireEvent, wait, waitForElementToBeRemoved } from '@testing-library/dom';
import { Modal, notification } from 'antd';
import { Status } from 'shared/types';
import { BitcoindLibrary } from 'types';
import { initChartFromNetwork } from 'utils/chart';
@ -47,11 +46,6 @@ describe('OpenChannelModal', () => {
};
};
afterEach(async () => {
Modal.destroyAll();
notification.destroy();
});
it('should render labels', async () => {
const { getByText } = await renderComponent();
expect(getByText('Source')).toBeInTheDocument();
@ -74,29 +68,30 @@ describe('OpenChannelModal', () => {
});
it('should hide modal when cancel is clicked', async () => {
jest.useFakeTimers();
const { getByText, queryByText } = await renderComponent();
const btn = getByText('Cancel');
expect(btn).toBeInTheDocument();
expect(btn.parentElement).toBeInstanceOf(HTMLButtonElement);
await wait(() => fireEvent.click(getByText('Cancel')));
jest.runAllTimers();
fireEvent.click(getByText('Cancel'));
await wait();
expect(queryByText('Cancel')).not.toBeInTheDocument();
jest.useRealTimers();
});
it('should remove chart link when cancel is clicked', async () => {
const { getByText, store } = await renderComponent(Status.Started);
const linkId = 'xxxx';
await wait(() => {
const { designer } = store.getActions();
const link = { linkId, fromNodeId: 'alice', fromPortId: 'p1' } as any;
// create a new link which will open the modal
const { designer } = store.getActions();
const link = { linkId, fromNodeId: 'alice', fromPortId: 'p1' } as any;
// create a new link which will open the modal
act(() => {
designer.onLinkStart(link);
});
act(() => {
designer.onLinkComplete({ ...link, toNodeId: 'bob', toPortId: 'p2' } as any);
});
expect(store.getState().designer.activeChart.links[linkId]).toBeTruthy();
await wait(() => fireEvent.click(getByText('Cancel')));
fireEvent.click(getByText('Cancel'));
await wait();
expect(store.getState().designer.activeChart.links[linkId]).toBeUndefined();
});
@ -110,19 +105,20 @@ describe('OpenChannelModal', () => {
it('should display an error if form is not valid', async () => {
await suppressConsoleErrors(async () => {
const { getAllByText, getByText, store } = await renderComponent();
await wait(() => store.getActions().modals.showOpenChannel({}));
await wait(() => fireEvent.click(getByText('Open Channel')));
act(() => store.getActions().modals.showOpenChannel({}));
fireEvent.click(getByText('Open Channel'));
expect(getAllByText('required')).toHaveLength(2);
});
});
it('should do nothing if an invalid node is selected', async () => {
const { getByText, getByLabelText, store } = await renderComponent();
await wait(() => {
store.getActions().modals.showOpenChannel({ from: 'invalid', to: 'invalid2' });
});
act(() =>
store.getActions().modals.showOpenChannel({ from: 'invalid', to: 'invalid2' }),
);
fireEvent.change(getByLabelText('Capacity (sats)'), { target: { value: '1000' } });
await wait(() => fireEvent.click(getByText('Open Channel')));
fireEvent.click(getByText('Open Channel'));
await wait();
expect(getByText('Open Channel')).toBeInTheDocument();
});
@ -143,15 +139,12 @@ describe('OpenChannelModal', () => {
it('should open a channel successfully', async () => {
const { getByText, getByLabelText, store, network } = await renderComponent();
await wait(() => {
store.getActions().modals.showOpenChannel({ from: 'bob', to: 'alice' });
});
act(() => store.getActions().modals.showOpenChannel({ from: 'bob', to: 'alice' }));
fireEvent.change(getByLabelText('Capacity (sats)'), { target: { value: '1000' } });
fireEvent.click(getByLabelText('Deposit enough funds to bob to open the channel'));
fireEvent.click(getByText('Open Channel'));
await wait(() => {
expect(store.getState().modals.openChannel.visible).toBe(false);
});
await wait();
expect(store.getState().modals.openChannel.visible).toBe(false);
const node2 = network.nodes.lightning[1];
expect(lightningServiceMock.openChannel).toBeCalledWith(node2, 'asdf@host', 1000);
expect(bitcoindServiceMock.mine).toBeCalledTimes(1);
@ -159,16 +152,11 @@ describe('OpenChannelModal', () => {
it('should open a channel and deposit funds', async () => {
const { getByText, getByLabelText, store, network } = await renderComponent();
await wait(() => {
store.getActions().modals.showOpenChannel({ from: 'bob', to: 'alice' });
});
act(() => store.getActions().modals.showOpenChannel({ from: 'bob', to: 'alice' }));
fireEvent.change(getByLabelText('Capacity (sats)'), { target: { value: '1000' } });
act(() => {
fireEvent.click(getByText('Open Channel'));
});
await wait(() => {
expect(store.getState().modals.openChannel.visible).toBe(false);
});
fireEvent.click(getByText('Open Channel'));
await wait();
expect(store.getState().modals.openChannel.visible).toBe(false);
const node2 = network.nodes.lightning[1];
expect(lightningServiceMock.openChannel).toBeCalledWith(node2, 'asdf@host', 1000);
expect(bitcoindServiceMock.mine).toBeCalledTimes(2);
@ -179,12 +167,11 @@ describe('OpenChannelModal', () => {
it('should display an error when opening a channel fails', async () => {
lightningServiceMock.openChannel.mockRejectedValue(new Error('error-msg'));
const { getByText, getByLabelText, store } = await renderComponent();
await wait(() => {
store.getActions().modals.showOpenChannel({ from: 'bob', to: 'alice' });
});
act(() => store.getActions().modals.showOpenChannel({ from: 'bob', to: 'alice' }));
fireEvent.change(getByLabelText('Capacity (sats)'), { target: { value: '1000' } });
fireEvent.click(getByLabelText('Deposit enough funds to bob to open the channel'));
await wait(() => fireEvent.click(getByText('Open Channel')));
fireEvent.click(getByText('Open Channel'));
await wait();
expect(getByText('Unable to open the channel')).toBeInTheDocument();
expect(getByText('error-msg')).toBeInTheDocument();
});

6
src/components/designer/lightning/actions/PayInvoiceModal.spec.tsx

@ -1,6 +1,5 @@
import React from 'react';
import { fireEvent, wait } from '@testing-library/dom';
import { Modal, notification } from 'antd';
import { Status } from 'shared/types';
import { initChartFromNetwork } from 'utils/chart';
import {
@ -39,11 +38,6 @@ describe('PayInvoiceModal', () => {
};
};
afterEach(async () => {
Modal.destroyAll();
notification.destroy();
});
it('should render labels', async () => {
const { getByText } = await renderComponent();
expect(getByText('From Node')).toBeInTheDocument();

16
src/components/designer/lightning/actions/RemoveNode.spec.tsx

@ -1,11 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {
fireEvent,
waitForElement,
waitForElementToBeRemoved,
} from '@testing-library/dom';
import { Modal, notification } from 'antd';
import { fireEvent, waitForElement } from '@testing-library/dom';
import { Status } from 'shared/types';
import { DockerLibrary } from 'types';
import { initChartFromNetwork } from 'utils/chart';
@ -51,14 +45,6 @@ describe('RemoveNode', () => {
lightningServiceMock.getChannels.mockResolvedValue([]);
});
afterEach(async () => {
Modal.destroyAll();
notification.destroy();
// wait for the modal to be removed before starting the next test
const getModal = () => document.querySelector('.ant-modal-root');
if (getModal()) await waitForElementToBeRemoved(getModal);
});
it('should show the remove node modal', async () => {
const { getByText } = renderComponent(Status.Started);
expect(getByText('Remove')).toBeInTheDocument();

27
src/components/network/NetworkView.spec.tsx

@ -190,15 +190,10 @@ describe('NetworkView Component', () => {
});
describe('delete network', () => {
beforeEach(jest.useFakeTimers);
afterEach(jest.useRealTimers);
it('should show the confirm modal', async () => {
const { getByLabelText, getByText } = renderComponent('1');
const { getByLabelText, getByText, findByText } = renderComponent('1');
fireEvent.mouseOver(getByLabelText('icon: more'));
await wait(() => jest.runOnlyPendingTimers());
fireEvent.click(getByText('Delete'));
await wait(() => jest.runOnlyPendingTimers());
fireEvent.click(await findByText('Delete'));
expect(
getByText('Are you sure you want to delete this network?'),
).toBeInTheDocument();
@ -207,17 +202,14 @@ describe('NetworkView Component', () => {
});
it('should delete the network', async () => {
const { getByLabelText, getByText, getAllByText, store } = renderComponent(
const { getByLabelText, getByText, findByText, store } = renderComponent(
'1',
Status.Started,
);
const path = store.getState().network.networks[0].path;
fireEvent.mouseOver(getByLabelText('icon: more'));
await wait(() => jest.runOnlyPendingTimers());
fireEvent.click(getByText('Delete'));
await wait(() => jest.runOnlyPendingTimers());
// antd creates two modals in the DOM for some silly reason. Need to click one
fireEvent.click(getAllByText('Yes')[0]);
fireEvent.click(await findByText('Delete'));
fireEvent.click(await findByText('Yes'));
// wait for the error notification to be displayed
await waitForElement(() => getByLabelText('icon: check-circle-o'));
expect(
@ -231,13 +223,10 @@ describe('NetworkView Component', () => {
// this supresses those errors from being displayed in test runs
await suppressConsoleErrors(async () => {
fsMock.remove = jest.fn().mockRejectedValue(new Error('cannot delete'));
const { getByLabelText, getByText, getAllByText, store } = renderComponent('1');
const { getByLabelText, getByText, findByText, store } = renderComponent('1');
fireEvent.mouseOver(getByLabelText('icon: more'));
await wait(() => jest.runOnlyPendingTimers());
fireEvent.click(getByText('Delete'));
await wait(() => jest.runOnlyPendingTimers());
// antd creates two modals in the DOM for some silly reason. Need to click one
fireEvent.click(getAllByText('Yes')[0]);
fireEvent.click(await findByText('Delete'));
fireEvent.click(await findByText('Yes'));
// wait for the error notification to be displayed
await waitForElement(() => getByLabelText('icon: close-circle-o'));
expect(getByText('cannot delete')).toBeInTheDocument();

14
src/setupTests.js

@ -1,3 +1,5 @@
import { message, Modal, notification } from 'antd';
import { waitForElementToBeRemoved } from '@testing-library/dom';
import './i18n';
// this adds jest-dom's custom assertions
import '@testing-library/jest-dom/extend-expect';
@ -19,3 +21,15 @@ console.warn = (...args) => {
}
originalConsoleWarning(...args);
};
afterEach(async () => {
// these antd components are rendered outside of the DOM tree of the component being tested,
// so they are not automatically cleaned up by the testing-library's cleanup function. This
// code below destroys those components before the next test is run
message.destroy();
notification.destroy();
Modal.destroyAll();
// wait for the modal to be removed before starting the next test. it uses a short animation
const getModal = () => document.querySelector('.ant-modal-root');
if (getModal()) await waitForElementToBeRemoved(getModal);
});

Loading…
Cancel
Save