diff --git a/.enzyme/lingui-helper.js b/.enzyme/lingui-helper.js new file mode 100644 index 000000000..6c21dec03 --- /dev/null +++ b/.enzyme/lingui-helper.js @@ -0,0 +1,67 @@ +import React from 'react' +import { shape, object } from 'prop-types' +import { mount, shallow } from 'enzyme' +import { I18nProvider } from '@lingui/react' + +// Create the I18nProvider to retrieve context for wrapping around. +const language = 'en' +const intlProvider = new I18nProvider({ + language, + catalogs: { + [language]: {} + } +}, {}) + +const { + linguiPublisher: { + i18n: originalI18n + } +} = intlProvider.getChildContext() + +// You customize the i18n object here: +const i18n = { + ...originalI18n, + _: key => key // provide _ macro, for just passing down the key +} + +/** + * When using Lingui `withI18n` on components, props.i18n is required. + */ +function nodeWithI18nProp(node) { + return React.cloneElement(node, { i18n }) +} + +/* + * Methods to use + */ +export function shallowWithIntl(node, { context } = {}) { + return shallow( + nodeWithI18nProp(node), + { + context: Object.assign({}, context, { i18n }) + } + ) +} + +export function mountWithIntl(node, { context, childContextTypes } = {}) { + const newContext = Object.assign({}, context, { linguiPublisher: { i18n } }) + /* + * I18nProvider sets the linguiPublisher in the context for withI18n to get + * the i18n object from. + */ + const newChildContextTypes = Object.assign({}, + { + linguiPublisher: shape({ + i18n: object.isRequired + }).isRequired + }, + childContextTypes + ) + return mount( + nodeWithI18nProp(node), + { + context: newContext, + childContextTypes: newChildContextTypes + } + ) +} diff --git a/.idea/webResources.xml b/.idea/webResources.xml index cbe20b27a..6573785ef 100644 --- a/.idea/webResources.xml +++ b/.idea/webResources.xml @@ -7,6 +7,7 @@ + diff --git a/.jest/register-context.js b/.jest/register-context.js index b0e24f936..f3caf5daa 100644 --- a/.jest/register-context.js +++ b/.jest/register-context.js @@ -8,3 +8,40 @@ jest.mock('../src/utils/sprite/spriteUI', () => ({ jest.mock('../src/utils/sprite/spriteAssets', () => ({ keys: () => [], })) + +jest.mock('../src/utils/sprite/coloredIconsUI', () => ({ + keys: () => [], +})) + +jest.mock('../src/public/assets/pic_transactions_112.svg', () => 'pic_transactions_112.svg') + +jest.mock('i18n/lingui', () => ({ + i18n: { + _: ( + key, + params, + data, + ) => { + if (!data) { + return key + } + + const source = data.defaults + + if (!params) { + return source + } + + return Object.keys(params).reduce(( + result, + param, + ) => { + if (result.indexOf(param) === -1) { + return result + } + + return result.replace(`{${param}}`, params[param]) + }, source) + } + } +})) diff --git a/Jenkinsfile b/Jenkinsfile index bf90008ce..b7c7bb4e4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,5 +1,31 @@ builder( + jUnitReportsPath: 'reports/unit-tests', + coverageReportsPath: 'reports/coverage', buildTasks: [ + [ + name: 'Linters', + type: 'lint', + method: 'inside', + buildStage: 'build', + command: [ + // 'npm run lint:css', // TODO: enable CSS linting + 'npm run lint:js', + ], + ], + [ + name: 'Tests', + type: 'test', + method: 'inside', + buildStage: 'build', + jUnitPath: '/usr/src/app/reports/unit-tests', + coveragePath: '/usr/src/app/reports/coverage', + environment: [ + NODE_ENV: 'production', + ], + command: [ + 'npm run test:unit', + ], + ], [ name: 'Nginx Checks', type: 'test', diff --git a/jest.config.js b/jest.config.js index 7af063724..e85218795 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,7 +11,7 @@ module.exports = { statements: 0, }, }, - coverageDirectory: '/reports/unit-tests/coverage', + coverageDirectory: '/reports/coverage', coverageReporters: [ 'lcov', ], @@ -25,7 +25,7 @@ module.exports = { '[/\\\\]node_modules[/\\\\](?!lodash-es).+\\.(js|jsx)$', ], moduleFileExtensions: ['js', 'jsx'], - moduleDirectories: ['src', 'node_modules'], + moduleDirectories: ['', 'src', 'node_modules'], moduleNameMapper: { '\\.(css|scss)$': 'identity-obj-proxy', '^lodash-es$': 'lodash', diff --git a/package-lock.json b/package-lock.json index 2bccc79e7..28bf0308f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3218,9 +3218,9 @@ } }, "@types/jasmine": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.4.5.tgz", - "integrity": "sha512-ljf19razYUgsBv5ofh6oqsd5KMM2Q7A/s2yKI+89v6PFr9jrTGLIIr1P4aR7g3J79s89fC61TX+bjqq+4jxFdQ==", + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.4.6.tgz", + "integrity": "sha512-hpQHs+lmZ0uuCrGyqypdI1Ho7jRFolOBT6OkNdZPFziLSSEKvWu+VxWU6bGdNEA/hoV4jV8pdDeNx8EWlmfNAw==", "dev": true }, "@types/minimatch": { @@ -3230,9 +3230,9 @@ "dev": true }, "@types/node": { - "version": "10.17.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.3.tgz", - "integrity": "sha512-QZ9CjUB3QoA3f2afw3utKlfRPhpmufB7jC2+oDhLWnXqoyx333fhKSQDLQu2EK7OE0a15X67eYiRAaJsHXrpMA==" + "version": "10.17.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.4.tgz", + "integrity": "sha512-F2pgg+LcIr/elguz+x+fdBX5KeZXGUOp7TV8M0TVIrDezYLFRNt8oMTyps0VQ1kj5WGGoR18RdxnRDHXrIFHMQ==" }, "@types/prop-types": { "version": "15.7.3", @@ -3440,15 +3440,15 @@ "dev": true }, "webdriverio": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-5.15.6.tgz", - "integrity": "sha512-pYRmJ5+a0oJxP0SEKAvpO5JZeUDT+5PlEXWG9inP4SlvQ4hrl963Pt5VOQqRxnBRPzZMFAeZYz34yHAZp0Ip4Q==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-5.16.0.tgz", + "integrity": "sha512-dqSLAZ9rQEldU3Hm+eet/DOceluYroN6/zghUa129+tofvDh2gfxuJ+O0eYTsj1/HOJLffM+TLIqfeaklCQ4oQ==", "dev": true, "requires": { - "@wdio/config": "5.15.3", + "@wdio/config": "5.16.0", "@wdio/logger": "5.13.2", - "@wdio/repl": "5.15.1", - "@wdio/utils": "5.15.1", + "@wdio/repl": "5.16.0", + "@wdio/utils": "5.16.0", "archiver": "^3.0.0", "css-value": "^0.0.1", "grapheme-splitter": "^1.0.2", @@ -3459,7 +3459,7 @@ "resq": "^1.6.0", "rgb2hex": "^0.1.0", "serialize-error": "^5.0.0", - "webdriver": "5.15.6" + "webdriver": "5.16.0" } }, "y18n": { @@ -3500,9 +3500,9 @@ } }, "@wdio/config": { - "version": "5.15.3", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-5.15.3.tgz", - "integrity": "sha512-FE7AOphggjyZvFb9EfT9KRm5BwTkzyGe22tNczS2ntDbexNqs73mHw59cd8Fw7rgM9a1ruF20DmrIIwIeeFEZg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-5.16.0.tgz", + "integrity": "sha512-jEcCci6oDJ2FONcvB6DZMnrG+P9kG2QIvDkfn0JY0JH3RNcpcnFZnFi6NXw76wWVsj753KLua3qKlwTeHkeMcw==", "dev": true, "requires": { "@wdio/logger": "5.13.2", @@ -3595,18 +3595,18 @@ } }, "@wdio/protocols": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-5.15.6.tgz", - "integrity": "sha512-5yDZmONUZTAWJaRYFHdKA6m3bEMwP0E8tApY/zlWagCwlSMpX+wsAJBsGjrIpblq9D+XWCww6mKg2kV/kXIwNg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-5.16.0.tgz", + "integrity": "sha512-3tlnDtE70/tKjHOrIM9h7P4/i5ui/sK5d4CN4fsF3t8JnodaWueQ2BhH9lD2VMJQsvLiaPLuS3aPfdc7vYKc2Q==", "dev": true }, "@wdio/repl": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-5.15.1.tgz", - "integrity": "sha512-lBDgKEj0TyuuhUpNaPA78e2kUfC9MkuRNEFW/n78vw4E0RSXsviKYABqAXcah+GDfTt17qR+cI8mQ89GYR+Acg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-5.16.0.tgz", + "integrity": "sha512-aUccCN5ZCasMqx8uNwxqw3ubejvor2GTHd2WK03bUIZYYzp//33H2YrWhy5uoEuNkk/giTWiQbtkiAJ2d1rOkg==", "dev": true, "requires": { - "@wdio/utils": "5.15.1" + "@wdio/utils": "5.16.0" } }, "@wdio/reporter": { @@ -3641,17 +3641,17 @@ } }, "@wdio/runner": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-5.15.6.tgz", - "integrity": "sha512-ecSOavuxQu3t7aOYRf3iHytkPBMpLGcpIITIwHzG14Gkyg2lb00TOBjCFbPbJ87+cKONZIH+47ZRakwKslxtyw==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-5.16.0.tgz", + "integrity": "sha512-DGY8yLfUSPV+ldoIZ1NO3UjC9l+6ZU17rrMbcDcGUF3VYXRDExKXcJTpiOlQuns1WsRMoX5qjZq65ze0hQqxxQ==", "dev": true, "requires": { - "@wdio/config": "5.15.3", + "@wdio/config": "5.16.0", "@wdio/logger": "5.13.2", - "@wdio/utils": "5.15.1", + "@wdio/utils": "5.16.0", "deepmerge": "^4.0.0", "gaze": "^1.1.2", - "webdriverio": "5.15.6" + "webdriverio": "5.16.0" }, "dependencies": { "deepmerge": { @@ -3661,15 +3661,15 @@ "dev": true }, "webdriverio": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-5.15.6.tgz", - "integrity": "sha512-pYRmJ5+a0oJxP0SEKAvpO5JZeUDT+5PlEXWG9inP4SlvQ4hrl963Pt5VOQqRxnBRPzZMFAeZYz34yHAZp0Ip4Q==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-5.16.0.tgz", + "integrity": "sha512-dqSLAZ9rQEldU3Hm+eet/DOceluYroN6/zghUa129+tofvDh2gfxuJ+O0eYTsj1/HOJLffM+TLIqfeaklCQ4oQ==", "dev": true, "requires": { - "@wdio/config": "5.15.3", + "@wdio/config": "5.16.0", "@wdio/logger": "5.13.2", - "@wdio/repl": "5.15.1", - "@wdio/utils": "5.15.1", + "@wdio/repl": "5.16.0", + "@wdio/utils": "5.16.0", "archiver": "^3.0.0", "css-value": "^0.0.1", "grapheme-splitter": "^1.0.2", @@ -3680,7 +3680,7 @@ "resq": "^1.6.0", "rgb2hex": "^0.1.0", "serialize-error": "^5.0.0", - "webdriver": "5.15.6" + "webdriver": "5.16.0" } } } @@ -3697,9 +3697,9 @@ } }, "@wdio/utils": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-5.15.1.tgz", - "integrity": "sha512-mbLcKqjRyryUuMk4n9sHWokhop82GcrVHh6GAyMFJgCG85zBel5+qmZkHHO4hqfUrxUg/J9XyQio2r1eUw0Fzg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-5.16.0.tgz", + "integrity": "sha512-cBXPgKM+Cf3V5pyDE7e5OmeCnFi3T04so71YMAu5nPrPTsa06qAsZKN7+XA4NjANCsuv0OxRgzKJuoXChkYs5Q==", "dev": true, "requires": { "@wdio/logger": "5.13.2", @@ -6903,9 +6903,9 @@ "dev": true }, "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -8319,9 +8319,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.303", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.303.tgz", - "integrity": "sha512-xDFPmMjJ0gQBsVwspB0bjcbFn3MVcvU0sxXYmh1UMbZ6rDogQVM3vSyOvTO4rym1KlnJIU6nqzK3qs0yKudmjw==", + "version": "1.3.304", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.304.tgz", + "integrity": "sha512-a5mqa13jCdBc+Crgk3Gyr7vpXCiFWfFq23YDCEmrPYeiDOQKZDVE6EX/Q4Xdv97n3XkcjiSBDOY0IS19yP2yeA==", "dev": true }, "elegant-spinner": { @@ -8467,18 +8467,20 @@ } }, "enzyme-adapter-react-16": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.11.2.tgz", - "integrity": "sha512-2ruTTCPRb0lPuw/vKTXGVZVBZqh83MNDnakMhzxhpJcIbneEwNy2Cv0KvL97pl57/GOazJHflWNLjwWhex5AAA==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.1.tgz", + "integrity": "sha512-yMPxrP3vjJP+4wL/qqfkT6JAIctcwKF+zXO6utlGPgUJT2l4tzrdjMDWGd/Pp1BjHBcljhN24OzNEGRteibJhA==", "dev": true, "requires": { - "enzyme-adapter-utils": "^1.10.1", + "enzyme-adapter-utils": "^1.12.1", + "enzyme-shallow-equal": "^1.0.0", + "has": "^1.0.3", "object.assign": "^4.1.0", "object.values": "^1.1.0", "prop-types": "^15.7.2", - "react-is": "^16.8.4", + "react-is": "^16.10.2", "react-test-renderer": "^16.0.0-0", - "semver": "^5.6.0" + "semver": "^5.7.0" } }, "enzyme-adapter-utils": { @@ -8505,6 +8507,16 @@ "deep-equal-ident": "^1.1.1" } }, + "enzyme-shallow-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.0.tgz", + "integrity": "sha512-VUf+q5o1EIv2ZaloNQQtWCJM9gpeux6vudGVH6vLmfPXFLRuxl5+Aq3U260wof9nn0b0i+P5OEUXm1vnxkRpXQ==", + "dev": true, + "requires": { + "has": "^1.0.3", + "object-is": "^1.0.1" + } + }, "enzyme-to-json": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.4.3.tgz", @@ -23490,15 +23502,15 @@ } }, "webdriver": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-5.15.6.tgz", - "integrity": "sha512-fYbH/l9nTR/boSjueBa7bxtdmDC61TBjYTpyP2py9MGOPkyVdn4K55tRhSqBmeWZBjK+YcyrN8L8id+S1kF8KA==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-5.16.0.tgz", + "integrity": "sha512-AQHhS1pfSpe+5zPm36HvldY9Bl2VXolrxPb4i5hoTUlJQzT9/j5mUewHxi7gNf9cY+mOtK273Y9jBfNEbObeTg==", "dev": true, "requires": { - "@wdio/config": "5.15.3", + "@wdio/config": "5.16.0", "@wdio/logger": "5.13.2", - "@wdio/protocols": "5.15.6", - "@wdio/utils": "5.15.1", + "@wdio/protocols": "5.16.0", + "@wdio/utils": "5.16.0", "lodash.merge": "^4.6.1", "request": "^2.83.0" } diff --git a/package.json b/package.json index 2d3c4b6a9..0a5bb86bf 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "css-loader": "1.0.1", "env-cmd": "8.0.2", "enzyme": "3.9.0", - "enzyme-adapter-react-16": "1.11.2", + "enzyme-adapter-react-16": "1.15.1", "eslint": "5.16.0", "eslint-config-airbnb": "17.1.0", "eslint-loader": "2.1.2", @@ -155,6 +155,7 @@ "postcss-flexbugs-fixes": "4.1.0", "postcss-loader": "3.0.0", "prettier": "1.17.0", + "prop-types": "15.7.2", "react-app-polyfill": "0.2.2", "react-dev-utils": "8.0.0", "react-error-overlay": "1.0.10", diff --git a/src/components/CopyIconButton/CopyIconButton.js b/src/components/CopyIconButton/CopyIconButton.js index 80660371e..4645ff70e 100644 --- a/src/components/CopyIconButton/CopyIconButton.js +++ b/src/components/CopyIconButton/CopyIconButton.js @@ -2,23 +2,23 @@ import React, { PureComponent } from 'react' import { withI18n } from '@lingui/react' -import { type I18n as I18nType } from '@lingui/core' +import { type I18n } from '@lingui/core' -import { clipboard } from 'services' +import clipboard from 'services/clipboard' +import toastsPlugin from 'store/plugins/toastsPlugin' import { JIcon } from 'components/base' -import { toastsPlugin } from 'store/plugins' import { gaSendEvent } from 'utils/analytics' -import copyIconButtonStyle from './copyIconButton.m.scss' +import styles from './copyIconButton.m.scss' type Props = {| - +i18n: I18nType, + +i18n: I18n, +title: ?string, +content: string, +toastMessage: ?string, |} -class CopyIconButton extends PureComponent { +export default class CopyIconButton extends PureComponent { static defaultProps = { title: null, toastMessage: null, @@ -59,7 +59,7 @@ class CopyIconButton extends PureComponent { { content }, { defaults: 'Copy {content}' }, )} - className={`__copy-icon-button ${copyIconButtonStyle.core}`} + className={`__copy-icon-button ${styles.core}`} type='button' > @@ -68,5 +68,4 @@ class CopyIconButton extends PureComponent { } } -const CopyIconButtonEnhanced = withI18n()(CopyIconButton) -export { CopyIconButtonEnhanced as CopyIconButton } +export const CopyIconButtonEnhanced = withI18n()(CopyIconButton) diff --git a/src/components/CopyIconButton/index.js b/src/components/CopyIconButton/index.js new file mode 100644 index 000000000..c82f619d5 --- /dev/null +++ b/src/components/CopyIconButton/index.js @@ -0,0 +1,5 @@ +// @flow strict + +import { CopyIconButtonEnhanced } from './CopyIconButton' + +export default CopyIconButtonEnhanced diff --git a/src/components/FieldPreview/FieldPreview.js b/src/components/FieldPreview/FieldPreview.js index c1cd96357..20115ead9 100644 --- a/src/components/FieldPreview/FieldPreview.js +++ b/src/components/FieldPreview/FieldPreview.js @@ -1,9 +1,10 @@ // @flow strict import React from 'react' +import { type I18n } from '@lingui/core' +import CopyIconButton from 'components/CopyIconButton' import { useI18n } from 'app/hooks' -import { CopyIconButton } from 'components' import { JLink, @@ -24,7 +25,7 @@ type Props = {| +isCopyable: boolean, |} -export function FieldPreview({ +export default function FieldPreview({ link, label, title, @@ -33,7 +34,7 @@ export function FieldPreview({ isContact, isCopyable, }: Props) { - const i18n = useI18n() + const i18n: I18n = useI18n() return (
@@ -44,8 +45,8 @@ export function FieldPreview({
{link ? ( {valueToShow || value} @@ -71,8 +72,8 @@ export function FieldPreview({ className={styles.icon} name='add-contact-use-fill' /> - ) - } + + )} {isCopyable && ( { test('Label and body value', () => { - const props = { - label: 'Test label', - body: 'Some value', - } - const wrapper = shallow() - - expect(wrapper.find('.label').text()).toBe(props.label) - expect(wrapper.find('.body').text()).toBe(props.body) + const wrapper = shallow( + , + ) + + expect(wrapper.find('.label').text()).toBe(LABEL) + expect(wrapper.find('.value').text()).toBe(VALUE) }) test('Body with link', () => { - const props = { - label: 'Test label', - body: 'Some value', - link: 'https://jibrel.network', - } - const wrapper = mount() - - expect(wrapper.find('.label').text()).toBe(props.label) - expect(wrapper.find('.body').children().hasClass('link')).toBe(true) - expect(wrapper.find('.body > .link').text()).toBe(props.body) - expect(wrapper.find('.body > .link').getDOMNode().getAttribute('href')).toBe(props.link) + const wrapper = mount( + , + ) + + expect(wrapper.find('.label').text()).toBe(LABEL) + expect(wrapper.find('.value > .link').text()).toBe(VALUE) + expect(wrapper.find('.value').children().hasClass('link')).toBe(true) + expect(wrapper.find('.value > .link').getDOMNode().getAttribute('href')).toBe(LINK) }) test('Copy button is rendered', () => { - const props = { - label: 'Test label', - body: 'Some value', - link: 'https://jibrel.network', - copy: 'copy text', - } - const wrapper = shallow() + const wrapper = shallow( + , + ) expect(wrapper.find('.actions').children()).toHaveLength(1) }) test('Add Contact button is rendered', () => { - const props = { - label: 'Test label', - body: 'Some value', - link: 'https://jibrel.network', - contact: 'contact', - } - const wrapper = shallow() + const wrapper = shallow( + , + ) expect(wrapper.find('.actions').children()).toHaveLength(1) }) test('Both actions is rendered', () => { - const props = { - label: 'Test label', - body: 'Some value', - link: 'https://jibrel.network', - copy: 'haha', - contact: 'contact', - } - const wrapper = shallow() - - expect(wrapper.find('.actions > .action')).toHaveLength(2) + const wrapper = shallow( + , + ) + + expect(wrapper.find('.actions').children()).toHaveLength(2) }) test('Applying copy message', () => { - const props = { - label: 'Test label', - body: 'Some value', - link: 'https://jibrel.network', - copy: 'haha', - contact: 'contact', - copyMessage: 'Copymassage :D', - } - const wrapper = shallow() - - expect(wrapper.find('.actions > .action')).toHaveLength(2) + const wrapper = shallow( + , + ) + + expect(wrapper.find('.actions').children()).toHaveLength(2) + expect(wrapper.find('.value > .link').prop('children')).toBe(VALUE_TO_SHOW) }) }) diff --git a/src/components/NewPasswordField/NewPasswordField.js b/src/components/NewPasswordField/NewPasswordField.js index 51281bc77..425a3d3db 100644 --- a/src/components/NewPasswordField/NewPasswordField.js +++ b/src/components/NewPasswordField/NewPasswordField.js @@ -1,10 +1,10 @@ -// @flow +// @flow strict import React, { Component } from 'react' -import { withI18n } from '@lingui/react' import { Field } from 'react-final-form' -import { type I18n as I18nType } from '@lingui/core' +import { type I18n } from '@lingui/core' +import { useI18n } from 'app/hooks' import { PasswordInput } from 'components' import { checkPasswordStrength } from 'utils/encryption' @@ -13,16 +13,17 @@ import { type IndicatorStatus, } from './components/Indicator' -import newPasswordFieldStyle from './newPasswordField.m.scss' +import styles from './newPasswordField.m.scss' +import { STATUS_MESSAGE_MAP } from './statusMessageMap' type Props = {| +onChange: FormFieldChange, +onScoreChange: (boolean) => void, +values: FormFields, +label: string, + +labelConfirm: string, +isDisabled: boolean, +isAutoFocus: boolean, - +i18n: I18nType, |} type StateProps = {| @@ -61,7 +62,7 @@ function checkStrong(score: number): boolean { return (score >= MIN_PASSWORD_STRENGTH_SCORE) } -class NewPasswordFieldComponent extends Component { +export class NewPasswordFieldView extends Component { static defaultProps = { isDisabled: false, isAutoFocus: false, @@ -107,31 +108,6 @@ class NewPasswordFieldComponent extends Component { return null } - const { i18n } = this.props - - const STATUS_MESSAGE_MAP: { [IndicatorStatus]: ?string } = { - 'red': i18n._( - 'common.NewPasswordField.strength.red', - null, - { defaults: 'Too weak' }, - ), - 'green': i18n._( - 'common.NewPasswordField.strength.green', - null, - { defaults: 'Not bad' }, - ), - 'yellow': i18n._( - 'common.NewPasswordField.strength.yellow', - null, - { defaults: 'Bit weak' }, - ), - 'orange': i18n._( - 'common.NewPasswordField.strength.orange', - null, - { defaults: 'Easily cracked' }, - ), - } - const { passwordResult, isFetching, @@ -185,9 +161,9 @@ class NewPasswordFieldComponent extends Component { const { values, label, + labelConfirm, isDisabled, isAutoFocus, - i18n, }: Props = this.props const { @@ -202,15 +178,15 @@ class NewPasswordFieldComponent extends Component { const errorMessage: ?string = isStrong ? null : infoMessage return ( -
+
{ {!isDisabled && } { } } -export const NewPasswordField = withI18n()(NewPasswordFieldComponent) +export function NewPasswordField(props: Props) { + const i18n: I18n = useI18n() + + return ( + + ) +} diff --git a/src/components/NewPasswordField/statusMessageMap.js b/src/components/NewPasswordField/statusMessageMap.js new file mode 100644 index 000000000..df760b536 --- /dev/null +++ b/src/components/NewPasswordField/statusMessageMap.js @@ -0,0 +1,28 @@ +// @flow strict + +import { i18n } from 'i18n/lingui' + +import { type IndicatorStatus } from './components/Indicator' + +export const STATUS_MESSAGE_MAP: { [IndicatorStatus]: ?string } = { + red: i18n._( + 'common.NewPasswordField.strength.red', + null, + { defaults: 'Too weak' }, + ), + green: i18n._( + 'common.NewPasswordField.strength.green', + null, + { defaults: 'Not bad' }, + ), + yellow: i18n._( + 'common.NewPasswordField.strength.yellow', + null, + { defaults: 'Bit weak' }, + ), + orange: i18n._( + 'common.NewPasswordField.strength.orange', + null, + { defaults: 'Easily cracked' }, + ), +} diff --git a/src/components/NewPasswordField/tests/NewPasswordField.test.js b/src/components/NewPasswordField/tests/NewPasswordField.test.js index fd4f8d2d7..016ddc8f8 100644 --- a/src/components/NewPasswordField/tests/NewPasswordField.test.js +++ b/src/components/NewPasswordField/tests/NewPasswordField.test.js @@ -56,7 +56,7 @@ jest.mock('../../../workers/scrypt/worker', () => class MOCK_WORKER { }) // eslint-disable-next-line import/first -import { NewPasswordField } from '../NewPasswordField' +import { NewPasswordFieldView as NewPasswordField } from '../NewPasswordField' describe('NewPasswordField', () => { test('is available', () => { @@ -79,6 +79,7 @@ describe('NewPasswordField', () => { onScoreChange={handleScoreChange} values={values} label='Label' + labelConfirm='Label Confirm' />, ) @@ -88,6 +89,7 @@ describe('NewPasswordField', () => { expect(componentInstance.props.onScoreChange).toBe(handleScoreChange) expect(componentInstance.props.values).toBe(values) expect(componentInstance.props.label).toBe('Label') + expect(componentInstance.props.labelConfirm).toBe('Label Confirm') expect(componentInstance.props.isDisabled).toBe(false) expect(componentInstance.props.isAutoFocus).toBe(false) @@ -104,7 +106,7 @@ describe('NewPasswordField', () => { expect(passwordInput.prop('name')).toBe('password') expect(passwordInput.prop('infoMessage')).toBe(null) expect(passwordInput.prop('errorMessage')).toBe(null) - expect(passwordInput.prop('label')).toBe('Label') + expect(passwordInput.prop('labelConfirm')).toBe() expect(passwordInput.prop('theme')).toBe('white-indicator') expect(passwordInput.prop('isDisabled')).toBe(false) expect(passwordInput.prop('isAutoFocus')).toBe(false) @@ -118,7 +120,7 @@ describe('NewPasswordField', () => { expect(passwordConfirmInput.prop('value')).toBe('') expect(passwordConfirmInput.prop('theme')).toBe('white-icon') expect(passwordConfirmInput.prop('name')).toBe('passwordConfirm') - expect(passwordConfirmInput.prop('label')).toBe('Repeat Security Password') + expect(passwordConfirmInput.prop('label')).toBe('Label Confirm') expect(passwordConfirmInput.prop('isDisabled')).toBe(false) // $FlowFixMe diff --git a/src/components/SearchInput/tests/SearchFilter.test.js b/src/components/SearchInput/tests/SearchFilter.test.js index 6b8afcff7..89a6de462 100644 --- a/src/components/SearchInput/tests/SearchFilter.test.js +++ b/src/components/SearchInput/tests/SearchFilter.test.js @@ -1,7 +1,7 @@ import React from 'react' import { shallow } from 'enzyme' -import { SearchFilter } from '../SearchFilter/SearchFilter.js' +import { SearchFilter } from '../../SearchFilter/SearchFilter.js' describe('SearchFilter', () => { it('is available', () => { @@ -14,18 +14,10 @@ describe('SearchFilter', () => { expect(wrapper.exists('em')).toBe(false) }) - it('does not display count if it is 0 or less than 0', () => { + it('does not display count if it is 0', () => { expect( shallow(test).exists('em'), ).toBe(false) - - expect( - shallow(test).exists('em'), - ).toBe(false) - - expect( - shallow(test).exists('em'), - ).toBe(false) }) it('displays count if it is more than 0', () => { diff --git a/src/components/base/JAssetSymbol/JAssetSymbol.js b/src/components/base/JAssetSymbol/JAssetSymbol.js index 6b13d2c72..8779faa7b 100644 --- a/src/components/base/JAssetSymbol/JAssetSymbol.js +++ b/src/components/base/JAssetSymbol/JAssetSymbol.js @@ -13,12 +13,12 @@ type JAssetSymbolSize = 24 | 32 type Props = {| +symbol: string, - +address?: ?string, - +className?: ?string, + +address: ?string, + +className: ?string, +size: JAssetSymbolSize, |} -export function JAssetSymbol({ +export default function JAssetSymbol({ size, symbol, address, diff --git a/src/components/base/JAssetSymbol/JAssetSymbol.stories.js b/src/components/base/JAssetSymbol/JAssetSymbol.stories.js index 2ef21fcea..1c9802cee 100644 --- a/src/components/base/JAssetSymbol/JAssetSymbol.stories.js +++ b/src/components/base/JAssetSymbol/JAssetSymbol.stories.js @@ -9,8 +9,8 @@ import { withKnobs, } from '@storybook/addon-knobs' +import JAssetSymbol from './JAssetSymbol' import { ADDRESSES_AVAILABLE } from './symbolsAvailable' -import { JAssetSymbol } from './JAssetSymbol' const ADDRESSES_LIST = Object.keys(ADDRESSES_AVAILABLE) diff --git a/src/components/base/JAssetSymbol/index.js b/src/components/base/JAssetSymbol/index.js new file mode 100644 index 000000000..86aa387df --- /dev/null +++ b/src/components/base/JAssetSymbol/index.js @@ -0,0 +1,5 @@ +// @flow strict + +import { JAssetSymbolEnhanced } from './JAssetSymbol' + +export default JAssetSymbolEnhanced diff --git a/src/components/base/JAssetSymbol/tests/JAssetSymbol.test.js b/src/components/base/JAssetSymbol/tests/JAssetSymbol.test.js index b3e2dcd78..4bb830961 100644 --- a/src/components/base/JAssetSymbol/tests/JAssetSymbol.test.js +++ b/src/components/base/JAssetSymbol/tests/JAssetSymbol.test.js @@ -1,12 +1,12 @@ +// @flow strict + import React from 'react' -import { - shallow, -} from 'enzyme' +import { shallow } from 'enzyme' -import { JAssetSymbol } from '../JAssetSymbol' +import JAssetSymbol from '../JAssetSymbol' jest.mock('../../../../utils/sprite/iconsAsset', () => ({ - 'eth-usage': { + 'ETH-usage': { url: '#eth', viewBox: '0 0 24 24', }, @@ -18,14 +18,26 @@ describe('JAssetSymbol', () => { }) it('renders icon if valid address is specified', () => { - const wrapper = shallow() + const wrapper = shallow( + , + ) expect(wrapper.exists('use')).toBe(true) expect(wrapper.exists('text')).toBe(false) }) it('renders symbol icon if no icon is available for address', () => { - const wrapper = shallow() + const wrapper = shallow( + , + ) expect(wrapper.exists('use')).toBe(false) expect(wrapper.exists('text')).toBe(true) @@ -34,43 +46,67 @@ describe('JAssetSymbol', () => { it('crops long symbols', () => { expect( shallow( - , + , ).find('text').text(), ).toBe('1') expect( shallow( - , + , ).find('text').text(), ).toBe('12') expect( shallow( - , + , ).find('text').text(), ).toBe('123') expect( shallow( - , + , ).find('text').text(), ).toBe('1234') expect( shallow( - , + , ).find('text').text(), ).toBe('123') expect( shallow( - , + , ).find('text').text(), ).toBe('123') }) it('adds specified class name', () => { - const wrapper = shallow() + const wrapper = shallow( + , + ) expect(wrapper.hasClass('foo')).toBe(true) }) diff --git a/src/components/base/JInputField/tests/JInputField.test.js b/src/components/base/JInputField/tests/JInputField.test.js index c2b1ff4f6..efaeb7334 100644 --- a/src/components/base/JInputField/tests/JInputField.test.js +++ b/src/components/base/JInputField/tests/JInputField.test.js @@ -28,7 +28,7 @@ describe('JInputField', () => { expect(wrapper.prop('theme')).toBe('white') expect(wrapper.prop('type')).toBe('text') expect(wrapper.prop('isDisabled')).toBe(false) - expect(wrapper.prop('validateType')).toBe('touched') + expect(wrapper.prop('validateType')).toBe('dirtySinceLastSubmit') expect(wrapper.prop('infoMessage')).toBe(null) expect(wrapper.prop('label')).toBe('') expect(wrapper.prop('placeholder')).toBe('') diff --git a/src/components/base/index.js b/src/components/base/index.js index ca1333168..471cafc53 100644 --- a/src/components/base/index.js +++ b/src/components/base/index.js @@ -12,7 +12,6 @@ import JSwitch from './JSwitch' export { Button } from './Button/Button' export { AppLogo } from './AppLogo/AppLogo' -export { JAssetSymbol } from './JAssetSymbol/JAssetSymbol' export { JIcon } from './JIcon/JIcon' export { Header } from './Header/Header' export { JLink } from './JLink/JLink' @@ -25,6 +24,8 @@ export { JFieldMessage } from './JFieldMessage/JFieldMessage' export { JShimmer } from './JShimmer/JShimmer' export { ItemCard } from './ItemCard/ItemCard' +export { default as JAssetSymbol } from './JAssetSymbol' + export { JFlatButton, JInput, diff --git a/src/components/index.js b/src/components/index.js index 6f8fc7c53..7e454c68d 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -16,7 +16,6 @@ export { CopyableField } from './CopyableField/CopyableField' export { EditableField } from './EditableField/EditableField' export { PasswordInput } from './PasswordInput/PasswordInput' export { WalletActions } from './WalletActions/WalletActions' -export { CopyIconButton } from './CopyIconButton/CopyIconButton' export { UserActionInfo } from './UserActionInfo/UserActionInfo' export { CloseableScreen } from './CloseableScreen/CloseableScreen' export { GlobalFormError } from './GlobalFormError/GlobalFormError' @@ -30,12 +29,13 @@ export { TransactionsFilter } from './TransactionsFilter/TransactionsFilter' export { ContactsItemDetails } from './ContactsItemDetails/ContactsItemDetails' export { DigitalAssetAddForm } from './DigitalAssetAddForm/DigitalAssetAddForm' export { DigitalAssetEditForm } from './DigitalAssetEditForm/DigitalAssetEditForm' -export { FieldPreview } from './FieldPreview/FieldPreview' export { WalletAddressItem } from './WalletAddressItem/WalletAddressItem' export { AddressPicker } from './AddressPicker/AddressPicker' export { ConfirmationBody } from './ConfirmationBody/ConfirmationBody' export { default as PopupButton } from './PopupButton' +export { default as FieldPreview } from './FieldPreview' +export { default as CopyIconButton } from './CopyIconButton' export { QRCode, diff --git a/src/pages/Send/PasswordStepForm.js b/src/pages/Send/PasswordStepForm.js index e857de48b..1d6217c9e 100644 --- a/src/pages/Send/PasswordStepForm.js +++ b/src/pages/Send/PasswordStepForm.js @@ -11,8 +11,8 @@ import { type FormRenderProps, } from 'react-final-form' +import walletsPlugin from 'store/plugins/walletsPlugin' import { PasswordForm } from 'components' -import { walletsPlugin } from 'store/plugins/walletsPlugin' import { selectPasswordHint } from 'store/selectors/password' import { selectActiveWalletId } from 'store/selectors/wallets' diff --git a/src/pages/Send/components/SendAmountField/tests/SendAmountField.test.js b/src/pages/Send/components/SendAmountField/tests/SendAmountField.test.js index 0feae8779..c34eb6270 100644 --- a/src/pages/Send/components/SendAmountField/tests/SendAmountField.test.js +++ b/src/pages/Send/components/SendAmountField/tests/SendAmountField.test.js @@ -31,7 +31,7 @@ describe('SendAmountField', () => { expect(wrapper.prop('fiatCurrency')).toBe('USD') expect(wrapper.prop('infoMessage')).toBe('') expect(wrapper.prop('className')).toBe('') - expect(wrapper.prop('label')).toBe('Amount') + expect(wrapper.prop('label')).toBeUndefined() expect(wrapper.prop('meta')).toBeDefined() expect(wrapper.prop('input')).toBeDefined() }) diff --git a/src/pages/WalletsCreate/WalletsCreateView.js b/src/pages/WalletsCreate/WalletsCreateView.js index ca3320b59..525ab36bb 100644 --- a/src/pages/WalletsCreate/WalletsCreateView.js +++ b/src/pages/WalletsCreate/WalletsCreateView.js @@ -5,11 +5,11 @@ import { withI18n } from '@lingui/react' import { type I18n } from '@lingui/core' import ofssetsStyle from 'styles/offsets.m.scss' +import walletsPlugin from 'store/plugins/walletsPlugin' import { type FormApi } from 'final-form' import { gaSendEvent } from 'utils/analytics' import { checkNameExists } from 'utils/wallets' import { generateMnemonic } from 'utils/mnemonic' -import { walletsPlugin } from 'store/plugins/walletsPlugin' import { Form, diff --git a/src/pages/WalletsImport/WalletsImportView.js b/src/pages/WalletsImport/WalletsImportView.js index 90931b8fc..8eaf0d259 100644 --- a/src/pages/WalletsImport/WalletsImportView.js +++ b/src/pages/WalletsImport/WalletsImportView.js @@ -11,8 +11,8 @@ import { } from 'react-final-form' import ofssetsStyle from 'styles/offsets.m.scss' +import walletsPlugin from 'store/plugins/walletsPlugin' import { gaSendEvent } from 'utils/analytics' -import { walletsPlugin } from 'store/plugins/walletsPlugin' import { getTypeByInput, diff --git a/src/pages/WalletsStart/components/NewWalletButtons/tests/NewWalletButtons.test.js b/src/pages/WalletsStart/components/NewWalletButtons/tests/NewWalletButtons.test.js index 026abc46d..11fe6edfe 100644 --- a/src/pages/WalletsStart/components/NewWalletButtons/tests/NewWalletButtons.test.js +++ b/src/pages/WalletsStart/components/NewWalletButtons/tests/NewWalletButtons.test.js @@ -2,12 +2,11 @@ import React from 'react' import sinon from 'sinon' -import { shallow } from 'enzyme' +import { mountWithIntl } from '.enzyme/lingui-helper.js' import { ACTIONS } from 'pages/WalletsStart/constants' import { NewWalletButtons } from '../NewWalletButtons' -import newWalletButtonsStyle from '../newWalletButtons.m.scss' const MOCK_EVENT = { preventDefault: () => {} } @@ -19,14 +18,16 @@ describe('NewWalletButtons', () => { test('sends valid action through onClick property', () => { const handleClick = sinon.spy() - const wrapper = shallow() + const wrapper = mountWithIntl() - wrapper.find(`.${newWalletButtonsStyle.create}`).simulate('click', MOCK_EVENT) + expect(wrapper.find('a.__create-button')).toHaveLength(1) + wrapper.find('a.__create-button').simulate('click', MOCK_EVENT) expect(handleClick).toHaveProperty('callCount', 1) expect(handleClick.calledWith(ACTIONS.CREATE)).toBe(true) - wrapper.find(`.${newWalletButtonsStyle.import}`).simulate('click', MOCK_EVENT) + expect(wrapper.find('a.__import-button')).toHaveLength(1) + wrapper.find('a.__import-button').simulate('click', MOCK_EVENT) expect(handleClick).toHaveProperty('callCount', 2) expect(handleClick.calledWith(ACTIONS.IMPORT)).toBe(true) diff --git a/src/store/plugins/index.js b/src/store/plugins/index.js index d2475c7ff..3e84a7747 100644 --- a/src/store/plugins/index.js +++ b/src/store/plugins/index.js @@ -1,4 +1,4 @@ // @flow strict -export { toastsPlugin } from './toastsPlugin' -export { walletsPlugin } from './walletsPlugin' +export { default as toastsPlugin } from './toastsPlugin' +export { default as walletsPlugin } from './walletsPlugin' diff --git a/src/store/plugins/toastsPlugin.js b/src/store/plugins/toastsPlugin.js index efd74b721..31873e2c0 100644 --- a/src/store/plugins/toastsPlugin.js +++ b/src/store/plugins/toastsPlugin.js @@ -95,4 +95,5 @@ class ToastsPlugin { } } -export const toastsPlugin = new ToastsPlugin() +const toastsPlugin = new ToastsPlugin() +export default toastsPlugin diff --git a/src/store/plugins/walletsPlugin.js b/src/store/plugins/walletsPlugin.js index 754a96fd6..e80f93fdf 100644 --- a/src/store/plugins/walletsPlugin.js +++ b/src/store/plugins/walletsPlugin.js @@ -613,4 +613,5 @@ class WalletsPlugin { } } -export const walletsPlugin = new WalletsPlugin() +const walletsPlugin = new WalletsPlugin() +export default walletsPlugin diff --git a/src/utils/address/tests/getAddressName.test.js b/src/utils/address/tests/getAddressName.test.js index 18ed041f2..dc35ef0e9 100644 --- a/src/utils/address/tests/getAddressName.test.js +++ b/src/utils/address/tests/getAddressName.test.js @@ -1,4 +1,4 @@ -// @flow +// @flow strict import { getAddressName } from '..' @@ -8,12 +8,30 @@ describe('address getAddressName', () => { }) it('it works with name and index', () => { - const withName = getAddressName('My Address', 1) - expect(withName).toBe('My Address') + const result: string = getAddressName( + 'My Address', + 1, + ) + + expect(result).toBe('My Address') }) - it('it works without name and index', () => { - const withName = getAddressName(null, 1) - expect(withName).toBe('Address 2') + it('it works without name', () => { + const result: string = getAddressName( + null, + 1, + ) + + expect(result).toBe('Address 2') + }) + + it('it works with wallet name', () => { + const result: string = getAddressName( + 'My Address', + 99, + 'My Wallet', + ) + + expect(result).toBe('My Wallet / My Address') }) })