diff --git a/.babelrc b/.babelrc deleted file mode 100644 index dcad53ff..00000000 --- a/.babelrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "presets": [ - "@babel/preset-typescript", - "@babel/preset-react", - [ - "@babel/preset-env", - { - "useBuiltIns": "entry", - "corejs": 3 - } - ] - ], - "plugins": [ - "react-hot-loader/babel", - "@babel/plugin-proposal-class-properties" - ] -} diff --git a/.travis.yml b/.travis.yml index 6d9d9082..3951087a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,11 @@ language: node_js +addons: + apt: + packages: + - libusb-1.0-0-dev + - libudev-dev + cache: yarn: true directories: @@ -16,6 +22,7 @@ script: - yarn run prettier:diff - yarn run tslint - yarn run tscheck + - yarn run test before_deploy: - yarn run build diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 00000000..9c2db66a --- /dev/null +++ b/babel.config.js @@ -0,0 +1,14 @@ +module.exports = { + presets: [ + '@babel/preset-typescript', + '@babel/preset-react', + [ + '@babel/preset-env', + { + useBuiltIns: 'entry', + corejs: 3 + } + ] + ], + plugins: ['react-hot-loader/babel', '@babel/plugin-proposal-class-properties'] +}; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..b476cf98 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + roots: ['src/'], + setupFilesAfterEnv: ['./jest/setupTests.js'], + transformIgnorePatterns: ['/node_modules/(?!@mycrypto/ui/)'], + snapshotSerializers: ['enzyme-to-json/serializer'], + snapshotResolver: './jest/snapshotResolver.js', + moduleNameMapper: { + '\\.svg$': '/jest/__mocks__/fileMock.ts' + } +}; diff --git a/jest/__mocks__/fileMock.ts b/jest/__mocks__/fileMock.ts new file mode 100644 index 00000000..602eb23e --- /dev/null +++ b/jest/__mocks__/fileMock.ts @@ -0,0 +1 @@ +export default 'test-file-stub'; diff --git a/jest/__snapshots__/components/ui/Address/Address.test.tsx.snap b/jest/__snapshots__/components/ui/Address/Address.test.tsx.snap new file mode 100644 index 00000000..c9bee980 --- /dev/null +++ b/jest/__snapshots__/components/ui/Address/Address.test.tsx.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + + 0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520 + + +`; diff --git a/jest/__snapshots__/components/ui/Align/Align.test.tsx.snap b/jest/__snapshots__/components/ui/Align/Align.test.tsx.snap new file mode 100644 index 00000000..071669a9 --- /dev/null +++ b/jest/__snapshots__/components/ui/Align/Align.test.tsx.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + Foo + +`; diff --git a/jest/__snapshots__/components/ui/Button/Button.test.tsx.snap b/jest/__snapshots__/components/ui/Button/Button.test.tsx.snap new file mode 100644 index 00000000..4de5b126 --- /dev/null +++ b/jest/__snapshots__/components/ui/Button/Button.test.tsx.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + Foo + +`; diff --git a/jest/__snapshots__/components/ui/ClickablePanel/ClickablePanel.test.tsx.snap b/jest/__snapshots__/components/ui/ClickablePanel/ClickablePanel.test.tsx.snap new file mode 100644 index 00000000..6685ce99 --- /dev/null +++ b/jest/__snapshots__/components/ui/ClickablePanel/ClickablePanel.test.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + + Foo + + + + +`; diff --git a/jest/__snapshots__/components/ui/ClickableText/ClickableText.test.tsx.snap b/jest/__snapshots__/components/ui/ClickableText/ClickableText.test.tsx.snap new file mode 100644 index 00000000..eadf2761 --- /dev/null +++ b/jest/__snapshots__/components/ui/ClickableText/ClickableText.test.tsx.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + Foo + +`; diff --git a/jest/__snapshots__/components/ui/Code/Code.test.tsx.snap b/jest/__snapshots__/components/ui/Code/Code.test.tsx.snap new file mode 100644 index 00000000..4e5c26bd --- /dev/null +++ b/jest/__snapshots__/components/ui/Code/Code.test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + Foo + +`; diff --git a/jest/__snapshots__/components/ui/ExternalLink/ExternalLink.test.tsx.snap b/jest/__snapshots__/components/ui/ExternalLink/ExternalLink.test.tsx.snap new file mode 100644 index 00000000..99efb04a --- /dev/null +++ b/jest/__snapshots__/components/ui/ExternalLink/ExternalLink.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + Foo + +`; diff --git a/jest/__snapshots__/components/ui/Footer/Footer.test.tsx.snap b/jest/__snapshots__/components/ui/Footer/Footer.test.tsx.snap new file mode 100644 index 00000000..e7448542 --- /dev/null +++ b/jest/__snapshots__/components/ui/Footer/Footer.test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + + Powered by + + | Made by Maarten Zuidhoorn + + + + +`; diff --git a/jest/__snapshots__/components/ui/Footer/GitHubIcon/GitHubIcon.test.tsx.snap b/jest/__snapshots__/components/ui/Footer/GitHubIcon/GitHubIcon.test.tsx.snap new file mode 100644 index 00000000..52973d76 --- /dev/null +++ b/jest/__snapshots__/components/ui/Footer/GitHubIcon/GitHubIcon.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + +`; diff --git a/jest/__snapshots__/components/ui/Footer/MyCryptoLogo/MyCryptoLogo.test.tsx.snap b/jest/__snapshots__/components/ui/Footer/MyCryptoLogo/MyCryptoLogo.test.tsx.snap new file mode 100644 index 00000000..d33a705d --- /dev/null +++ b/jest/__snapshots__/components/ui/Footer/MyCryptoLogo/MyCryptoLogo.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + +`; diff --git a/jest/__snapshots__/components/ui/Header/Header.test.tsx.snap b/jest/__snapshots__/components/ui/Header/Header.test.tsx.snap new file mode 100644 index 00000000..21daec2c --- /dev/null +++ b/jest/__snapshots__/components/ui/Header/Header.test.tsx.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + + + + Are you sure you want to stop searching? + + + + FindETH + + + + +`; diff --git a/jest/__snapshots__/components/ui/Icon/Icon.test.tsx.snap b/jest/__snapshots__/components/ui/Icon/Icon.test.tsx.snap new file mode 100644 index 00000000..0d3adedd --- /dev/null +++ b/jest/__snapshots__/components/ui/Icon/Icon.test.tsx.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + +`; diff --git a/jest/__snapshots__/components/ui/Loader/Loader.test.tsx.snap b/jest/__snapshots__/components/ui/Loader/Loader.test.tsx.snap new file mode 100644 index 00000000..639ebc01 --- /dev/null +++ b/jest/__snapshots__/components/ui/Loader/Loader.test.tsx.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + + + Foo + +`; diff --git a/jest/__snapshots__/components/ui/Loader/Spinner/Spinner.test.tsx.snap b/jest/__snapshots__/components/ui/Loader/Spinner/Spinner.test.tsx.snap new file mode 100644 index 00000000..99b73b2b --- /dev/null +++ b/jest/__snapshots__/components/ui/Loader/Spinner/Spinner.test.tsx.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + +
+
+
+
+ +`; diff --git a/jest/__snapshots__/components/ui/Message/Message.test.tsx.snap b/jest/__snapshots__/components/ui/Message/Message.test.tsx.snap new file mode 100644 index 00000000..ecede5bb --- /dev/null +++ b/jest/__snapshots__/components/ui/Message/Message.test.tsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + Foo + +`; diff --git a/jest/__snapshots__/components/ui/Modal/Modal.test.tsx.snap b/jest/__snapshots__/components/ui/Modal/Modal.test.tsx.snap new file mode 100644 index 00000000..d7478682 --- /dev/null +++ b/jest/__snapshots__/components/ui/Modal/Modal.test.tsx.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + Foo + + + Close + + + + +`; diff --git a/jest/__snapshots__/components/ui/SmallPanel/SmallPanel.test.tsx.snap b/jest/__snapshots__/components/ui/SmallPanel/SmallPanel.test.tsx.snap new file mode 100644 index 00000000..cedf70f3 --- /dev/null +++ b/jest/__snapshots__/components/ui/SmallPanel/SmallPanel.test.tsx.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + Foo + +`; diff --git a/jest/__snapshots__/components/ui/Spinner/Spinner.test.tsx.snap b/jest/__snapshots__/components/ui/Spinner/Spinner.test.tsx.snap new file mode 100644 index 00000000..b864157e --- /dev/null +++ b/jest/__snapshots__/components/ui/Spinner/Spinner.test.tsx.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + + Foo + + +`; diff --git a/jest/__snapshots__/components/ui/Switch/Switch.test.tsx.snap b/jest/__snapshots__/components/ui/Switch/Switch.test.tsx.snap new file mode 100644 index 00000000..08c30924 --- /dev/null +++ b/jest/__snapshots__/components/ui/Switch/Switch.test.tsx.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + +`; diff --git a/jest/__snapshots__/components/ui/Tooltip/Tooltip.test.tsx.snap b/jest/__snapshots__/components/ui/Tooltip/Tooltip.test.tsx.snap new file mode 100644 index 00000000..034bce28 --- /dev/null +++ b/jest/__snapshots__/components/ui/Tooltip/Tooltip.test.tsx.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders a snapshot 1`] = ` + + + + Foo + + + Bar + +`; diff --git a/jest/__snapshots__/utils/tokens.test.ts.snap b/jest/__snapshots__/utils/tokens.test.ts.snap new file mode 100644 index 00000000..0674aa8a --- /dev/null +++ b/jest/__snapshots__/utils/tokens.test.ts.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`fetches token metadata for ERC-20 tokens 1`] = ` +Object { + "address": "0xa74476443119A942dE498590Fe1f2454d7D4aC0d", + "decimals": 18, + "name": "Golem Network Token", + "symbol": "GNT", +} +`; + +exports[`fetches token metadata for non-compliant ERC-20 tokens 1`] = ` +Object { + "address": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", + "decimals": 18, + "name": "Dai Stablecoin v1.0", + "symbol": "DAI", +} +`; diff --git a/jest/__snapshots__/wallets/Trezor.test.ts.snap b/jest/__snapshots__/wallets/Trezor.test.ts.snap new file mode 100644 index 00000000..d14d19d6 --- /dev/null +++ b/jest/__snapshots__/wallets/Trezor.test.ts.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Trezor generates an address from a derivation path 1`] = `"0x64E8A4EbF08cbAb754F9BEcBe80FA3CDFfbbdb1b"`; + +exports[`Trezor generates an address from a derivation path 2`] = `"0xb3363c233026E498c0a651D5E577396eCAca4AcB"`; + +exports[`Trezor generates an address from a hardened derivation path 1`] = `"0x0719f46C96047A4f8A791394E338c8108E56F246"`; + +exports[`Trezor generates an address from a hardened derivation path 2`] = `"0xA900c74246933B5F447C96581C5554Ac1bf36A92"`; + +exports[`Trezor pre-fetches multiple addresses 1`] = ` +Object { + "m/44'/1'/0'/0": Object { + "chainCode": "9d73a228d784f361eed3910b1d49750b33bc8aea09180b78abb71a09a17ae689", + "publicKey": "02bc7ab0a01997363ba548279abf8302ecc50ed376fe74ba3133f1678346ce0c5d", + }, + "m/44'/60'/0'/0": Object { + "chainCode": "51f696d1838ec2986b979577cc43c1098e26fe34d9abaf319df00a7eb20e0311", + "publicKey": "021c3e866ccb8f158431f5036319dc16f0409bd385d796fbc122a22819f0ec9017", + }, +} +`; diff --git a/jest/__snapshots__/wallets/ledger/Ledger.test.ts.snap b/jest/__snapshots__/wallets/ledger/Ledger.test.ts.snap new file mode 100644 index 00000000..49847e10 --- /dev/null +++ b/jest/__snapshots__/wallets/ledger/Ledger.test.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Ledger generates an address from a derivation path 1`] = `"0x55A64976d11064d0a5049Ec14ec632B921e1dBB4"`; + +exports[`Ledger generates an address from a derivation path 2`] = `"0xb1B46Cfcc7c7e72B6981e8aF8EB8CceB6d76E26e"`; + +exports[`Ledger generates an address from a hardened derivation path 1`] = `"0x1bD7f8d135C7565f63e6eB24E9A8704d2eB90f69"`; + +exports[`Ledger generates an address from a hardened derivation path 2`] = `"0xba4Ad981A41390f56cAB43549CC15050BD0418E8"`; diff --git a/jest/setupTests.js b/jest/setupTests.js new file mode 100644 index 00000000..afa3fbfe --- /dev/null +++ b/jest/setupTests.js @@ -0,0 +1,7 @@ +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; +import 'jest-styled-components'; +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +configure({ adapter: new Adapter() }); diff --git a/jest/snapshotResolver.js b/jest/snapshotResolver.js new file mode 100644 index 00000000..354a3777 --- /dev/null +++ b/jest/snapshotResolver.js @@ -0,0 +1,13 @@ +module.exports = { + resolveSnapshotPath: (testPath, snapshotExtension) => { + return testPath.replace('src/', 'jest/__snapshots__/') + snapshotExtension; + }, + + resolveTestPath: (snapshotFilePath, snapshotExtension) => { + return snapshotFilePath + .replace('jest/__snapshots__/', 'src/') + .slice(0, -snapshotExtension.length); + }, + + testPathForConsistencyCheck: 'src/component/example.js' +}; diff --git a/package.json b/package.json index 5576c97e..8a28c090 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "analyze": "webpack --config ./webpack/analyzer.ts", "tscheck": "tsc --noEmit --project tsconfig.json", "tslint": "tslint --project .", - "prettier:diff": "prettier --write --config ./.prettierrc --list-different './**/*.ts' './**/*.tsx'" + "prettier:diff": "prettier --write --config ./.prettierrc --list-different './**/*.ts' './**/*.tsx'", + "test": "jest", + "snapshot": "jest -u" }, "devDependencies": { "@babel/core": "^7.4.4", @@ -22,10 +24,16 @@ "@babel/preset-env": "^7.4.5", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.3.3", + "@ledgerhq/hw-transport-mocker": "^4.60.3", + "@ledgerhq/hw-transport-node-hid-noevents": "^4.60.3", + "@types/enzyme": "^3.9.3", + "@types/enzyme-adapter-react-16": "^1.0.5", + "@types/enzyme-to-json": "^1.5.3", "@types/finalhandler": "^1.1.0", "@types/hard-source-webpack-plugin": "^1.0.1", "@types/hdkey": "^0.7.0", "@types/html-webpack-plugin": "^3.2.0", + "@types/jest": "^24.0.13", "@types/react": "^16.8.17", "@types/react-dom": "^16.8.4", "@types/react-redux": "^7.0.9", @@ -45,6 +53,9 @@ "cross-env": "^5.2.0", "csp-html-webpack-plugin": "^3.0.1", "css-loader": "^2.1.1", + "enzyme": "^3.9.0", + "enzyme-adapter-react-16": "^1.13.2", + "enzyme-to-json": "^3.3.5", "favicons-webpack-plugin": "^0.0.9", "file-loader": "^3.0.1", "finalhandler": "^1.1.2", @@ -52,6 +63,8 @@ "hard-source-webpack-plugin": "^0.13.1", "html-webpack-plugin": "^3.2.0", "husky": "^2.3.0", + "jest": "^24.8.0", + "jest-styled-components": "^6.3.1", "lint-staged": "^8.1.7", "ora": "^3.4.0", "prettier": "^1.17.1", @@ -87,7 +100,7 @@ "@ledgerhq/hw-transport-u2f": "^4.60.2", "@ledgerhq/hw-transport-web-ble": "^4.58.0", "@ledgerhq/hw-transport-webusb": "^4.56.0", - "@mycrypto/ui": "^0.16.2", + "@mycrypto/ui": "^0.16.3", "eth-scan": "^0.1.2", "hdkey": "^1.1.1", "react": "^16.8.6", @@ -118,7 +131,7 @@ }, "husky": { "hooks": { - "pre-commit": "yarn run tscheck && lint-staged" + "pre-commit": "yarn run tscheck && yarn run test && lint-staged" } } } diff --git a/src/@types/jest-styled-components.d.ts b/src/@types/jest-styled-components.d.ts new file mode 100644 index 00000000..a94bd476 --- /dev/null +++ b/src/@types/jest-styled-components.d.ts @@ -0,0 +1,18 @@ +declare namespace jest { + interface AsymmetricMatcher { + $$typeof: symbol; + sample?: string | RegExp | object | any[] | (() => any); + } + + type Value = string | number | RegExp | AsymmetricMatcher | undefined; + + interface Options { + media?: string; + modifier?: any; + supports?: string; + } + + interface Matchers { + toHaveStyleRule(property: string, value?: Value, options?: Options): R; + } +} diff --git a/src/@types/ledgerhq/hw-transport-mocker/RecordStore.d.ts b/src/@types/ledgerhq/hw-transport-mocker/RecordStore.d.ts new file mode 100644 index 00000000..b9dfeaa1 --- /dev/null +++ b/src/@types/ledgerhq/hw-transport-mocker/RecordStore.d.ts @@ -0,0 +1,58 @@ +declare module '@ledgerhq/hw-transport-mocker/RecordStore' { + export type Queue = [string, string][]; + + export class RecordStore { + /** + * Create an instance of RecordStore from a string. + * + * @param {string} str The string to create the RecordStore from. + * @return {RecordStore} An instance of RecordStore; + */ + public static fromString(str: string): RecordStore; + + public queue: Queue; + + /** + * Create a new instance of the RecordStore class. + * + * @param {Queue} queue An optional queue to use. + */ + public constructor(queue?: Queue); + + /** + * Get whether the queue is empty. + * + * @return {boolean} TRUE if the queue is empty, FALSE otherwise + */ + public isEmpty(): boolean; + + /** + * Record an APDU exchange to the queue. + * + * @param {Buffer} apdu The input data. + * @param {Buffer} out The output data. + */ + public recordExchange(apdu: Buffer, out: Buffer): void; + + /** + * Replay a previously recorded APDU exchange. Throws an error if the queue is empty or if the + * recorded APDU is invalid. + * + * @param {Buffer} apdu The input to replay + * @return {Buffer} A Buffer with the previously recorded output data. + */ + public replayExchange(apdu: Buffer): Buffer; + + /** + * Ensure the queue is empty. Throws an error if the queue isn't empty. + */ + public ensureQueueEmpty(): void; + + /** + * Get the current queue as string. + * + * @return {string} The queue as string. + */ + public toString(): string; + } +} diff --git a/src/@types/ledgerhq/hw-transport-mocker/createTransportRecorder.d.ts b/src/@types/ledgerhq/hw-transport-mocker/createTransportRecorder.d.ts new file mode 100644 index 00000000..a6a12ad1 --- /dev/null +++ b/src/@types/ledgerhq/hw-transport-mocker/createTransportRecorder.d.ts @@ -0,0 +1,29 @@ +declare module '@ledgerhq/hw-transport-mocker/createTransportRecorder' { + import Transport from '@ledgerhq/hw-transport'; + import { RecordStore } from '@ledgerhq/hw-transport-mocker/RecordStore'; + + class TransportRecorder extends Transport { + public static recordStore: RecordStore; + + public static isSupported: typeof Transport.isSupported; + + public static list: typeof Transport.list; + } + + type TransportConstructor = new (...args: any[]) => Transport; + + type TransportRecorderConstructor = typeof TransportRecorder & + (new (...args: any[]) => TransportRecorder); + + /** + * Create a decorated transport, which records any APDU exchanges. + * + * @param {TransportConstructor} DecoratedTransport The transport class to decorate. + * @param {RecordStore} recordStore The RecordStore to record to. + * @return {TransportRecorder} The decorated transport. + */ + export default function( + DecoratedTransport: TransportConstructor, + recordStore: RecordStore + ): TransportRecorderConstructor; +} diff --git a/src/@types/ledgerhq/hw-transport-mocker/createTransportReplayer.d.ts b/src/@types/ledgerhq/hw-transport-mocker/createTransportReplayer.d.ts new file mode 100644 index 00000000..83a4c5e7 --- /dev/null +++ b/src/@types/ledgerhq/hw-transport-mocker/createTransportReplayer.d.ts @@ -0,0 +1,27 @@ +declare module '@ledgerhq/hw-transport-mocker/createTransportReplayer' { + import Transport, { Observer, Subscription } from '@ledgerhq/hw-transport'; + import { RecordStore } from '@ledgerhq/hw-transport-mocker/RecordStore'; + + class TransportReplayer extends Transport { + public static isSupported(): Promise; + + public static list(): Promise<[null]>; + + public static listen(observer: Observer<{ type: 'add'; descriptor: null }>): Subscription; + + public static open(): Promise>; + } + + type TransportConstructor = new (...args: any[]) => Transport; + + type TransportReplayerConstructor = typeof TransportReplayer & + (new (...args: any[]) => TransportReplayer); + + /** + * Create a transport, which replays any APDU exchanges. + * + * @param {RecordStore} recordStore The RecordStore to replay from. + * @return {TransportReplayer} The decorated transport. + */ + export default function(recordStore: RecordStore): TransportReplayerConstructor; +} diff --git a/src/@types/ledgerhq/hw-transport-mocker/index.d.ts b/src/@types/ledgerhq/hw-transport-mocker/index.d.ts new file mode 100644 index 00000000..700ed667 --- /dev/null +++ b/src/@types/ledgerhq/hw-transport-mocker/index.d.ts @@ -0,0 +1,12 @@ +/** + * Note: This stuff isn't documented at all, so these declarations may be inaccurate. + */ +declare module '@ledgerhq/hw-transport-mocker' { + export { + default as createTransportRecorder + } from '@ledgerhq/hw-transport-mocker/createTransportRecorder'; + export { + default as createTransportReplayer + } from '@ledgerhq/hw-transport-mocker/createTransportReplayer'; + export * from '@ledgerhq/hw-transport-mocker/RecordStore'; +} diff --git a/src/@types/ledgerhq/hw-transport-node-hid-noevents/index.d.ts b/src/@types/ledgerhq/hw-transport-node-hid-noevents/index.d.ts new file mode 100644 index 00000000..b241027f --- /dev/null +++ b/src/@types/ledgerhq/hw-transport-node-hid-noevents/index.d.ts @@ -0,0 +1,51 @@ +declare module '@ledgerhq/hw-transport-node-hid-noevents' { + import Transport, { DescriptorEvent, Observer, Subscription } from '@ledgerhq/hw-transport'; + import { DeviceModel } from '@ledgerhq/devices'; + + export default class TransportNodeHid extends Transport { + /** + * List all available descriptors. For a better granularity, use `listen()`. + * + * @return {Promise} All available descriptors. + */ + public static list(): Promise; + + /** + * Listen to all device events for a given Transport. The method takes an Observer of + * DescriptorEvent and returns a Subscription (according to Observable paradigm + * https://github.com/tc39/proposal-observable). Each `listen()` call will first emit all + * potential devices already connected and then will emit events that can come over time, for + * instance if you plug a USB device after `listen()` or a Bluetooth device becomes + * discoverable. + * + * Must be called in the context of a UI click. + * + * @param {Observer} observer The observer object. + * @return A Subcription object on which you can `.unsubscribe()`, to stop listening to + * descriptors. + */ + public static listen(observer: Observer>): Subscription; + + /** + * Attempt to create an instance of the Transport with the descriptor. + * + * @param {string} descriptor The descriptor to open the Transport with. If none provided, the + * first available device will be used. + * @return {Promise} A Promise with the Transport instance. + */ + public static open(descriptor?: string): Promise; + + public readonly device: any; + public readonly deviceModel?: DeviceModel; + public readonly channel: number; + public readonly packetSize: number; + public readonly disconnected: boolean; + + public constructor(device: USBDevice); + + /** + * Not used by this specific Transport. + */ + public setScramblekey(): void; + } +} diff --git a/src/components/ui/Address/Address.test.tsx b/src/components/ui/Address/Address.test.tsx new file mode 100644 index 00000000..dd332b63 --- /dev/null +++ b/src/components/ui/Address/Address.test.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Address from './Address'; + +it('renders a snapshot', () => { + const component = shallow(
); + + expect(component).toMatchSnapshot(); +}); + +it('renders an address', () => { + const component = shallow(
); + + expect(component.prop('truncate')).toBeFalsy(); + expect(component.contains('0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520')).toBe(true); +}); + +it('renders a truncated address', () => { + const component = shallow( +
+ ); + + expect(component.contains('0x4bbe...1520')).toBe(true); +}); diff --git a/src/components/ui/Align/Align.test.tsx b/src/components/ui/Align/Align.test.tsx new file mode 100644 index 00000000..bf07f8aa --- /dev/null +++ b/src/components/ui/Align/Align.test.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { StyledAlign } from './StyledAlign'; + +it('renders a snapshot', () => { + const component = shallow(Foo); + + expect(component).toMatchSnapshot(); + expect(component.prop('flexWrap')).toBeFalsy(); +}); + +it('renders the children', () => { + const component = shallow(Foo); + + expect(component.contains('Foo')).toBe(true); +}); diff --git a/src/components/ui/Button/Button.test.tsx b/src/components/ui/Button/Button.test.tsx new file mode 100644 index 00000000..34418e9f --- /dev/null +++ b/src/components/ui/Button/Button.test.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { StyledButton } from './StyledButton'; + +it('renders a snapshot', () => { + const component = shallow(Foo); + + expect(component).toMatchSnapshot(); +}); + +it('renders with text', () => { + const component = shallow(Foo); + + expect(component.contains('Foo')).toBe(true); +}); + +it('calls the onClick function if clicked', () => { + const handleClick = jest.fn(); + + const component = shallow(); + component.simulate('click'); + + expect(handleClick).toHaveBeenCalledTimes(1); +}); diff --git a/src/components/ui/ClickablePanel/ClickablePanel.test.tsx b/src/components/ui/ClickablePanel/ClickablePanel.test.tsx new file mode 100644 index 00000000..537d4dd7 --- /dev/null +++ b/src/components/ui/ClickablePanel/ClickablePanel.test.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { ClickablePanel } from './ClickablePanel'; +import { Panel } from '@mycrypto/ui'; +import { createMemoryHistory } from 'history'; + +it('renders a snapshot', () => { + const history = createMemoryHistory(); + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); +}); + +it('renders the children', () => { + const handleClick = jest.fn(); + + const history = createMemoryHistory(); + const component = shallow( + + ); + + expect(component.contains('Foo')).toBe(true); +}); + +it(`doesn't navigate on click with a onClick handler provided`, () => { + const handleClick = jest.fn(); + + const history = createMemoryHistory(); + const component = shallow( + + ); + + const listener = jest.fn(); + history.listen(listener); + + component.find(Panel).simulate('click'); + + expect(handleClick).toHaveBeenCalledTimes(1); + expect(listener).not.toHaveBeenCalled(); +}); + +it('navigates on click without a onClick handler provided', () => { + const history = createMemoryHistory(); + const component = shallow( + + ); + + const listener = jest.fn(); + history.listen(listener); + + component.find(Panel).simulate('click'); + + expect(listener).toHaveBeenCalledTimes(1); + expect(listener.mock.calls[0][0].pathname).toBe('/baz'); +}); diff --git a/src/components/ui/ClickablePanel/ClickablePanel.tsx b/src/components/ui/ClickablePanel/ClickablePanel.tsx index 8d6be84f..a170042f 100644 --- a/src/components/ui/ClickablePanel/ClickablePanel.tsx +++ b/src/components/ui/ClickablePanel/ClickablePanel.tsx @@ -12,7 +12,7 @@ interface OwnProps { type Props = OwnProps & RouteComponentProps; -const ClickablePanel: FunctionComponent = ({ title, to, icon, history, onClick }) => { +export const ClickablePanel: FunctionComponent = ({ title, to, icon, history, onClick }) => { const handleClick = () => { if (to) { history.push(to); diff --git a/src/components/ui/ClickableText/ClickableText.test.tsx b/src/components/ui/ClickableText/ClickableText.test.tsx new file mode 100644 index 00000000..65e603cc --- /dev/null +++ b/src/components/ui/ClickableText/ClickableText.test.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { StyledClickableText } from './StyledClickableText'; + +it('renders a snapshot', () => { + const component = shallow(Foo); + + expect(component).toMatchSnapshot(); +}); + +it('renders its children', () => { + const component = shallow(Foo); + + expect(component.contains('Foo')).toBe(true); +}); diff --git a/src/components/ui/Code/Code.test.tsx b/src/components/ui/Code/Code.test.tsx new file mode 100644 index 00000000..45cdda05 --- /dev/null +++ b/src/components/ui/Code/Code.test.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Code from './StyledCode'; + +it('renders a snapshot', () => { + const component = shallow(Foo); + + expect(component).toMatchSnapshot(); +}); + +it('renders its children', () => { + const component = shallow(Foo); + + expect(component.contains('Foo')).toBe(true); +}); diff --git a/src/components/ui/ExternalLink/ExternalLink.test.tsx b/src/components/ui/ExternalLink/ExternalLink.test.tsx new file mode 100644 index 00000000..e500cecd --- /dev/null +++ b/src/components/ui/ExternalLink/ExternalLink.test.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import ExternalLink from './ExternalLink'; + +it('renders a snapshot', () => { + const component = shallow(Foo); + + expect(component).toMatchSnapshot(); +}); + +it('renders an element with props and children', () => { + const component = shallow(Foo); + + expect(component.find('a').props().href).toBe('https://example.com'); + expect(component.contains('Foo')).toBe(true); +}); diff --git a/src/components/ui/Footer/Footer.test.tsx b/src/components/ui/Footer/Footer.test.tsx new file mode 100644 index 00000000..ce0a3f55 --- /dev/null +++ b/src/components/ui/Footer/Footer.test.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Footer from './Footer'; + +it('renders a snapshot', () => { + const component = shallow(