diff --git a/.detoxrc.json b/.detoxrc.json new file mode 100644 index 00000000..217717a7 --- /dev/null +++ b/.detoxrc.json @@ -0,0 +1,41 @@ +{ + "testRunner": "jest", + "runnerConfig": "e2e/config.json", + "skipLegacyWorkersInjection": true, + "apps": { + "ios": { + "type": "ios.app", + "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/monsuivipsy.app", + "build": "xcodebuild -workspace ios/monsuivipsy.xcworkspace -configuration Release -sdk iphonesimulator -scheme monsuivipsy -derivedDataPath ios/build", + "bundleId": "org.reactjs.native.example.monsuivipsy" + }, + "android": { + "type": "android.apk", + "binaryPath": "SPECIFY_PATH_TO_YOUR_APP_BINARY" + } + }, + "devices": { + "simulator": { + "type": "ios.simulator", + "device": { + "type": "iPhone 13" + } + }, + "emulator": { + "type": "android.emulator", + "device": { + "avdName": "Pixel_3a_API_30_x86" + } + } + }, + "configurations": { + "ios": { + "device": "simulator", + "app": "ios" + }, + "android": { + "device": "emulator", + "app": "android" + } + } +} \ No newline at end of file diff --git a/App.js b/App.js index 426fd082..8cf7f0f4 100644 --- a/App.js +++ b/App.js @@ -8,8 +8,10 @@ import {DiaryNotesProvider} from './src/context/diaryNotes'; import NPS from './src/services/NPS/NPS'; import VersionChecker from './src/services/versionChecker'; import {Sentry} from 'react-native-sentry'; +import { LogBox } from 'react-native'; if (!__DEV__) { + LogBox.ignoreAllLogs(); Sentry.config( 'https://9f0bd8f8af8444eea9f470d00a1bb411@sentry.fabrique.social.gouv.fr/54', ).install(); diff --git a/e2e/config.json b/e2e/config.json new file mode 100644 index 00000000..1f9b30c4 --- /dev/null +++ b/e2e/config.json @@ -0,0 +1,9 @@ +{ + "maxWorkers": 1, + "testEnvironment": "./environment", + "testRunner": "jest-circus/runner", + "testTimeout": 120000, + "testRegex": "\\.e2e\\.js$", + "reporters": ["detox/runners/jest/streamlineReporter"], + "verbose": true +} diff --git a/e2e/environment.js b/e2e/environment.js new file mode 100644 index 00000000..7f4fc942 --- /dev/null +++ b/e2e/environment.js @@ -0,0 +1,23 @@ +const { + DetoxCircusEnvironment, + SpecReporter, + WorkerAssignReporter, +} = require('detox/runners/jest-circus'); + +class CustomDetoxEnvironment extends DetoxCircusEnvironment { + constructor(config, context) { + super(config, context); + + // Can be safely removed, if you are content with the default value (=300000ms) + this.initTimeout = 300000; + + // This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level. + // This is strictly optional. + this.registerListeners({ + SpecReporter, + WorkerAssignReporter, + }); + } +} + +module.exports = CustomDetoxEnvironment; diff --git a/e2e/onboarding.e2e.js b/e2e/onboarding.e2e.js new file mode 100644 index 00000000..15fef8e6 --- /dev/null +++ b/e2e/onboarding.e2e.js @@ -0,0 +1,98 @@ +describe('Onboarding', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' } }); + }); + + beforeEach(async () => { + await device.reloadReactNative(); + }); + + it('Presentation', async () => { + await expect(element(by.id('screen-0'))).toBeVisible(); + + await expect(element(by.id('scroll-view'))).toBeVisible(); + + await element(by.id('scroll-view')).swipe('left', 'fast'); + await expect(element(by.id('screen-1'))).toBeVisible(); + await element(by.id('scroll-view')).swipe('left', 'fast'); + await expect(element(by.id('screen-2'))).toBeVisible(); + await element(by.id('scroll-view')).swipe('left', 'fast'); + await expect(element(by.id('screen-3'))).toBeVisible(); + + await element(by.id('scroll-view')).swipe('right', 'fast'); + await expect(element(by.id('screen-2'))).toBeVisible(); + await element(by.id('scroll-view')).swipe('right', 'fast'); + await expect(element(by.id('screen-1'))).toBeVisible(); + + await element(by.id('next-button')).tap(); + await expect(element(by.id('screen-2'))).toBeVisible(); + await element(by.id('next-button')).tap(); + await expect(element(by.id('screen-3'))).toBeVisible(); + + try { // check button disabled + await element(by.id('start-button')).tap(); + await expect(element(by.id('__unknown'))).toBeVisible(); + } catch(e) {} + + await element(by.id('check-box')).tap(); + + await element(by.id('start-button')).tap(); + }); + + it('Supported', async () => { + await element(by.id('not-followed-button')).tap(); + }); + + it('Symptoms', async () => { + await element(by.id('scroll-view')).scrollTo('bottom'); + + try { // check button disabled + await element(by.id('validate-button')).tap(); + await expect(element(by.id('__unknown'))).toBeVisible(); + } catch(e) {} + + await element(by.id('scroll-view')).scrollTo('top'); + + await element(by.id('check-box-mood')).tap(); + await element(by.id('check-box-anxiety')).tap(); + + await element(by.id('custom-input')).typeText('Custom'); + await element(by.id('custom-add-button')).tap(); + await expect(element(by.text('Custom'))).toBeVisible(); + + await element(by.id('scroll-view')).scrollTo('bottom'); + + await element(by.id('validate-button')).tap(); + }); + + it('Drugs', async () => { + await element(by.id('add-drugs-button')).tap(); + + await waitFor(element(by.text('Champix'))).toBeVisible().whileElement(by.id('scroll-view')).scroll(300, 'down', 0.5, 0.7); + + await element(by.id('check-box-champix')).tap(); + + await waitFor(element(by.text('Deroxat'))).toBeVisible().whileElement(by.id('scroll-view')).scroll(300, 'down', 0.5, 0.7); + + await element(by.id('check-box-deroxat')).tap(); + + await element(by.id('validate-button')).tap(); + + await expect(element(by.id('drug-item-deroxat'))).toBeVisible(); + await expect(element(by.id('drug-item-champix'))).toBeVisible(); + + await element(by.id('continue-button')).tap(); + }); + + it('Reminder', async () => { + await element(by.id('later-button')).tap(); + }); + + it('Final check', async () => { + await element(by.id('main-button')).tap(); + await element(by.id('day-button-0')).tap(); + await expect(element(by.text('Humeur'))).toBeVisible(); + await expect(element(by.text('Anxiété'))).toBeVisible(); + await expect(element(by.text('Custom'))).toBeVisible(); + }); +}); diff --git a/package.json b/package.json index aba5fe51..8f366a7f 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "ios:12promax": "react-native run-ios --simulator=\"iPhone 12 Pro Max\"", "start": "react-native start --reset-cache", "test": "jest", + "detox:ios": "detox build --configuration ios && detox test --configuration ios", "lint": "eslint .", "android:build-aab": "cd android && ./gradlew clean && ./gradlew bundleRelease && cd ../", "postinstall": "pod install --project-directory=ios/ --repo-update && npx jetify -r", @@ -63,8 +64,10 @@ "@babel/runtime": "^7.12.5", "@react-native-community/eslint-config": "^2.0.0", "babel-jest": "^26.6.3", + "detox": "^19.4.2", "eslint": "7.32.0", "jest": "^26.6.3", + "jest-circus": "^27.4.6", "metro-react-native-babel-preset": "^0.66.0", "react-native-codegen": "^0.0.12", "react-native-svg-transformer": "^0.14.3", diff --git a/src/components/AddElemToList.js b/src/components/AddElemToList.js index 4eaa512d..15c797d5 100644 --- a/src/components/AddElemToList.js +++ b/src/components/AddElemToList.js @@ -14,6 +14,7 @@ export default ({ placeholder = 'Ajouter...', styleContainer, onChangeText = console.log, + testID }) => { const [value, setValue] = useState(); @@ -29,6 +30,7 @@ export default ({ placeholder={placeholder} placeholderTextColor="lightgrey" style={styles.text} + testID={testID ? testID+'-input' : undefined} /> @@ -39,7 +41,8 @@ export default ({ onChange(value); setValue(''); onChangeText(''); - }}> + }} + testID={testID ? testID+'-add-button' : undefined}> { const color = disabled ? 'lightgrey' : buttonColor; return ( @@ -53,7 +54,8 @@ const Button = ({ buttonStyle, ]} onPress={onPress} - disabled={disabled}> + disabled={disabled} + testID={testID}> {title} diff --git a/src/scenes/diary/index.js b/src/scenes/diary/index.js index 9fdab57c..1e3a44a0 100644 --- a/src/scenes/diary/index.js +++ b/src/scenes/diary/index.js @@ -53,8 +53,13 @@ const Diary = ({navigation}) => { else { const isFirstAppLaunch = await localStorage.getIsFirstAppLaunch(); if (isFirstAppLaunch !== 'false') { - navigation.navigate('onboarding', { - screen: onboardingStep || 'OnboardingPresentation', + navigation.reset({ + routes: [ + { + name: 'onboarding', + params: {screen: onboardingStep || 'OnboardingPresentation'}, + }, + ], }); } } diff --git a/src/scenes/onboarding/onboarding.js b/src/scenes/onboarding/onboarding.js index d75e6c59..6350e797 100644 --- a/src/scenes/onboarding/onboarding.js +++ b/src/scenes/onboarding/onboarding.js @@ -51,7 +51,7 @@ const Onboarding = ({navigation}) => { Mon Suivi Psy m'accompagne entre mes consultations - + { // dirty hack because of this issue @@ -84,6 +84,7 @@ const Onboarding = ({navigation}) => { style={styles.checkbox} value={isCguChecked} onValueChange={(newValue) => setIsCguChecked(newValue)} + testID='check-box' /> En cochant cette case, vous acceptez nos{' '} @@ -106,18 +107,19 @@ const Onboarding = ({navigation}) => {