diff --git a/__mocks__/react-i18next.ts b/__mocks__/react-i18next.ts new file mode 100644 index 00000000..68ac12e3 --- /dev/null +++ b/__mocks__/react-i18next.ts @@ -0,0 +1,24 @@ +// Copyright 2019-2021 @polkadot/extension-ui authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +interface useTranslationReturnObj { + i18n: { changeLanguage: () => Promise; }; + t: (str: string) => string; +} + +export const useTranslation = (): useTranslationReturnObj => { + return { + i18n: { + changeLanguage: () => new Promise(() => { /**/ }) + }, + t: (str: string) => str + }; +}; + +export const withTranslation = () => (component: React.ReactElement): React.ReactElement => component; + +export const Trans = ({ children }: { children: React.ReactElement }): React.ReactElement => children; + +export default withTranslation; diff --git a/packages/extension-ui/src/Popup/ImportQr.test.tsx b/packages/extension-ui/src/Popup/ImportQr.test.tsx new file mode 100644 index 00000000..9aa2a34c --- /dev/null +++ b/packages/extension-ui/src/Popup/ImportQr.test.tsx @@ -0,0 +1,128 @@ +// Copyright 2019-2021 @polkadot/extension-ui authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import '../../../../__mocks__/chrome'; + +import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; +import { configure, mount, ReactWrapper } from 'enzyme'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { MemoryRouter } from 'react-router'; + +import { Button } from '../components'; +import * as messaging from '../messaging'; +import { flushAllPromises } from '../testHelpers'; +import ImportQr from './ImportQr'; + +const mockedAccount = { + content: '12bxf6QJS5hMJgwbJMDjFot1sq93EvgQwyuPWENr9SzJfxtN', + expectedBannerChain: 'Polkadot', + genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3', + isAddress: true, + name: 'My Polkadot Account' +}; + +interface ScanType { + isAddress: boolean; + content: string; + genesisHash: string; + name?: string; +} + +interface QrScanAddressProps { + className?: string; + onError?: (error: Error) => void; + onScan: (scanned: ScanType) => void; + size?: string | number; + style?: React.CSSProperties; +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call +configure({ adapter: new Adapter() }); + +const typeName = async (wrapper: ReactWrapper, value:string) => { + wrapper.find('input').first().simulate('change', { target: { value } }); + await act(flushAllPromises); + wrapper.update(); +}; + +jest.mock('@polkadot/react-qr', () => { + return { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + QrScanAddress: ({ onScan }: QrScanAddressProps): null => { + return null; + } + }; +}); + +describe('ImportQr component', () => { + let wrapper: ReactWrapper; + + beforeEach(async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + wrapper = mount( + + + + ); + + act(() => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + (wrapper.find('QrScanAddress').first().prop('onScan') as unknown as QrScanAddressProps['onScan'])(mockedAccount); + }); + await act(flushAllPromises); + wrapper.update(); + }); + + describe('Address component', () => { + it('shows account as external', () => { + expect(wrapper.find('Name').find('FontAwesomeIcon [data-icon="external-link-square-alt"]').exists()).toBe(true); + }); + + it('shows the correct name', () => { + expect(wrapper.find('Name span').text()).toEqual(mockedAccount.name); + }); + + it('shows the correct address', () => { + expect(wrapper.find('[data-field="address"]').text()).toEqual(mockedAccount.content); + }); + + it('shows the correct banner', () => { + expect(wrapper.find('[data-field="chain"]').text()).toEqual(mockedAccount.expectedBannerChain); + }); + }); + + it('has the button enabled', () => { + expect(wrapper.find(Button).prop('isDisabled')).toBe(false); + }); + + it('displays and error and the button is disabled with a short name', async () => { + await typeName(wrapper, 'a'); + + expect(wrapper.find('.warning-message').first().text()).toBe('Account name is too short'); + expect(wrapper.find(Button).prop('isDisabled')).toBe(true); + }); + + it('has no error message and button enabled with a long name', async () => { + const longName = 'aaa'; + + await typeName(wrapper, 'a'); + await typeName(wrapper, longName); + + expect(wrapper.find('.warning-message')).toHaveLength(0); + expect(wrapper.find(Button).prop('isDisabled')).toBe(false); + expect(wrapper.find('Name span').text()).toEqual(longName); + }); + + it('shows the external name in the input field', () => { + expect(wrapper.find('input').prop('value')).toBe(mockedAccount.name); + }); + + it('creates the external account', async () => { + jest.spyOn(messaging, 'createAccountExternal').mockResolvedValue(false); + wrapper.find(Button).simulate('click'); + await act(flushAllPromises); + + expect(messaging.createAccountExternal).toHaveBeenCalledWith(mockedAccount.name, mockedAccount.content, mockedAccount.genesisHash); + }); +}); diff --git a/packages/extension-ui/src/Popup/ImportQr.tsx b/packages/extension-ui/src/Popup/ImportQr.tsx index 88169a97..4d507210 100644 --- a/packages/extension-ui/src/Popup/ImportQr.tsx +++ b/packages/extension-ui/src/Popup/ImportQr.tsx @@ -62,15 +62,14 @@ export default function ImportQr (): React.ReactElement { value={name || ''} /> - {name && ( - - - {t('Add the account with identified address')} - - - )} + + + {t('Add the account with identified address')} + + )}