diff --git a/packages/integration-tests/projects/suite-web/support/utils/device.ts b/packages/integration-tests/projects/suite-web/support/utils/device.ts index 07316ec88cb..019d777f491 100644 --- a/packages/integration-tests/projects/suite-web/support/utils/device.ts +++ b/packages/integration-tests/projects/suite-web/support/utils/device.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ -// @ts-ignore + import { DEVICE, Features, Device } from 'trezor-connect'; import { getConnectDevice, @@ -13,18 +13,17 @@ import { * @param features */ -export const connectDevice = (device?: Partial, features?: Partial) => { - return cy +export const connectDevice = (device?: Partial, features?: Partial) => + cy .window() .its('store') .invoke('dispatch', { type: DEVICE.CONNECT, payload: getConnectDevice(device, getDeviceFeatures(features)), }); -}; -export const connectBootloaderDevice = (path: string) => { - return cy +export const connectBootloaderDevice = (path: string) => + cy .window() .its('store') .invoke('dispatch', { @@ -74,7 +73,6 @@ export const connectBootloaderDevice = (path: string) => { useEmptyPassphrase: true, }, }); -}; /** * Helper method to dispatch DEVICE.CHANGED action. * diff --git a/packages/integration-tests/projects/suite-web/tests/onboarding/hologram.test.ts b/packages/integration-tests/projects/suite-web/tests/onboarding/hologram.test.ts deleted file mode 100644 index 7981534dca4..00000000000 --- a/packages/integration-tests/projects/suite-web/tests/onboarding/hologram.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -// @group:onboarding -// @retry=2 - -// todo: is there any hologram in der neue onboarding? -describe.skip('Onboarding - hologram', () => { - beforeEach(() => { - cy.task('startBridge'); - cy.viewport(1024, 768).resetDb(); - cy.prefixedVisit('/'); - - - cy.goToOnboarding(); - - // cy.onboardingShouldLoad(); - // common steps - navigation through onboarding - // cy.getTestElement('@onboarding/begin-button').click(); - // cy.getTestElement('@onboarding/path-recovery-button').click(); - // cy.getTestElement('@onboarding/path-new-button').click(); - - }); - - it('Hologram, various cases', () => { - cy.task('startEmu', { version: '2.1.4', wipe: true }); - - cy.log('first check if correct video is displayed according to users choice of device'); - cy.getTestElement('@onboarding/option-model-one-path').click(); - cy.getTestElement('@onboarding/hologram/model-1-video'); - cy.getTestElement('@onboarding/back-button').click(); - cy.getTestElement('@onboarding/option-model-t-path').click(); - cy.getTestElement('@onboarding/hologram/model-2-video'); - - cy.log('hmm... it looks different, maybe?'); - cy.getTestElement('@onboarding/hologram/hologram-different-button').click(); - cy.getTestElement('@onboarding/hologram/show-hologram-again-button').click(); - cy.log('nah, it is fine actually, lets proceed'); - cy.getTestElement('@onboarding/hologram/continue-button').click(); - }); -}); diff --git a/packages/integration-tests/projects/suite-web/tests/onboarding/packaging-security.test.ts b/packages/integration-tests/projects/suite-web/tests/onboarding/packaging-security.test.ts new file mode 100644 index 00000000000..0828b0648ce --- /dev/null +++ b/packages/integration-tests/projects/suite-web/tests/onboarding/packaging-security.test.ts @@ -0,0 +1,17 @@ +// @group:onboarding +// @retry=2 + +describe('Onboarding - packaging security', () => { + beforeEach(() => { + cy.task('startBridge'); + cy.viewport(1024, 768).resetDb(); + cy.prefixedVisit('/'); + }); + + it('Device without firmware is expected to come fresh out of package', () => { + cy.connectDevice({ mode: 'initialize', firmware: 'none' }); + cy.getTestElement('@onboarding/continue-button').click(); + cy.getTestElement('@onboarding/exit-app-button'); + cy.matchImageSnapshot('security-check'); + }); +}); diff --git a/packages/integration-tests/projects/suite-web/tests/onboarding/steps-order.test.ts b/packages/integration-tests/projects/suite-web/tests/onboarding/steps-order.test.ts deleted file mode 100644 index 6f78ea7f36d..00000000000 --- a/packages/integration-tests/projects/suite-web/tests/onboarding/steps-order.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ - -// @group:onboarding -// @retry=2 - -describe.skip('Onboarding - steps order', () => { - before(() => { - cy.task('startBridge'); - }); - - beforeEach(() => { - cy.viewport(1024, 768).resetDb(); - cy.prefixedVisit('/'); - cy.goToOnboarding(); - cy.onboardingShouldLoad(); - cy.getTestElement('@onboarding/begin-button').click(); - }); - - describe('New device', () => { - beforeEach(() => { - cy.getTestElement('@onboarding/path-create-button').click(); - }); - - it('No device connected -> offer device selection', () => { - cy.getTestElement('@onboarding/path-new-button').click(); - cy.getTestElement('@onboarding/option-model-one-path'); - }); - - it('Device is already connected -> skip device selection and go to hologram directly', () => { - cy.connectDevice({ firmware: 'none' }, { firmware_present: false }); - cy.getTestElement('@onboarding/path-new-button').click(); - cy.getTestElement('@onboarding/hologram/hologram-different-button'); - }); - }); - - describe('Used device', () => { - beforeEach(() => { - cy.getTestElement('@onboarding/path-create-button').click(); - }); - - it('No device connected -> skip device selection, skip hologram and go to pair device step directly', () => { - cy.getTestElement('@onboarding/path-used-button').click(); - cy.getTestElement('@onboarding/pair-device-step'); - }); - }); - - describe('Back buttons', () => { - it('back buttons', () => { - cy.getTestElement('@onboarding/path-create-button').click(); - cy.getTestElement('@onboarding/back-button').click(); - cy.getTestElement('@onboarding/path-create-button').click(); - - cy.getTestElement('@onboarding/path-new-button').click(); - cy.getTestElement('@onboarding/back-button').click(); - cy.getTestElement('@onboarding/path-used-button').click(); - cy.getTestElement('@onboarding/back-button').click(); - cy.getTestElement('@onboarding/path-new-button').click(); - - cy.getTestElement('@onboarding/option-model-one-path').click(); - cy.getTestElement('@onboarding/back-button').click(); - cy.getTestElement('@onboarding/option-model-t-path').click(); - cy.getTestElement('@onboarding/back-button').click(); - cy.getTestElement('@onboarding/option-model-t-path').click(); - }); - }); -}); diff --git a/packages/integration-tests/projects/suite-web/tests/onboarding/t1-recovery-advanced.test.ts b/packages/integration-tests/projects/suite-web/tests/onboarding/t1-recovery-advanced.test.ts index 9c579ef6baf..53abeeac5a9 100644 --- a/packages/integration-tests/projects/suite-web/tests/onboarding/t1-recovery-advanced.test.ts +++ b/packages/integration-tests/projects/suite-web/tests/onboarding/t1-recovery-advanced.test.ts @@ -38,7 +38,7 @@ describe('Onboarding - recover wallet T1', () => { ); cy.wait(501); cy.task('stopEmu'); - cy.getTestElement('@onboarding/connect-device', { timeout: 20000 }); + cy.getTestElement('@connect-device-prompt', { timeout: 20000 }); cy.task('startEmu', { version: '1.9.0' }); cy.getTestElement('@onboarding/recovery/retry-button').click(); diff --git a/packages/integration-tests/projects/suite-web/tests/onboarding/t2-recovery-fail.test.ts b/packages/integration-tests/projects/suite-web/tests/onboarding/t2-recovery-fail.test.ts index 6bb552539ab..91d23871a5a 100644 --- a/packages/integration-tests/projects/suite-web/tests/onboarding/t2-recovery-fail.test.ts +++ b/packages/integration-tests/projects/suite-web/tests/onboarding/t2-recovery-fail.test.ts @@ -21,7 +21,7 @@ describe('Onboarding - recover wallet T2', () => { cy.task('pressYes'); cy.wait(501); cy.task('stopEmu'); - cy.getTestElement('@onboarding/connect-device', { timeout: 20000 }); + cy.getTestElement('@connect-device-prompt', { timeout: 20000 }); cy.task('startEmu', { version: '2.1.4', wipe: false }); cy.log( 'If device disconnected during call, error page with retry button should appear. Also note, that unlike with T1, retry button initiates recoveryDevice call immediately', diff --git a/packages/integration-tests/projects/suite-web/tests/onboarding/t2-recovery-persistence.test.ts b/packages/integration-tests/projects/suite-web/tests/onboarding/t2-recovery-persistence.test.ts index 3f9cc85992a..0b2ee6373db 100644 --- a/packages/integration-tests/projects/suite-web/tests/onboarding/t2-recovery-persistence.test.ts +++ b/packages/integration-tests/projects/suite-web/tests/onboarding/t2-recovery-persistence.test.ts @@ -70,7 +70,7 @@ describe('Onboarding - T2 in recovery mode', () => { // disconnect device, reload application cy.task('stopEmu'); - cy.getTestElement('@onboarding/connect-device', { timeout: 20000 }); + cy.getTestElement('@connect-device-prompt', { timeout: 20000 }); cy.wait(501); cy.resetDb(); cy.reload(); @@ -110,7 +110,7 @@ describe('Onboarding - T2 in recovery mode', () => { cy.wait(501); cy.task('stopEmu'); cy.wait(1000); - cy.getTestElement('@onboarding/connect-device', { timeout: 30000 }); + cy.getTestElement('@connect-device-prompt', { timeout: 30000 }); cy.task('startEmu', { version: '2.3.1', wipe: false }); cy.getTestElement('@onboarding/confirm-on-device'); cy.wait(1000); diff --git a/packages/integration-tests/projects/suite-web/tests/recovery/t2-dry-run-persistence.test.ts b/packages/integration-tests/projects/suite-web/tests/recovery/t2-dry-run-persistence.test.ts index b3683905b44..bb00dadf31e 100644 --- a/packages/integration-tests/projects/suite-web/tests/recovery/t2-dry-run-persistence.test.ts +++ b/packages/integration-tests/projects/suite-web/tests/recovery/t2-dry-run-persistence.test.ts @@ -33,7 +33,7 @@ describe('Recovery - dry run', () => { cy.wait(501); cy.task('stopEmu'); cy.getTestElement('@recovery/close-button', { timeout: 30000 }).click(); - cy.getTestElement('@modal/connect-device'); + cy.getTestElement('@connect-device-prompt'); cy.task('startEmu', { wipe: false, version: '2.3.1' }); cy.getTestElement('@suite/modal/confirm-action-on-device', { timeout: 20000 }); cy.task('pressYes'); diff --git a/packages/integration-tests/projects/suite-web/tests/suite/bridge.test.ts b/packages/integration-tests/projects/suite-web/tests/suite/bridge.test.ts index 58d8d2d0206..e83c1a68ebd 100644 --- a/packages/integration-tests/projects/suite-web/tests/suite/bridge.test.ts +++ b/packages/integration-tests/projects/suite-web/tests/suite/bridge.test.ts @@ -40,11 +40,13 @@ describe('Bridge page', () => { // user may exit bridge page and use webusb cy.getTestElement('@bridge/goto/wallet-index').click(); - // connect device modal with webusb enabled appears - cy.getTestElement('@modal/connect-device'); + // connect device prompt with webusb enabled appears + cy.getTestElement('@connect-device-prompt'); // linux platforms show udev rules link also - cy.getTestElement('@modal/connect-device/goto/suite-udev').click(); + // todo: rename @onboarding/expand-troubleshooting-tips as it is in suite now + cy.getTestElement('@onboarding/expand-troubleshooting-tips').click(); + cy.getTestElement('@goto/udev').click(); cy.getTestElement('@modal/udev').matchImageSnapshot('udev rules modal'); // udev rules modal is closable via close button diff --git a/packages/integration-tests/projects/suite-web/tests/suite/prerequisites.test.ts b/packages/integration-tests/projects/suite-web/tests/suite/prerequisites.test.ts new file mode 100644 index 00000000000..3a3ff43a1a5 --- /dev/null +++ b/packages/integration-tests/projects/suite-web/tests/suite/prerequisites.test.ts @@ -0,0 +1,57 @@ +import { PrerequisiteType } from "@suite/types/suite"; + +type Fixture = { + desc: PrerequisiteType; + mockDevice: any; +} + +describe('prerequisites = test various types of devices connecting to the application', () => { + beforeEach(() => { + + cy.viewport(1024, 768).resetDb(); + cy.prefixedVisit('/'); + + cy.getTestElement('@connect-device-prompt'); + }); + + // todo: add transport related prerequisites + const fixtures: Fixture[] = [ + { + desc: 'device-seedless', + mockDevice: () => cy.connectDevice({ mode: 'seedless' }), + }, { + desc: 'device-unacquired', + mockDevice: () => cy.connectDevice({ type: 'unacquired' }), + }, { + desc: 'device-unreadable', + mockDevice: () => cy.connectDevice({ type: 'unreadable' }), + }, { + desc: 'device-unknown', + mockDevice: () => cy.connectDevice({ features: undefined }) + }, { + desc: 'device-disconnected', + mockDevice: () => { }, + }, + { + desc: 'device-bootloader', + mockDevice: () => cy.connectBootloaderDevice('1'), + } + ]; + + fixtures.forEach(f => { + it(f.desc, () => { + f.mockDevice(); + cy.getTestElement('@onboarding/expand-troubleshooting-tips').click(); + cy.matchImageSnapshot(f.desc); + }) + }) + + describe('should redirect to onboarding', () => { + it('to welcome step', () => { + cy.connectDevice({ mode: 'initialize' }); + cy.getTestElement('@onboarding/welcome'); + }); + + // device-recover-mode is tested elsewhere with full-fledged emulator + }) +}) \ No newline at end of file diff --git a/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx b/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx new file mode 100644 index 00000000000..c49f8ca7bd9 --- /dev/null +++ b/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx @@ -0,0 +1,341 @@ +import React from 'react'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { renderWithProviders, findByTestId } from '@suite/support/tests/hooksHelper'; +import Preloader from '..'; + +// react-svg will not work +jest.mock('react-svg', () => ({ ReactSVG: () => 'SVG' })); +// render only Translation.id in data-test attribute +jest.mock('@suite-components/Translation', () => ({ + Translation: ({ id }: any) =>
{id}
, +})); +// jest.mock('@firmware-components/ReconnectDevicePrompt', () => ({ +// __esModule: true, // export as module +// default: ({ children }: any) =>
{children}
, +// })); +// jest.mock('@onboarding-components/DeviceAnimation', () => ({ +// __esModule: true, // export as module +// default: ({ children }: any) =>
{children}
, +// })); +// do not render animations +// jest.mock('lottie-react', () => ({ +// // Lottie: ({ trezorVersion }: any) =>
{trezorVersion}
, +// __esModule: true, // export as module +// default: ({ trezorVersion }: any) =>
{trezorVersion}
, +// })); +// jest.mock('react-use/lib/useMeasure', () => ({ +// // Lottie: ({ trezorVersion }: any) =>
{trezorVersion}
, +// __esModule: true, // export as module +// default: () => ['ref', { height: 0 }], +// })); +// jest.mock('react-spring', () => ({ +// config: { +// default: {}, +// }, +// animated: { +// div: (props: any) =>
{props.children}
, +// }, +// useSpring: () => ({}), +// })); + +export const getInitialState = ({ suite, router }: any = {}) => ({ + suite: { + loading: false, + loaded: true, + transport: { type: undefined }, + settings: { debug: {}, theme: { variant: 'light' } }, + online: true, + locks: [], + flags: {}, + ...suite, + }, + devices: [], + resize: { + size: 'LARGE', + }, + guide: {}, + messageSystem: { + config: { + actions: [], + }, + validMessages: { + banner: [], + context: [], + modal: [], + }, + }, + modal: { + context: '@modal/context-none', + }, + notifications: [], + wallet: { + discovery: [], + accountSearch: {}, + settings: {}, + }, + router: { + app: 'suite-start', + loaded: true, + route: { + app: 'suite-start', + }, + ...router, + }, + recovery: {}, +}); + +type State = ReturnType; +const mockStore = configureStore([thunk]); + +const initStore = (state: State) => { + const store = mockStore(state); + return store; +}; + +const Index = ({ app }: any) => {app || 'foo'}; + +describe('Preloader component', () => { + it('Loading: suite is loading', () => { + const store = initStore( + getInitialState({ + suite: { + loading: true, + loaded: false, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + expect(findByTestId('@suite/loading')).not.toBeNull(); + + unmount(); + }); + + it('Loading: router is loading', () => { + const store = initStore( + getInitialState({ + router: { + loaded: false, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + expect(findByTestId('@suite/loading')).not.toBeNull(); + + unmount(); + }); + + it('Loading: transport is not set yet', () => { + const store = initStore( + getInitialState({ + suite: { + transport: null, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + expect(findByTestId('@suite/loading')).not.toBeNull(); + + unmount(); + }); + + it('No transport', () => { + const store = initStore(getInitialState()); + const { unmount } = renderWithProviders(store, ); + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId('TR_TREZOR_BRIDGE_IS_NOT_RUNNING')).not.toBeNull(); + + unmount(); + }); + + it('Bridge transport, no device', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'bridge' }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId(/TR_STILL_DONT_SEE_YOUR_TREZOR/)).not.toBeNull(); + + unmount(); + }); + + it('WebUSB transport, no device', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'WebUsbPlugin' }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId('TR_CHECK_FOR_DEVICES')).not.toBeNull(); + + unmount(); + }); + + it('Unacquired device', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'bridge' }, + device: { type: 'unacquired' }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId('TR_ACQUIRE_DEVICE_TITLE')).not.toBeNull(); + + unmount(); + }); + + it('Unreadable device', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'bridge' }, + device: { type: 'unreadable' }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId(/TR_YOUR_DEVICE_IS_CONNECTED_BUT_UNREADABLE/)).not.toBeNull(); + + unmount(); + }); + + it('Unknown device (should never happen)', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'bridge' }, + device: { features: null }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId(/TR_UNKNOWN_DEVICE/)).not.toBeNull(); + + unmount(); + }); + + it('Seedless device', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'bridge' }, + device: { mode: 'seedless', features: {} }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId(/TR_YOUR_DEVICE_IS_SEEDLESS/)).not.toBeNull(); + expect(findByTestId('TR_SEEDLESS_SETUP_IS_NOT_SUPPORTED_TITLE')).not.toBeNull(); + + unmount(); + }); + + it('Recovery mode device', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'bridge' }, + device: { features: { recovery_mode: true } }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId(/TR_DEVICE_IN_RECOVERY_MODE/)).not.toBeNull(); + expect(findByTestId('TR_CONTINUE')).not.toBeNull(); + + unmount(); + }); + + it('Not initialized device', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'bridge' }, + device: { mode: 'initialize', features: {} }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId(/TR_DEVICE_NOT_INITIALIZED/)).not.toBeNull(); + expect(findByTestId('TR_GO_TO_ONBOARDING')).not.toBeNull(); + + unmount(); + }); + + it('Bootloader device with installed firmware', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'bridge' }, + device: { mode: 'bootloader', features: { firmware_present: true } }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId(/TR_DEVICE_IN_BOOTLOADER/)).not.toBeNull(); + expect(findByTestId('TR_RECONNECT_IN_NORMAL')).not.toBeNull(); + + unmount(); + }); + + it('Bootloader device without firmware', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'bridge' }, + device: { mode: 'bootloader', features: { firmware_present: false } }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId(/TR_NO_FIRMWARE/)).not.toBeNull(); + expect(findByTestId('TR_GO_TO_ONBOARDING')).not.toBeNull(); + + unmount(); + }); + + it('Required FW update device', () => { + const store = initStore( + getInitialState({ + suite: { + transport: { type: 'bridge' }, + device: { firmware: 'required', features: {} }, + }, + }), + ); + const { unmount } = renderWithProviders(store, ); + + expect(findByTestId('@connect-device-prompt')).not.toBeNull(); + expect(findByTestId(/FW_CAPABILITY_UPDATE_REQUIRED/)).not.toBeNull(); + expect(findByTestId('TR_SEE_DETAILS')).not.toBeNull(); + + unmount(); + }); +});