diff --git a/.circleci/config.yml b/.circleci/config.yml index 23123c95..1ef6fede 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,39 +1,128 @@ version: 2 -# Defaults +# ============================== +# DEFAULTS FOR JOBS +# ============================== + default job config: &defaults working_directory: ~/async_storage + +checkout step for each job: &addWorkspace + attach_workspace: + at: ~/async_storage + +# ============================== +# ENVIRONMENT VARIABLES +# ============================== + +default config for js: &js_defaults + <<: *defaults docker: - image: circleci/node:8 -checkout step for each job: &addWorkspace - attach_workspace: - at: ~/ +default config for macOS: &macos_defaults + <<: *defaults + resource_class: 'medium' + macos: + xcode: '10.1.0' + +default config for android apk builds: &android_defaults + <<: *defaults + docker: + - image: reactnativecommunity/react-native-android + resource_class: 'medium' + working_directory: ~/async_storage + environment: + - _JAVA_OPTIONS: '-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Xmx2048m' + - GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError -Xmx2048m"' + - BUILD_THREADS: 2 + +# ============================== +# CACHE CONFIG +# ============================== + +# brew +save brew cache: &cache_save_brew + name: Saving Brew cache + paths: + - /usr/local/Homebrew + - ~/Library/Caches/Homebrew + key: v1-brew-cache-{{ arch }} + +restore brew cache: &cache_restore_brew + name: Restoring Brew cache + keys: + - v1-brew-cache-{{ arch }} + +save brew cache for android: &cache_save_brew_android + name: Saving Brew cache for android + paths: + - /usr/local/Homebrew + - ~/Library/Caches/Homebrew + key: v1-brew-cache-{{ arch }}-android + +restore brew cache for android: &cache_restore_brew_android + name: Restoring Brew cache for android + keys: + - v1-brew-cache-{{ arch }}-android + +# yarn +save yarn cache: &cache_save_yarn + name: Saving Yarn cache + paths: + - ~/.cache/yarn + - ~/Library/Detox + key: v1-yarn-cache-{{ checksum "package.json" }}-{{ arch }} + +restore yarn cache: &cache_restore_yarn + name: Restoring Yarn cache + keys: + - v1-yarn-cache-{{ checksum "package.json" }}-{{ arch }} +# gradle +save gradle wrapper cache: &cache_save_gradle_wrapper + name: Saving Gradle Wrapper cache + paths: + - ~/.gradle/wrapper + key: gradle-wrapper-v1-{{ checksum "example/android/gradle/wrapper/gradle-wrapper.properties" }} + +save gradle build cache: &cache_save_gradle_build + name: Saving Gradle app/build cache + paths: + - ~/.gradle/caches + key: app-build-gradle-v1-{{ checksum "example/android/app/build.gradle" }} + +restore gradle wrapper cache: &cache_restore_gradle_wrapper + name: Restoring Gradle Wrapper cache + keys: + - gradle-wrapper-v1-{{ checksum "example/android/gradle/wrapper/gradle-wrapper.properties" }} + +restore gradle build cache: &cache_restore_gradle_build + name: Restoring Gradle app/build cache + keys: + - app-build-gradle-v1-{{ checksum "example/android/app/build.gradle" }} + + +# ============================== +# JOBS +# ============================== jobs: "Setup environment": - <<: *defaults + <<: *js_defaults steps: - checkout - - restore_cache: - name: Restore node modules - keys: - - node_modules-{{ checksum "yarn.lock" }}-{{ checksum "package.json" }}-{{ arch }} + - restore-cache: *cache_restore_yarn - run: name: Install dependencies - command: yarn --pure-lockfile --non-interactive - - save_cache: - name: Save node modules - key: node_modules-{{ checksum "yarn.lock" }}-{{ checksum "package.json" }}-{{ arch }} - paths: - - node_modules + command: yarn --pure-lockfile --non-interactive --cache-folder ~/.cache/yarn + - save-cache: *cache_save_yarn - persist_to_workspace: root: . paths: . "Test: lint": - <<: *defaults + <<: *js_defaults steps: - *addWorkspace - run: @@ -41,16 +130,160 @@ jobs: command: yarn test:lint "Test: flow": - <<: *defaults + <<: *js_defaults steps: - *addWorkspace - run: name: Flow check command: yarn test:flow + "Test: iOS e2e": + <<: *macos_defaults + steps: + - *addWorkspace + - restore-cache: *cache_restore_brew + - run: + name: Configure OSX Environment + command: | + HOMEBREW_NO_AUTO_UPDATE=1 brew install node@8 >/dev/null + HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null + HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null + touch .watchmanconfig + node -v + - save-cache: *cache_save_brew + - restore-cache: *cache_restore_yarn + - run: + name: Installing Yarn dependencies + command: yarn --pure-lockfile --non-interactive --cache-folder ~/.cache/yarn + - save-cache: *cache_save_yarn + - run: + name: Build iOS app + command: yarn build:e2e:ios + + - run: + name: Run e2e tests + command: yarn test:e2e:ios + + "Build: Android release apk": + <<: *android_defaults + steps: + - *addWorkspace + - restore-cache: *cache_restore_yarn + - run: + name: Installing Yarn dependencies + command: yarn --pure-lockfile --non-interactive --cache-folder ~/.cache/yarn + - save-cache: *cache_save_yarn + + # Gradle + - restore-cache: *cache_restore_gradle_wrapper + - restore-cache: *cache_restore_gradle_build + - run: + name: Downloading Gradle dependencies + command: cd example/android && ./gradlew --max-workers 2 fetchDependencies + - save-cache: *cache_save_gradle_wrapper + - save-cache: *cache_save_gradle_build + + # Build and test + - run: + name: Build Android apk + command: cd example/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release --max-workers 2 + + - persist_to_workspace: + root: ~/async_storage + paths: + - example/android/app/build/outputs/apk/* + + "Test: Android e2e": + <<: *macos_defaults + steps: + - *addWorkspace + - run: + name: Configure env variables + command: | + echo 'export ANDROID_HOME="/usr/local/share/android-sdk"' >> $BASH_ENV + echo 'export ANDROID_SDK_ROOT="/usr/local/share/android-sdk"' >> $BASH_ENV + echo 'export PATH="$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/tools:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/tools/bin:$PATH"' >> $BASH_ENV + echo 'export QEMU_AUDIO_DRV=none' >> $BASH_ENV + echo 'export JAVA_HOME=/Library/Java/Home' >> $BASH_ENV + source $BASH_ENV + + # Android tools + - restore-cache: *cache_restore_brew_android + - run: + name: Install Android SDK tools + command: | + HOMEBREW_NO_AUTO_UPDATE=1 brew tap homebrew/cask >/dev/null + HOMEBREW_NO_AUTO_UPDATE=1 brew cask install android-sdk >/dev/null + HOMEBREW_NO_AUTO_UPDATE=1 brew cask install intel-haxm >/dev/null + HOMEBREW_NO_AUTO_UPDATE=1 brew install node@8 >/dev/null >/dev/null + - save-cache: *cache_save_brew_android + + - run: + name: Install Android emulator + shell: /bin/bash -e + command: | + yes | sdkmanager "platform-tools" "tools" >/dev/null + yes | sdkmanager "platforms;android-28" "system-images;android-21;google_apis;x86" >/dev/null + yes | sdkmanager "emulator" --channel=3 >/dev/null + yes | sdkmanager "build-tools;28.0.3" >/dev/null + yes | sdkmanager --licenses >/dev/null + yes | sdkmanager --list + + - run: + name: ADB start/stop + command: | + adb start-server + adb devices + adb kill-server + ls -la ~/.android + + - run: + name: Create emulator + command: | + avdmanager create avd \ + --force \ + -n "Emu_E2E" \ + -k "system-images;android-21;google_apis;x86" \ + -g "google_apis" \ + -d "Nexus 4" + + + + - run: + name: Start emulator in background + background: true + command: | + emulator -avd "Emu_E2E" \ + -cores 1 \ + -gpu auto \ + -accel on \ + -memory 1024 \ + -no-audio \ + -no-snapshot \ + -no-boot-anim \ + -no-window \ + -logcat '*:W' | grep -i "ReactNative" + + - run: + name: Wait for emulator to boot + command: yarn build:e2e:android + + - run: + name: Wake device + command: | + adb shell input keyevent + adb shell input keyevent 82 & + + - run: + name: Run e2e tests + command: yarn test:e2e:android + +# ============================== +# WORK FLOWS +# ============================== workflows: version: 2 - "Basic check": + "Testing": jobs: - "Setup environment" - "Test: lint": @@ -58,4 +291,17 @@ workflows: - "Setup environment" - "Test: flow": requires: - - "Setup environment" \ No newline at end of file + - "Setup environment" + - "Test: iOS e2e": + requires: + - "Test: lint" + - "Test: flow" + - "Build: Android release apk": + requires: + - "Test: lint" + - "Test: flow" + - "Test: Android e2e": + requires: + - "Test: lint" + - "Test: flow" + - "Build: Android release apk" \ No newline at end of file diff --git a/.circleci/scripts/run_android_e2e.sh b/.circleci/scripts/run_android_e2e.sh new file mode 100755 index 00000000..bf4aa64c --- /dev/null +++ b/.circleci/scripts/run_android_e2e.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# On CI, waits for emu to be booted +# Locally, builds apk + +ROOT_DIR=$PWD + +INTERVAL=5 # 5 secs between each check +MAX_RETRIES=60 # wait max 5 minutes for emu to boot + +build_apk() { + echo + echo "[Detox e2e] Building APK" + cd "example/android" + eval "./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release" + cd ${ROOT_DIR} +} + +wait_for_emulator_to_boot() { + isBooted=$(adb shell getprop sys.boot_completed 2>&1 | tr -d '\r') + retriesLeft=${MAX_RETRIES} + + echo + echo "[Detox e2e] Checking if emulator is booted up." + + while [[ "$isBooted" != "1" ]]; do + + if [[ ${retriesLeft} -eq 0 ]]; then + echo "[Detox e2e] Seems like emulator could not be booted." 1>&2 + exit 125 + fi + + isBooted=$(adb shell getprop sys.boot_completed 2>&1 | tr -d '\r') + + retriesLeft=$((retriesLeft - 1)) + echo "[Detox e2e] $retriesLeft checks left." + sleep ${INTERVAL} + done + + echo "[Detox e2e] Emulator booted." +} + +if [[ -n $CIRCLECI ]]; then + wait_for_emulator_to_boot # Run it on CI +else + build_apk # Run locally +fi diff --git a/.circleci/scripts/run_ios_e2e.sh b/.circleci/scripts/run_ios_e2e.sh new file mode 100755 index 00000000..f3d1b20d --- /dev/null +++ b/.circleci/scripts/run_ios_e2e.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +RESOURCE_DIR="$PWD/example/ios/build/Build/Products/Release-iphonesimulator/AsyncStorageExample.app" +ENTRY_FILE="example/index.js" +BUNDLE_FILE="$RESOURCE_DIR/main.jsbundle" +EXTRA_PACKAGER_ARGS="--entry-file=$ENTRY_FILE" + + +build_project() { + echo "[Detox e2e] Building iOS project" + eval "xcodebuild \ + -project example/ios/AsyncStorageExample.xcodeproj \ + -scheme AsyncStorageExample \ + -configuration Release \ + -sdk iphonesimulator \ + -derivedDataPath example/ios/build \ + -UseModernBuildSystem=NO \ + BUNDLE_FILE=$BUNDLE_FILE \ + EXTRA_PACKAGER_ARGS=$EXTRA_PACKAGER_ARGS" +} + +run_simulator() { + if [[ -n $1 ]]; then + deviceName=$1 + else + echo "[Detox e2e] Device name not passed!" >&2; + exit; + fi + + + if [[ $2 = "headless" ]]; then + runHeadless=1 + else + runHeadless=0 + fi + + # Find simulator + devDir=`xcode-select -p` + devDir=$devDir/Applications/Simulator.app + + # parse output + availableDevices=$( + eval "xcrun simctl list devices" |\ + eval "sed '/"$deviceName"/!d'" |\ + eval "sed '/unavailable/d'" |\ + eval "sed 's/(Shutdown)//; s/(Shutting Down)//; s/(Booted)//; s/ (/*/; s/)//'" + ) + + IFS='*' read -a deviceInfo <<< "$availableDevices" + + if [[ $deviceInfo == "" ]]; then + echo "[Detox e2e] Could not find device: $deviceName" >&2 + exit; + fi + + + deviceUUID=${deviceInfo[1]} + + echo "[Detox e2e] Booting up $deviceName (id: $deviceUUID)" + + # Booting emulator in headless mode + eval "open $devDir --args -CurrentDeviceUDID $deviceUUID" + + # Decide if should run headless or not + if [ "$runHeadless" -eq 0 ]; then + eval "xcrun instruments -w $deviceUUID" >/dev/null 2>&1 + else + echo "[Detox e2e] Running simulator in headless mode." + fi +} + + +build_project + +sleep 2 + +run_simulator "$1" "$2" + +sleep 10 + +exit 0 diff --git a/.eslintrc b/.eslintrc index 61b3a62c..cc8c9cdb 100644 --- a/.eslintrc +++ b/.eslintrc @@ -51,7 +51,10 @@ "setInterval": false, "setTimeout": false, "window": false, - "XMLHttpRequest": false + "XMLHttpRequest": false, + "device": true, + "element": true, + "by": true }, "rules": { @@ -171,7 +174,7 @@ // Prettier Plugin // https://github.com/prettier/eslint-plugin-prettier - "prettier/prettier": [2, "fb", "@format"], + "prettier/prettier": "error", // Stylistic Issues // These rules are purely matters of style and are quite subjective. diff --git a/android/build.gradle b/android/build.gradle index 436e8dec..1d0cb61c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -35,6 +35,6 @@ repositories { } dependencies { - api 'com.facebook.react:react-native:+' + implementation 'com.facebook.react:react-native:+' } \ No newline at end of file diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 5af21ec5..692458fc 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -72,10 +72,14 @@ import com.android.build.OutputFile * ] */ + project.ext.react = [ - entryFile: "index.js" + cliPath: "../node_modules/react-native/local-cli/cli.js", + entryFile: "example/index.js" ] + +apply from: "./scripts/CI.gradle" apply from: "../../../node_modules/react-native/react.gradle" /** @@ -97,12 +101,24 @@ android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion + signingConfigs { + release { + storeFile file("exampleAsyncStorage.keystore") + keyAlias "asyncstorage" + keyPassword "asyncstorage" + storePassword "asyncstorage" + } + } + defaultConfig { applicationId "com.asyncstorageexample" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" + testBuildType System.getProperty('testBuildType', 'debug') + missingDimensionStrategy "minReactNative", "minReactNative46" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } splits { abi { @@ -116,6 +132,8 @@ android { release { minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + signingConfig signingConfigs.release + matchingFallbacks = ['release'] } } // applicationVariants are e.g. debug, release @@ -137,6 +155,12 @@ dependencies { implementation project(':rnAsyncStorage') implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" implementation "com.facebook.react:react-native:+" // From node_modules + + // tests + androidTestImplementation project(":detox") + androidTestImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test:rules:1.0.2' } // Run this once to be able to run the application with BUCK diff --git a/example/android/app/exampleAsyncStorage.keystore b/example/android/app/exampleAsyncStorage.keystore new file mode 100644 index 00000000..e77450d7 Binary files /dev/null and b/example/android/app/exampleAsyncStorage.keystore differ diff --git a/example/android/app/scripts/CI.gradle b/example/android/app/scripts/CI.gradle new file mode 100644 index 00000000..878ee24f --- /dev/null +++ b/example/android/app/scripts/CI.gradle @@ -0,0 +1,25 @@ +// Fetch all dependencies upfront +task fetchDependencies() { + description 'Download all dependencies to the Gradle cache' + group 'android' + doLast { + project.rootProject.allprojects.each { subProject -> + subProject.buildscript.configurations.each {config -> + if(config.canBeResolved) { + config.files + } + } + subProject.configurations.each {config -> + if(config.canBeResolved) { + // DefaultLenientConfiguration$ArtifactResolveException + try { + config.files + } catch(e) { + println e + } + + } + } + } + } +} \ No newline at end of file diff --git a/example/android/app/src/androidTest/java/com/asyncstorageexample/DetoxTest.java b/example/android/app/src/androidTest/java/com/asyncstorageexample/DetoxTest.java new file mode 100644 index 00000000..eeb9ae91 --- /dev/null +++ b/example/android/app/src/androidTest/java/com/asyncstorageexample/DetoxTest.java @@ -0,0 +1,24 @@ +package com.asyncstorageexample; + +import android.support.test.filters.LargeTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; + +import com.wix.detox.Detox; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class DetoxTest { + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); + + @Test + public void runDetoxTests() throws InterruptedException { + Detox.runTests(mActivityRule); + } +} \ No newline at end of file diff --git a/example/android/build.gradle b/example/android/build.gradle index f84862ff..050db5f6 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -2,11 +2,13 @@ buildscript { ext { + kotlinVersion = '1.3.0' buildToolsVersion = "28.0.3" - minSdkVersion = 16 + minSdkVersion = 19 compileSdkVersion = 28 targetSdkVersion = 27 supportLibVersion = "28.0.0" + detoxKotlinVersion = kotlinVersion } repositories { google() @@ -14,9 +16,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 1d2109a8..9791199f 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -2,5 +2,8 @@ rootProject.name = 'AsyncStorageExample' include ':app' include ':rnAsyncStorage' +include ':detox' + project(':rnAsyncStorage').projectDir = new File(rootProject.projectDir, '../../android') +project(':detox').projectDir = new File(rootProject.projectDir, '../../node_modules/detox/android/detox') \ No newline at end of file diff --git a/example/e2e/asyncstorage.spec.js b/example/e2e/asyncstorage.spec.js new file mode 100644 index 00000000..5d1b4f50 --- /dev/null +++ b/example/e2e/asyncstorage.spec.js @@ -0,0 +1,142 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +describe('Async Storage', () => { + let restartButton; + let closeKeyboard; + let test_getSetClear; + let test_mergeItem; + + beforeAll(async () => { + await device.reloadReactNative(); + restartButton = await element(by.id('restart_button')); + closeKeyboard = await element(by.id('closeKeyboard')); + test_getSetClear = await element(by.id('testType_getSetClear')); + test_mergeItem = await element(by.id('testType_mergeItem')); + }); + + it('should load default screen', async () => { + await expect(restartButton).toExist(); + await expect(closeKeyboard).toExist(); + await expect(test_getSetClear).toExist(); + await expect(test_mergeItem).toExist(); + }); + + describe('get / set / clear item test', () => { + it('should be visible', async () => { + await test_getSetClear.tap(); + await expect(element(by.id('clear_button'))).toExist(); + await expect(element(by.id('increaseByTen_button'))).toExist(); + await expect(element(by.id('storedNumber_text'))).toExist(); + }); + it('should store value in async storage', async () => { + const storedNumberText = await element(by.id('storedNumber_text')); + const increaseByTenButton = await element(by.id('increaseByTen_button')); + + await expect(storedNumberText).toHaveText(''); + + const tapTimes = Math.round(Math.random() * 9) + 1; + + for (let i = 0; i < tapTimes; i++) { + await increaseByTenButton.tap(); + } + + await expect(storedNumberText).toHaveText(`${tapTimes * 10}`); + await restartButton.tap(); + await expect(storedNumberText).toHaveText(`${tapTimes * 10}`); + }); + + it('should clear item', async () => { + const storedNumberText = await element(by.id('storedNumber_text')); + const increaseByTenButton = await element(by.id('increaseByTen_button')); + const clearButton = await element(by.id('clear_button')); + + await increaseByTenButton.tap(); + await clearButton.tap(); + await restartButton.tap(); + await expect(storedNumberText).toHaveText(''); + }); + }); + + describe('merge item test', () => { + it('should be visible', async () => { + await test_mergeItem.tap(); + await expect(element(by.id('saveItem_button'))).toExist(); + await expect(element(by.id('mergeItem_button'))).toExist(); + await expect(element(by.id('restoreItem_button'))).toExist(); + await expect(element(by.id('testInput-name'))).toExist(); + await expect(element(by.id('testInput-age'))).toExist(); + await expect(element(by.id('testInput-eyes'))).toExist(); + await expect(element(by.id('testInput-shoe'))).toExist(); + }); + + it('should merge items in async storage', async () => { + const buttonSaveItem = await element(by.id('saveItem_button')); + const buttonMergeItem = await element(by.id('mergeItem_button')); + const buttonRestoreItem = await element(by.id('restoreItem_button')); + + const nameInput = await element(by.id('testInput-name')); + const ageInput = await element(by.id('testInput-age')); + const eyesInput = await element(by.id('testInput-eyes')); + const shoeInput = await element(by.id('testInput-shoe')); + const storyText = await element(by.id('storyTextView')); + + const isAndroid = device.getPlatform() === 'android'; + + async function performInput() { + const name = Math.random() > 0.5 ? 'Jerry' : 'Sarah'; + const age = Math.random() > 0.5 ? '21' : '23'; + const eyesColor = Math.random() > 0.5 ? 'blue' : 'green'; + const shoeSize = Math.random() > 0.5 ? '9' : '10'; + + if (!isAndroid) { + await eyesInput.tap(); + } + await nameInput.typeText(name); + await closeKeyboard.tap(); + + if (!isAndroid) { + await eyesInput.tap(); + } + await ageInput.typeText(age); + await closeKeyboard.tap(); + + if (!isAndroid) { + await eyesInput.tap(); + } + await eyesInput.typeText(eyesColor); + await closeKeyboard.tap(); + + if (!isAndroid) { + await eyesInput.tap(); + } + await shoeInput.typeText(shoeSize); + await closeKeyboard.tap(); + + return `${name} is ${age}, has ${eyesColor} eyes and shoe size of ${shoeSize}.`; + } + + const story = await performInput(); + await buttonSaveItem.tap(); + await restartButton.tap(); + await buttonRestoreItem.tap(); + expect(storyText).toHaveText(story); + await restartButton.tap(); + + // merging here + + const newStory = await performInput(); + + await buttonMergeItem.tap(); + await restartButton.tap(); + await buttonRestoreItem.tap(); + expect(storyText).toHaveText(newStory); + }); + }); +}); diff --git a/example/e2e/config.json b/example/e2e/config.json new file mode 100644 index 00000000..38036d30 --- /dev/null +++ b/example/e2e/config.json @@ -0,0 +1,4 @@ +{ + "setupFilesAfterEnv": ["./init.js"], + "testEnvironment": "node" +} \ No newline at end of file diff --git a/example/e2e/init.js b/example/e2e/init.js new file mode 100644 index 00000000..78be9377 --- /dev/null +++ b/example/e2e/init.js @@ -0,0 +1,19 @@ +const detox = require('detox'); +const config = require('../../package.json').detox; +const adapter = require('detox/runners/jest/adapter'); + +jest.setTimeout(120000); +jasmine.getEnv().addReporter(adapter); + +beforeAll(async () => { + await detox.init(config); +}); + +beforeEach(async () => { + await adapter.beforeEach(); +}); + +afterAll(async () => { + await adapter.afterAll(); + await detox.cleanup(); +}); diff --git a/example/src/App.js b/example/src/App.js index d5c10fbc..b21a713c 100644 --- a/example/src/App.js +++ b/example/src/App.js @@ -15,65 +15,95 @@ import { Text, TouchableOpacity, View, + ScrollView, + Keyboard, + Button, } from 'react-native'; -import SimpleGetSet from './examples/GetSet'; -import ClearStorage from './examples/ClearSingle'; +import GetSetClear from './examples/GetSetClear'; +import MergeItem from './examples/MergeItem'; -const EXAMPLES = [ - { +const TESTS = { + GetSetClear: { title: 'Simple Get/Set value', + testId: 'get-set-clear', description: 'Store and retrieve persisted data', render() { - return ; + return ; }, }, - { - title: 'Clear', - description: 'Clear persisting data storage', + MergeItem: { + title: 'Merge item', + testId: 'merge-item', + description: 'Merge object with already stored data', render() { - return ; + return ; }, }, -]; +}; type Props = {}; -type State = {restarting: boolean}; +type State = {restarting: boolean, currentTest: Object}; export default class App extends Component { state = { restarting: false, + currentTest: TESTS.GetSetClear, }; _simulateRestart = () => { this.setState({restarting: true}, () => this.setState({restarting: false})); }; + _changeTest = testName => { + this.setState({currentTest: TESTS[testName]}); + }; + render() { - const {restarting} = this.state; + const {restarting, currentTest} = this.state; return ( Keyboard.dismiss()} + testID="closeKeyboard" + /> + + Simulate Restart - {restarting - ? null - : EXAMPLES.map(example => { - return ( - - {example.title} - - {example.description} - - - {example.render()} - - - ); - })} + + +