├── .babelrc ├── .buckconfig ├── .editorconfig ├── .env ├── .env.automation ├── .eslintrc ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .watchmanconfig ├── App.js ├── LICENSE.txt ├── README.md ├── __tests__ ├── appium │ ├── config │ │ ├── helpers │ │ │ ├── after.scenario.js │ │ │ └── report.hook.js │ │ ├── wdio.android.conf.js │ │ ├── wdio.ios.conf.js │ │ └── wdio.shared.conf.js │ ├── features │ │ ├── chats.feature │ │ ├── navigation.feature │ │ └── webview.feature │ ├── screen-objects │ │ ├── base.js │ │ ├── chat.js │ │ ├── navigation.js │ │ └── webview.js │ ├── step_definitions │ │ ├── base.steps.js │ │ ├── chat.steps.js │ │ ├── navigation.steps.js │ │ └── webview.steps.js │ └── support │ │ ├── constants.js │ │ ├── expectations.js │ │ └── utils.js └── detox │ ├── config │ ├── detox.json │ └── setup.js │ ├── features │ ├── chats.spec.js │ ├── navigation.spec.js │ └── webview.spec.js │ ├── screen-objects │ ├── base.js │ ├── chats.js │ ├── navigation.js │ └── webview.js │ └── support │ └── constants.js ├── android ├── app │ ├── BUCK │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── fonts │ │ │ ├── Entypo.ttf │ │ │ ├── EvilIcons.ttf │ │ │ ├── Feather.ttf │ │ │ ├── FontAwesome.ttf │ │ │ ├── Foundation.ttf │ │ │ ├── Ionicons.ttf │ │ │ ├── MaterialCommunityIcons.ttf │ │ │ ├── MaterialIcons.ttf │ │ │ ├── Octicons.ttf │ │ │ ├── RobotoMono-Bold.ttf │ │ │ ├── RobotoMono-BoldItalic.ttf │ │ │ ├── RobotoMono-Italic.ttf │ │ │ ├── RobotoMono-Light.ttf │ │ │ ├── RobotoMono-LightItalic.ttf │ │ │ ├── RobotoMono-Medium.ttf │ │ │ ├── RobotoMono-MediumItalic.ttf │ │ │ ├── RobotoMono-Regular.ttf │ │ │ ├── RobotoMono-Thin.ttf │ │ │ ├── RobotoMono-ThinItalic.ttf │ │ │ ├── SimpleLineIcons.ttf │ │ │ └── Zocial.ttf │ │ ├── java │ │ └── com │ │ │ └── wswebcreation │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── drawable-hdpi │ │ └── screen.png │ │ ├── drawable-mdpi │ │ └── screen.png │ │ ├── drawable-xhdpi │ │ └── screen.png │ │ ├── drawable-xxhdpi │ │ └── screen.png │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-ldpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystores │ ├── BUCK │ └── debug.keystore.properties └── settings.gradle ├── app.json ├── app ├── assets │ ├── fonts │ │ └── Roboto_Mono │ │ │ ├── LICENSE.txt │ │ │ ├── RobotoMono-Bold.ttf │ │ │ ├── RobotoMono-BoldItalic.ttf │ │ │ ├── RobotoMono-Italic.ttf │ │ │ ├── RobotoMono-Light.ttf │ │ │ ├── RobotoMono-LightItalic.ttf │ │ │ ├── RobotoMono-Medium.ttf │ │ │ ├── RobotoMono-MediumItalic.ttf │ │ │ ├── RobotoMono-Regular.ttf │ │ │ ├── RobotoMono-Thin.ttf │ │ │ └── RobotoMono-ThinItalic.ttf │ └── loader.gif ├── components │ ├── BorderText.js │ ├── ChatInput.js │ ├── CustomHeader.js │ └── MessageBubble.js ├── config │ ├── Api.js │ ├── AutomationRestart.js │ ├── Constants.js │ ├── TestProperties.js │ ├── labels.json │ └── router.js └── screens │ ├── ChatBox.js │ ├── Chats.js │ ├── Home.js │ ├── Webview.js │ └── WebviewSelection.js ├── assets ├── wswebcreation-android.gif ├── wswebcreation-animation.gif ├── wswebcreation-deeplinking-chatbox.gif ├── wswebcreation-deeplinking.gif ├── wswebcreation-disable-animation.gif ├── wswebcreation-icon.png ├── wswebcreation-ios.gif └── wswebcreation-storybook.gif ├── index.js ├── ios ├── wswebcreation-tvOS │ └── Info.plist ├── wswebcreation-tvOSTests │ └── Info.plist ├── wswebcreation.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── automation.build.xcscheme │ │ ├── wswebcreation-tvOS.xcscheme │ │ └── wswebcreation.xcscheme ├── wswebcreation │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-1024@1x.png │ │ │ ├── icon-20@1x.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-29@1x.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-40@1x.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-40@3x.png │ │ │ ├── icon-50@1x.png │ │ │ ├── icon-50@2x.png │ │ │ ├── icon-57@1x.png │ │ │ ├── icon-57@2x.png │ │ │ ├── icon-60@2x.png │ │ │ ├── icon-60@3x.png │ │ │ ├── icon-72@1x.png │ │ │ ├── icon-72@2x.png │ │ │ ├── icon-76@1x.png │ │ │ ├── icon-76@2x.png │ │ │ └── icon-83.5@2x.png │ │ ├── Contents.json │ │ └── LaunchImage.launchimage │ │ │ ├── Contents.json │ │ │ ├── Default-1242@3x~iphone6s-landscape_2208x1242.png │ │ │ ├── Default-1242@3x~iphone6s-portrait_1242x2208.png │ │ │ ├── Default-568h@2x~iphone_640x1136.png │ │ │ ├── Default-750@2x~iphone6-landscape_1334x750.png │ │ │ ├── Default-750@2x~iphone6-portrait_750x1334.png │ │ │ ├── Default-Landscape@2x~ipad_2048x1496.png │ │ │ ├── Default-Landscape@2x~ipad_2048x1536.png │ │ │ ├── Default-Landscape~ipad_1024x748.png │ │ │ ├── Default-Landscape~ipad_1024x768.png │ │ │ ├── Default-Portrait@2x~ipad_1536x2008.png │ │ │ ├── Default-Portrait@2x~ipad_1536x2048.png │ │ │ ├── Default-Portrait~ipad_768x1024.png │ │ │ ├── Default.png │ │ │ ├── Default@2x~iphone_640x960.png │ │ │ ├── Default~ipad.png │ │ │ └── Default~iphone.png │ ├── Info.plist │ └── main.m └── wswebcreationTests │ ├── Info.plist │ └── wswebcreationTests.m ├── package-lock.json ├── package.json └── storybook ├── addons.js ├── decorators ├── centerDecorator.js └── defaultDecorator.js ├── index.android.js ├── index.ios.js ├── index.js ├── stories ├── Components │ ├── AnimationStory.js │ ├── BorderTextStory.js │ ├── ChatInputStory.js │ ├── CustomHeaderStory.js │ ├── MessageBubbleStory.js │ └── index.js ├── Welcome │ └── index.js └── index.js ├── storybook.app.js └── storybook.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["module:metro-react-native-babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | max_line_length = 140 11 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BASE_URL=https://gist.githubusercontent.com/wswebcreation/ 2 | ENVIRONMENT=development 3 | -------------------------------------------------------------------------------- /.env.automation: -------------------------------------------------------------------------------- 1 | BASE_URL=https://gist.githubusercontent.com/wswebcreation/ 2 | ENVIRONMENT=automation 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": [ 4 | "react-native" 5 | ], 6 | "env": { 7 | "jest": true 8 | }, 9 | "extends": [ 10 | "airbnb", 11 | "plugin:react-native/all" 12 | ], 13 | "settings": { 14 | "import/resolver": "react-native" 15 | }, 16 | "globals": { 17 | "fetch": false, 18 | "window": true, 19 | "document": true, 20 | "device": true, 21 | "$": true, 22 | "$$": true, 23 | "browser": true 24 | }, 25 | "rules": { 26 | "no-use-before-define": [0], 27 | "import/no-extraneous-dependencies": ["error", {"devDependencies": ["__tests__/**/*.js", "__mocks__/**/*.js" , "storybook/**/*.js", "app/config/ReactotronConfig.js"]}], 28 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] } ], 29 | "react/jsx-no-literals": [1, { "noStrings": true }], 30 | "react-native/no-color-literals": [0] , 31 | "react-native/no-inline-styles": [0], 32 | "react-native/split-platform-components": [0], 33 | "react/jsx-no-bind": [0], 34 | "no-underscore-dangle": [0], 35 | "max-len": [2, 140] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | 16 | ; Ignore polyfills 17 | .*/Libraries/polyfills/.* 18 | 19 | [include] 20 | 21 | [libs] 22 | node_modules/react-native/Libraries/react-native/react-native-interface.js 23 | node_modules/react-native/flow/ 24 | 25 | [options] 26 | emoji=true 27 | 28 | esproposal.optional_chaining=enable 29 | esproposal.nullish_coalescing=enable 30 | 31 | module.system=haste 32 | module.system.haste.use_name_reducers=true 33 | # get basename 34 | module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' 35 | # strip .js or .js.flow suffix 36 | module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' 37 | # strip .ios suffix 38 | module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' 39 | module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' 40 | module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' 41 | module.system.haste.paths.blacklist=.*/__tests__/.* 42 | module.system.haste.paths.blacklist=.*/__mocks__/.* 43 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* 44 | module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* 45 | 46 | munge_underscores=true 47 | 48 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 49 | 50 | suppress_type=$FlowIssue 51 | suppress_type=$FlowFixMe 52 | suppress_type=$FlowFixMeProps 53 | suppress_type=$FlowFixMeState 54 | suppress_type=$FixMe 55 | 56 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 57 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 58 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 59 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 60 | 61 | [version] 62 | ^0.78.0 63 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | android/app/gradle/ 33 | android/app/gradlew 34 | android/app/gradlew.bat 35 | 36 | # node.js 37 | # 38 | node_modules/ 39 | npm-debug.log 40 | yarn-error.log 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | 47 | # fastlane 48 | # 49 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 50 | # screenshots whenever they are needed. 51 | # For more information about the recommended setup visit: 52 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 53 | */fastlane/report.xml 54 | */fastlane/Preview.html 55 | */fastlane/screenshots 56 | 57 | # Bundle artifact 58 | *.jsbundle 59 | 60 | # E2E 61 | .tmp/ 62 | .dist/ 63 | appium.log 64 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | StyleSheet, 4 | View, 5 | } from 'react-native'; 6 | import { StackMainNavigation } from './app/config/router'; 7 | import AutomationRestartLink from './app/config/AutomationRestart'; 8 | 9 | export default class WsWebcreationApp extends Component<{}> { 10 | render() { 11 | return ( 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } 19 | 20 | const styles = StyleSheet.create({ 21 | container: { 22 | backgroundColor: '#f0eff5', 23 | flex: 1, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 wswebcreation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__tests__/appium/config/helpers/after.scenario.js: -------------------------------------------------------------------------------- 1 | import { After, Status } from 'cucumber'; 2 | import { ensureDirSync } from 'fs-extra'; 3 | import { resolve } from 'path'; 4 | import { switchToContext } from '../../screen-objects/webview'; 5 | import { CONTEXT_REF, SCREENSHOTS_FOLDERS } from '../../support/constants'; 6 | 7 | After(function (scenarioResult) { 8 | const world = this; 9 | // Always set it to false 10 | device.options.firstAppStart = false; 11 | return (scenarioResult.status === Status.FAILED) 12 | ? saveFailedScenarioScreenshot(world, scenarioResult) 13 | : scenarioResult.status; 14 | }); 15 | 16 | /** 17 | * Always turn back the context to native when we are testing with the webview 18 | */ 19 | After('@webview', () => { 20 | try { 21 | switchToContext(CONTEXT_REF.NATIVE); 22 | } catch (error) { 23 | // Do nothing 24 | } 25 | }); 26 | 27 | /** 28 | * Save a screenshot when a scenario failed 29 | */ 30 | function saveFailedScenarioScreenshot(world, scenarioResult) { 31 | const screenshotPath = resolve(process.cwd(), SCREENSHOTS_FOLDERS.TMP); 32 | ensureDirSync(screenshotPath); 33 | const fileName = `${scenarioResult.scenario.name 34 | .replace(/[^a-zA-Z0-9\s]/g, '') 35 | .replace(/\s/g, '-') 36 | .toLowerCase() 37 | .substr(0, 100)}.png`; 38 | const filepath = resolve(screenshotPath, fileName); 39 | const screenshot = device.saveScreenshot(filepath); 40 | 41 | world.attach(screenshot, 'image/png'); 42 | 43 | // Restart the app the next time 44 | device.options.restartApp = true; 45 | 46 | return scenarioResult.status; 47 | } 48 | -------------------------------------------------------------------------------- /__tests__/appium/config/helpers/report.hook.js: -------------------------------------------------------------------------------- 1 | import Cucumber, { registerListener } from 'cucumber'; 2 | import { ensureDirSync, writeJsonSync } from 'fs-extra'; 3 | import { join } from 'path'; 4 | 5 | const jsonFormatter = new Cucumber.JsonFormatter(); 6 | const projectRoot = process.cwd(); 7 | 8 | /** 9 | * This hook is needed to generate a json-file for the reporting 10 | */ 11 | registerListener(jsonFormatter); 12 | 13 | generateAndSaveJsonFile(); 14 | 15 | /** 16 | * Generate and save the report json files 17 | */ 18 | function generateAndSaveJsonFile() { 19 | jsonFormatter.log = (report) => { 20 | adjustAndSaveJsonFile(device.desiredCapabilities, report); 21 | }; 22 | } 23 | 24 | /** 25 | * Adjust and save the json files 26 | */ 27 | function adjustAndSaveJsonFile(capabilities, report) { 28 | const jsonReport = JSON.parse(report); 29 | if (jsonReport.length > 0) { 30 | const featureName = jsonReport[0].name.replace(/\s+/g, '_').replace(/\W/g, '').toLowerCase() || 'noName'; 31 | const snapshotPath = join(projectRoot, '.tmp/json-output'); 32 | const filePath = join(snapshotPath, `${featureName}.${capabilities.deviceName}.${(new Date).getTime()}.json`); // eslint-disable-line 33 | jsonReport[0].metadata = { 34 | app: { 35 | name: 'wswebcreation-app', 36 | version: '0.1.0', 37 | }, 38 | device: capabilities.deviceName, 39 | platform: { 40 | name: capabilities.platformName, 41 | version: capabilities.platformVersion, 42 | }, 43 | }; 44 | 45 | ensureDirSync(snapshotPath); 46 | 47 | writeJsonSync(filePath, jsonReport, { spaces: 2 }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /__tests__/appium/config/wdio.android.conf.js: -------------------------------------------------------------------------------- 1 | const config = require('./wdio.shared.conf').config; 2 | 3 | const ANDROID_APP_PATH = './android/app/build/outputs/apk/app-debug.apk'; 4 | const deviceName = 'Nexus_5_7.1.1'; 5 | const platformName = 'Android'; 6 | const platformVersion = '7.1.1'; 7 | 8 | // ============ 9 | // Capabilities 10 | // ============ 11 | config.capabilities = [ 12 | { 13 | app: ANDROID_APP_PATH, 14 | deviceName, 15 | maxInstances: 1, 16 | noReset: true, 17 | orientation: 'PORTRAIT', 18 | platformName, 19 | platformVersion, 20 | 21 | // Custom 22 | customCapabilities: { 23 | pixelRatio: 3, 24 | }, 25 | }, 26 | ]; 27 | 28 | // ==================== 29 | // Appium configuration 30 | // ==================== 31 | config.appium = { 32 | args: { 33 | address: '127.0.0.1', 34 | commandTimeout: '11000', 35 | deviceName, 36 | platformName, 37 | platformVersion, 38 | }, 39 | }; 40 | 41 | config.cucumberOpts.tagExpression = `${config.cucumberOpts.tagExpression} and (not @ios)`; 42 | 43 | config.port = 4723; 44 | 45 | exports.config = config; 46 | -------------------------------------------------------------------------------- /__tests__/appium/config/wdio.ios.conf.js: -------------------------------------------------------------------------------- 1 | const config = require('./wdio.shared.conf').config; 2 | 3 | const IOS_APP_PATH = './ios/build/Build/Products/Debug-iphonesimulator/wswebcreation.app'; 4 | const deviceName = 'iPhone 8'; 5 | const platformName = 'iOS'; 6 | const platformVersion = '11.4'; 7 | 8 | // ============ 9 | // Capabilities 10 | // ============ 11 | config.capabilities = [ 12 | { 13 | app: IOS_APP_PATH, 14 | deviceName, 15 | maxInstances: 1, 16 | noReset: true, 17 | orientation: 'PORTRAIT', 18 | platformName, 19 | platformVersion, 20 | }, 21 | ]; 22 | 23 | // ==================== 24 | // Appium configuration 25 | // ==================== 26 | config.appium = { 27 | args: { 28 | address: '127.0.0.1', 29 | commandTimeout: '11000', 30 | deviceName, 31 | platformName, 32 | platformVersion, 33 | }, 34 | }; 35 | 36 | config.cucumberOpts.tagExpression = `${config.cucumberOpts.tagExpression} and (not @android)`; 37 | 38 | config.port = 4723; 39 | 40 | exports.config = config; 41 | -------------------------------------------------------------------------------- /__tests__/appium/config/wdio.shared.conf.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const argv = require('yargs').argv; 3 | 4 | const report = require('multiple-cucumber-html-reporter'); 5 | const chai = require('chai'); 6 | 7 | exports.config = { 8 | // ==================== 9 | // Appium configuration 10 | // ==================== 11 | services: ['appium'], 12 | 13 | // ================== 14 | // Test Configuration 15 | // ================== 16 | sync: true, 17 | logLevel: argv.logLevel || 'silent', 18 | coloredLogs: true, 19 | bail: 0, 20 | baseUrl: 'http://localhost', 21 | waitforTimeout: 20000, 22 | connectionRetryTimeout: 90000, 23 | connectionRetryCount: 3, 24 | 25 | // ====================== 26 | // Cucumber configuration 27 | // ====================== 28 | framework: 'cucumber', 29 | specs: getFeatureFiles(), 30 | cucumberOpts: { 31 | require: [ 32 | '__tests__/appium/config/helpers/after.scenario.js', 33 | '__tests__/appium/config/helpers/report.hook.js', 34 | '__tests__/appium/step_definitions/base.steps.js', 35 | '__tests__/appium/step_definitions/navigation.steps.js', 36 | '__tests__/appium/step_definitions/webview.steps.js', 37 | '__tests__/appium/step_definitions/chat.steps.js', 38 | ], 39 | backtrace: false, 40 | compiler: ['js:babel-register'], 41 | format: ['pretty'], 42 | colors: true, 43 | snippets: true, 44 | source: true, 45 | tagExpression: '(not @wip)', 46 | timeout: 60000, 47 | ignoreUndefinedDefinitions: false, 48 | }, 49 | reporters: ['spec'], 50 | 51 | // ===== 52 | // Hooks 53 | // ===== 54 | // WebdriverIO provides several hooks you can use to interfere with the test process in order to 55 | // enhance it and to build services around it. You can either apply a single function or an array 56 | // of methods to it. If one of them returns with a promise, WebdriverIO will wait until that 57 | // promise got resolved to continue. 58 | /** 59 | * Gets executed once before all workers get launched. 60 | */ 61 | onPrepare: () => { 62 | console.log(` 63 | ================================================================================= 64 | The '.tmp'-folder is being removed. This is the folder that holds all the 65 | reports and failure screenshots. 66 | =================================================================================\n`); 67 | fs.emptyDirSync('.tmp/'); 68 | }, 69 | /** 70 | * Gets executed before test execution begins. At this point you can access to all global 71 | * variables like `browser`. 72 | */ 73 | before: () => { 74 | /** 75 | * Setup the Chai assertion framework 76 | */ 77 | global.expect = chai.expect; 78 | global.assert = chai.assert; 79 | global.should = chai.should(); 80 | 81 | /** 82 | * Rename the browser to device to prevent confusion 83 | */ 84 | global.device = browser; 85 | 86 | /** 87 | * Custom property that is used to determine if the app is already launched for the first time 88 | */ 89 | device.options.firstAppStart = true; 90 | 91 | /** 92 | * Custom property that is used to determine if the app needs to be restarted 93 | */ 94 | device.options.restartApp = false; 95 | }, 96 | /** 97 | * Gets executed after all workers got shut down and the process is about to exit. 98 | */ 99 | onComplete: () => { 100 | report.generate({ 101 | jsonDir: '.tmp/json-output/', 102 | reportPath: '.tmp/report/', 103 | }); 104 | }, 105 | }; 106 | 107 | /** 108 | * Get the featurefiles that need to be run based on an command line flag that is passed, 109 | * if nothing is passed all the featurefiles are run 110 | * 111 | * @example: 112 | * 113 | *
114 |  *     // For 1 feature
115 |  *     npm run appium.io -- --feature=playground
116 |  *
117 |  *     // For multiple features
118 |  *     npm run appium.io -- --feature=playground,login,...
119 |  *
120 |  *     // Else
121 |  *     npm run appium.ios
122 |  * 
123 | */ 124 | function getFeatureFiles() { 125 | if (argv.feature) { 126 | return argv.feature.split(',') 127 | .map(feature => `${process.cwd()}/__tests__/appium/**/${feature}.feature`); 128 | } 129 | 130 | return [`${process.cwd()}/__tests__/appium/**/*.feature`]; 131 | } 132 | -------------------------------------------------------------------------------- /__tests__/appium/features/chats.feature: -------------------------------------------------------------------------------- 1 | Feature: Use the Chats 2 | 3 | Background: Open the app 4 | Given I open the app 5 | Then the Home screen is visible 6 | When I select Chats from the tabbar 7 | Then the Chats screen is visible 8 | 9 | Scenario: As an automation engineer I want to validate the initial chats 10 | When I select the first chat 11 | Then the following chats would be shown 12 | | chatMessage | 13 | | Hey wassup? | 14 | | So it seems like this internet thing is here to stay, huh? | 15 | 16 | Scenario: As an automation engineer I want to able to chat and get a random response back 17 | When I select the first chat 18 | And I chat "Hello, it's me. I was wondering if after all these years you'd like to meet" 19 | Then the following chats would be shown 20 | | chatMessage | 21 | | Hey wassup? | 22 | | So it seems like this internet thing is here to stay, huh? | 23 | | Hello, it's me. I was wondering if after all these years you'd like to meet | 24 | | random response | 25 | -------------------------------------------------------------------------------- /__tests__/appium/features/navigation.feature: -------------------------------------------------------------------------------- 1 | Feature: Navigate through the app 2 | 3 | Background: Open the app 4 | Given I open the app 5 | Then the Home screen is visible 6 | 7 | Scenario: As a user I want to navigate through the app with the tabbar 8 | When I select Webview from the tabbar 9 | Then the Webview screen is visible 10 | When I select Chats from the tabbar 11 | Then the Chats screen is visible 12 | When I select Home from the tabbar 13 | Then the Home screen is visible 14 | 15 | Scenario: As a user I want to navigate through the app by swiping 16 | When I swipe left 17 | Then the Webview screen is visible 18 | When I swipe left 19 | Then the Chats screen is visible 20 | When I swipe right 21 | Then the Webview screen is visible 22 | When I swipe right 23 | Then the Home screen is visible 24 | 25 | Scenario: As a user I want to use the back button in the webview header 26 | When I select Webview from the tabbar 27 | Then the Webview screen is visible 28 | And I want to visit www.wswebcreation.nl 29 | Then the site is loaded 30 | When I click on the go back arrow in the header 31 | Then the Webview screen is visible 32 | 33 | Scenario: As a user I want to use the back button in the chatbox header 34 | When I select Chats from the tabbar 35 | Then the Chats screen is visible 36 | When I select the first chat 37 | Then the Chatbox should be visible 38 | When I click on the go back arrow in the header 39 | Then the Chats screen is visible 40 | 41 | 42 | -------------------------------------------------------------------------------- /__tests__/appium/features/webview.feature: -------------------------------------------------------------------------------- 1 | @webview 2 | Feature: Use the webview 3 | 4 | Background: Open the app 5 | Given I open the app 6 | Then the Home screen is visible 7 | When I select Webview from the tabbar 8 | Then the Webview screen is visible 9 | 10 | Scenario: As an automation engineer I want to swipe through the webview without switching context 11 | When I want to visit www.wswebcreation.nl 12 | Then the site is loaded 13 | Then I can scroll through the site without switching the context 14 | 15 | Scenario: As an automation engineer I want to scroll through the webview with a Javascript scroll 16 | When I want to visit www.wswebcreation.nl 17 | Then the site is loaded 18 | When I change the context to webview 19 | Then I can scroll through the site with Javascript 20 | 21 | Scenario: As an automation engineer I want to see all posts in the testautomation category 22 | When I want to visit www.wswebcreation.nl 23 | Then the site is loaded 24 | When I change the context to webview 25 | And I open the menu and select the testautomation category 26 | Then the category testautomation is shown 27 | 28 | Scenario: As an automation engineer I want to validate the error pop 29 | When I enter an incorrect url 30 | Then an error message is shown 31 | 32 | -------------------------------------------------------------------------------- /__tests__/appium/screen-objects/base.js: -------------------------------------------------------------------------------- 1 | import * as labels from '../../../app/config/labels.json'; 2 | import { TEST_PREFIX, WAIT_FOR_STATE } from '../support/constants'; 3 | import { waitFor } from '../support/utils'; 4 | 5 | const SCREEN_SELECTORS = { 6 | home: `${TEST_PREFIX}${labels.stackNavigatorTitle.home}`, 7 | webview: `${TEST_PREFIX}${labels.stackNavigatorTitle.webview}`, 8 | chats: `${TEST_PREFIX}${labels.chats.window}`, 9 | }; 10 | 11 | /** 12 | * Wait for a specific screen to be visible 13 | * @param {string} screen see the 'SCREEN_SELECTORS' const in './constants.js' for all the 14 | * possible values 15 | */ 16 | export function waitForScreenToBeVisible(screen) { 17 | waitFor({ 18 | selector: SCREEN_SELECTORS[screen.toLowerCase()], 19 | state: WAIT_FOR_STATE.VISIBLE, 20 | milliseconds: 25000, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /__tests__/appium/screen-objects/chat.js: -------------------------------------------------------------------------------- 1 | import * as labels from '../../../app/config/labels.json'; 2 | import { TEST_PREFIX, WAIT_FOR_STATE } from '../support/constants'; 3 | import { getTextOfElement, tapOnButton, waitFor } from '../support/utils'; 4 | 5 | const CHAT_SELECTORS = { 6 | BOX: `${TEST_PREFIX}${labels.stackNavigatorTitle.chatBox}`, 7 | BUBBLE: `${TEST_PREFIX}${labels.components.messageBubble.accessibilityLabel}`, 8 | INPUT: `${TEST_PREFIX}${labels.components.chatInput.inputAccessibilityLabel}`, 9 | SUBMIT_BUTTON: `${TEST_PREFIX}${labels.components.chatInput.sendAccessibilityLabel}`, 10 | }; 11 | 12 | /** 13 | * Select a chat 14 | * @param {string} name The name of the chat person 15 | */ 16 | export function selectChat(name) { 17 | tapOnButton(`${TEST_PREFIX}${name}`); 18 | } 19 | 20 | /** 21 | * Wait for the chat box is visible 22 | */ 23 | export function chatBoxIsVisible() { 24 | waitFor({ 25 | selector: CHAT_SELECTORS.BOX, 26 | state: WAIT_FOR_STATE.VISIBLE, 27 | milliseconds: 6000, 28 | }); 29 | } 30 | 31 | 32 | /** 33 | * Wait for all the expected chats to be visible in the view and verify them with a 34 | * provided set of expected chats 35 | * @param {Object} table 36 | * @param {boolean} showTextLogging this is used for debugging, if true it will do some console log 37 | */ 38 | export function verifyChatsShownInView(table, showTextLogging = false) { 39 | const expectedChats = table.hashes(); 40 | device.pause(expectedChats.length * 100); 41 | const shownChats = getChatsShownInView(); 42 | if (showTextLogging) { 43 | console.log('shownChats = ', shownChats); 44 | } 45 | let foundChatIndex = 0; 46 | expectedChats.forEach((expectedChat, index) => { 47 | if (index === 0) { 48 | /** 49 | * Find a partial string in an array and return the index 50 | */ 51 | // eslint-disable-next-line no-plusplus 52 | for (let i = 0; i < shownChats.length; i++) { 53 | if (shownChats[i].includes(expectedChat.chatMessage)) { 54 | foundChatIndex = i; 55 | break; 56 | } 57 | } 58 | } else { 59 | foundChatIndex += 1; 60 | } 61 | 62 | if (expectedChat.chatMessage === 'random response') { 63 | console.log(` 64 | ======================================================================= 65 | Found random response was: 66 | 67 | '${shownChats[foundChatIndex]}' 68 | =======================================================================`); 69 | } else { 70 | expect(shownChats[foundChatIndex]) 71 | .to 72 | .have 73 | .string(expectedChat.chatMessage); 74 | } 75 | }); 76 | } 77 | 78 | /** 79 | * Get all the chats that are currently shown in the view of the device. 80 | * Keep in mind that each device can return a different set of chats 81 | * 82 | * @return {Array} of chats 83 | * 84 | * @example 85 | *
 86 |  *  const chats = [
 87 |  *    'Hey wassup?',
 88 |  *    'So it seems like this internet thing is here to stay, huh?',
 89 |  *    '....
 90 |  *  ];
 91 |  * 
92 | */ 93 | export function getChatsShownInView() { 94 | const chatInView = $$(CHAT_SELECTORS.BUBBLE); 95 | const chatsText = []; 96 | chatInView.forEach((chat) => { 97 | try { 98 | chatsText.push(getTextOfElement(chat)); 99 | } catch (error) { 100 | // Do nothing. 101 | // It's a partially visible bubble, but no text visible in it so it can't be retrieved 102 | } 103 | }); 104 | return device.isAndroid ? chatsText.reverse() : chatsText; 105 | } 106 | 107 | /** 108 | * Add a chat 109 | * @param {string} chat 110 | */ 111 | export function submitChat(chat) { 112 | $(CHAT_SELECTORS.INPUT).setValue(chat); 113 | tapOnButton(CHAT_SELECTORS.SUBMIT_BUTTON); 114 | } 115 | -------------------------------------------------------------------------------- /__tests__/appium/screen-objects/navigation.js: -------------------------------------------------------------------------------- 1 | import * as labels from '../../../app/config/labels.json'; 2 | import { TEST_PREFIX } from '../support/constants'; 3 | import { tapOnButton } from '../support/utils'; 4 | 5 | const TABBAR_SELECTORS = { 6 | home: `${TEST_PREFIX}${labels.tabNavigator.home}`, 7 | webview: `${TEST_PREFIX}${labels.tabNavigator.webview}`, 8 | chats: `${TEST_PREFIX}${labels.tabNavigator.chats}`, 9 | }; 10 | const HEADER_BACK_BUTTON = `${TEST_PREFIX}${labels.stackNavigatorTitle.goBackAccessibilityLabel}`; 11 | 12 | /** 13 | * Select screen from the tabbar 14 | * @param {string} screen see the 'SCREEN_SELECTORS' const in './constants.js' for all the 15 | * possible values 16 | */ 17 | export function selectScreenFromTabBar(screen) { 18 | tapOnButton(TABBAR_SELECTORS[screen.toLowerCase()]); 19 | } 20 | 21 | /** 22 | * Click on the back button in the header 23 | */ 24 | export function goBackFromHeader() { 25 | tapOnButton(HEADER_BACK_BUTTON); 26 | } 27 | -------------------------------------------------------------------------------- /__tests__/appium/screen-objects/webview.js: -------------------------------------------------------------------------------- 1 | import * as labels from '../../../app/config/labels.json'; 2 | import { 3 | TEST_PREFIX, 4 | WAIT_FOR_STATE, 5 | CONTEXT_REF, 6 | DOCUMENT_READY_STATE, 7 | } from '../support/constants'; 8 | import { 9 | acceptAlert, 10 | getAlertText, 11 | swipe, 12 | tapOnButton, 13 | waitFor, 14 | waitForAlert, 15 | } from '../support/utils'; 16 | 17 | const WEBVIEW_SELECTORS = { 18 | INPUT: `${TEST_PREFIX}${labels.webview.textAccessibilityLabel}`, 19 | BUTTON: `${TEST_PREFIX}${labels.webview.button}`, 20 | LOADER: `${TEST_PREFIX}${labels.webview.loadingText}`, 21 | MENU: { 22 | ANDROID: '~MENU Click to view the navigation', 23 | IOS: '~MENU', 24 | }, 25 | CSS: { 26 | MENU: { 27 | BUTTON: '.menu', 28 | TESTAUTOMATION_MENU_ITEM: 'a*=testautomation', 29 | 30 | }, 31 | TESTAUTOMATION_CATEGORY_TITLE: 'h4*=testautomation', 32 | }, 33 | }; 34 | 35 | /** 36 | * Select screen from the tabbar 37 | * @param {string} url A url like `www.wswebcreation.nl` or with `http(s)://` in front of it 38 | */ 39 | export function enterURL(url) { 40 | $(WEBVIEW_SELECTORS.INPUT).addValue(url.toLowerCase()); 41 | tapOnButton(WEBVIEW_SELECTORS.BUTTON); 42 | waitFor({ 43 | falseState: true, 44 | selector: WEBVIEW_SELECTORS.LOADER, 45 | state: WAIT_FOR_STATE.EXIST, 46 | }); 47 | } 48 | 49 | /** 50 | * Wait for the website in the webview to be loaded 51 | */ 52 | export function waitForWebsiteLoaded() { 53 | waitForWebviewContextLoaded(); 54 | switchToContext(CONTEXT_REF.WEBVIEW); 55 | waitForDocumentFullyLoaded(); 56 | switchToContext(CONTEXT_REF.NATIVE); 57 | } 58 | 59 | /** 60 | * Switch to native or webview context 61 | * @param {string} context should be native of webview 62 | */ 63 | export function switchToContext(context) { 64 | device.context(getCurrentContexts()[context === CONTEXT_REF.WEBVIEW ? 1 : 0]); 65 | } 66 | 67 | /** 68 | * Return the current context 69 | * @return {string} The current context 70 | */ 71 | export function getCurrentContext() { 72 | return device.context(); 73 | } 74 | 75 | /** 76 | * Returns an object with the list of all available contexts 77 | * @return {object} An object containing the list of all available contexts 78 | */ 79 | export function getCurrentContexts() { 80 | return device.contexts().value; 81 | } 82 | 83 | 84 | /** 85 | * Validate if native webview can be scrolled 86 | */ 87 | export function validateCanNativelyScrollWebview() { 88 | const selector = WEBVIEW_SELECTORS.MENU[device.isIOS ? 'IOS' : 'ANDROID']; 89 | const centerOfScreen = { x: 50, y: 65 }; 90 | const topOfScreen = { x: 50, y: 1 }; 91 | const initialLocation = device.getLocation(selector); 92 | swipe(centerOfScreen, topOfScreen); 93 | const newLocation = device.getLocation(selector); 94 | expect(initialLocation) 95 | .to 96 | .not 97 | .deep 98 | .equal(newLocation, 'Webview could not be scrolled'); 99 | } 100 | 101 | /** 102 | * Validate if webview can be scrolled with JS 103 | */ 104 | export function validateCanScrollWebviewWithJavascript() { 105 | const initialLocation = device.execute(() => document.querySelector('.menu').getBoundingClientRect()); 106 | device.execute(() => window.scrollTo(0, 500)); 107 | const newLocation = device.execute(() => document.querySelector('.menu').getBoundingClientRect()); 108 | expect(initialLocation) 109 | .to 110 | .not 111 | .deep 112 | .equal(newLocation, 'Webview could not be scrolled with JS'); 113 | } 114 | 115 | /** 116 | * Open the menu with "plain" CSS selectors 117 | */ 118 | export function openTestautomationCategory() { 119 | categoryTestautomationIsShown(false); 120 | $(WEBVIEW_SELECTORS.CSS.MENU.BUTTON).click(); 121 | waitFor({ 122 | selector: WEBVIEW_SELECTORS.CSS.MENU.TESTAUTOMATION_MENU_ITEM, 123 | state: WAIT_FOR_STATE.VISIBLE, 124 | }); 125 | $(WEBVIEW_SELECTORS.CSS.MENU.TESTAUTOMATION_MENU_ITEM).click(); 126 | } 127 | 128 | /** 129 | * Expect the testautomation category to be shown or not 130 | * @param {boolean} isShown, default false 131 | */ 132 | export function categoryTestautomationIsShown(isShown = true) { 133 | waitFor({ 134 | falseState: !isShown, 135 | selector: WEBVIEW_SELECTORS.CSS.TESTAUTOMATION_CATEGORY_TITLE, 136 | state: WAIT_FOR_STATE.VISIBLE, 137 | }); 138 | } 139 | 140 | /** 141 | * Wait for the webview context to be loaded 142 | */ 143 | export function waitForWebviewContextLoaded() { 144 | device.waitUntil( 145 | () => 146 | getCurrentContexts().length > 1 147 | && getCurrentContexts()[1].toLowerCase().includes(CONTEXT_REF.WEBVIEW), 148 | 10000, 149 | 'Webview context not loaded', 150 | 100, 151 | ); 152 | } 153 | 154 | /** 155 | * Wait for the document to be full loaded 156 | */ 157 | export function waitForDocumentFullyLoaded() { 158 | device.pause(1000); 159 | device.waitUntil( 160 | () => device.execute(() => document.readyState).value === DOCUMENT_READY_STATE.COMPLETE, 161 | 15000, 162 | 'Website not loaded', 163 | 100, 164 | ); 165 | } 166 | 167 | /** 168 | * Check if the alert is shown 169 | * @param {string} url 170 | */ 171 | export function validateAlertIsShown(url) { 172 | waitForAlert(); 173 | expect(getAlertText()) 174 | .to 175 | .have 176 | .string(`Alert http://${url} is not a valid url!`); 177 | acceptAlert(); 178 | } 179 | -------------------------------------------------------------------------------- /__tests__/appium/step_definitions/base.steps.js: -------------------------------------------------------------------------------- 1 | import { Given, When, Then } from 'cucumber'; 2 | import { waitForScreenToBeVisible } from '../screen-objects/base'; 3 | import { 4 | launchApp, 5 | restartApp, 6 | swipeDown, 7 | swipeLeft, 8 | swipeRight, 9 | swipeUp, 10 | tapOnScreen, 11 | } from '../support/utils'; 12 | 13 | Given( 14 | /I open the app/, 15 | () => { 16 | launchApp(); 17 | }, 18 | ); 19 | 20 | Then( 21 | /the (Home|Webview|Chats) screen is visible/, 22 | (screen) => { 23 | waitForScreenToBeVisible(screen); 24 | }, 25 | ); 26 | 27 | When( 28 | /I restart the App/, 29 | () => { 30 | restartApp(); 31 | }, 32 | ); 33 | When( 34 | /I hide the software keyboard/, 35 | () => { 36 | tapOnScreen(); 37 | }, 38 | ); 39 | When( 40 | /I swipe (down|up|left|right)/, 41 | (direction) => { 42 | switch (direction) { 43 | case 'down': 44 | swipeDown(); 45 | break; 46 | case 'up': 47 | swipeUp(); 48 | break; 49 | case 'left': 50 | swipeLeft(); 51 | break; 52 | case 'right': 53 | swipeRight(); 54 | break; 55 | default: 56 | console.log('This should never happen'); 57 | } 58 | }, 59 | ); 60 | -------------------------------------------------------------------------------- /__tests__/appium/step_definitions/chat.steps.js: -------------------------------------------------------------------------------- 1 | import { When, Then } from 'cucumber'; 2 | import { 3 | chatBoxIsVisible, 4 | selectChat, 5 | submitChat, 6 | verifyChatsShownInView, 7 | } from '../screen-objects/chat'; 8 | 9 | When( 10 | /I select the first chat/, 11 | () => { 12 | selectChat('Dick Tracy'); 13 | }, 14 | ); 15 | 16 | When( 17 | /I chat "(.*)"/, 18 | (chat) => { 19 | submitChat(chat); 20 | }, 21 | ); 22 | 23 | Then( 24 | /the Chatbox should be visible/, 25 | () => { 26 | chatBoxIsVisible(); 27 | }, 28 | ); 29 | 30 | Then( 31 | /the following chats would be shown/, 32 | (table) => { 33 | verifyChatsShownInView(table); 34 | }, 35 | ); 36 | -------------------------------------------------------------------------------- /__tests__/appium/step_definitions/navigation.steps.js: -------------------------------------------------------------------------------- 1 | import { When } from 'cucumber'; 2 | import { goBackFromHeader, selectScreenFromTabBar } from '../screen-objects/navigation'; 3 | 4 | When( 5 | /I select (Home|Webview|Chats) from the tabbar/, 6 | (screen) => { 7 | selectScreenFromTabBar(screen); 8 | }, 9 | ); 10 | 11 | When( 12 | /I click on the go back arrow in the header/, 13 | () => { 14 | goBackFromHeader(); 15 | }, 16 | ); 17 | -------------------------------------------------------------------------------- /__tests__/appium/step_definitions/webview.steps.js: -------------------------------------------------------------------------------- 1 | import { When, Then } from 'cucumber'; 2 | import { 3 | categoryTestautomationIsShown, 4 | enterURL, 5 | openTestautomationCategory, 6 | switchToContext, 7 | validateAlertIsShown, 8 | validateCanNativelyScrollWebview, 9 | validateCanScrollWebviewWithJavascript, 10 | waitForWebsiteLoaded, 11 | } from '../screen-objects/webview'; 12 | import { INCORRECT_URL } from '../support/constants'; 13 | 14 | When( 15 | /I want to visit (.*)/, 16 | (url) => { 17 | enterURL(url); 18 | }, 19 | ); 20 | 21 | When( 22 | /I enter an incorrect url/, 23 | () => { 24 | enterURL(INCORRECT_URL); 25 | }, 26 | ); 27 | 28 | When( 29 | /I change the context to (webview|native)/, 30 | (context) => { 31 | switchToContext(context); 32 | }, 33 | ); 34 | 35 | When( 36 | /I open the menu and select the testautomation category/, 37 | () => { 38 | openTestautomationCategory(); 39 | }, 40 | ); 41 | 42 | Then( 43 | /the site is loaded/, 44 | () => { 45 | waitForWebsiteLoaded(); 46 | }, 47 | ); 48 | 49 | Then( 50 | /I can scroll through the site without switching the context/, 51 | () => { 52 | validateCanNativelyScrollWebview(); 53 | }, 54 | ); 55 | 56 | Then( 57 | /I can scroll through the site with Javascript/, 58 | () => { 59 | validateCanScrollWebviewWithJavascript(); 60 | }, 61 | ); 62 | 63 | Then( 64 | /the category testautomation is shown/, 65 | () => { 66 | categoryTestautomationIsShown(); 67 | }, 68 | ); 69 | 70 | Then( 71 | /an error message is shown/, 72 | () => { 73 | validateAlertIsShown(INCORRECT_URL); 74 | }, 75 | ); 76 | -------------------------------------------------------------------------------- /__tests__/appium/support/constants.js: -------------------------------------------------------------------------------- 1 | export const SCREENSHOTS_FOLDERS = { 2 | TMP: '.tmp/screenshots/', 3 | DIST: '.dist/screenshots/', 4 | }; 5 | export const SWIPE_DIRECTION = { 6 | down: { 7 | start: { x: 50, y: 15 }, 8 | end: { x: 50, y: 85 }, 9 | }, 10 | left: { 11 | start: { x: 85, y: 30 }, 12 | end: { x: 25, y: 30 }, 13 | }, 14 | right: { 15 | start: { x: 25, y: 30 }, 16 | end: { x: 85, y: 30 }, 17 | }, 18 | up: { 19 | start: { x: 50, y: 85 }, 20 | end: { x: 50, y: 15 }, 21 | }, 22 | }; 23 | export const CONTEXT_REF = { 24 | NATIVE: 'native', 25 | WEBVIEW: 'webview', 26 | }; 27 | export const DOCUMENT_READY_STATE = { 28 | COMPLETE: 'complete', 29 | INTERACTIVE: 'interactive', 30 | LOADING: 'loading', 31 | }; 32 | export const TEST_PREFIX = '~test-'; 33 | export const NATIVE_APP = 'NATIVE_APP'; 34 | export const WAIT_FOR_STATE = { 35 | EXIST: 'exist', 36 | VISIBLE: 'visible', 37 | }; 38 | export const INCORRECT_URL = 'in.correct.url'; 39 | 40 | /** 41 | * Cross-platform Text selectors 42 | */ 43 | export const ANDROID_TEXT_SELECTOR = '*//android.widget.TextView'; 44 | export const ANDROID_ALERT_TITLE_SELECTOR = '*//android.widget.TextView[@resource-id="android:id/alertTitle"]'; 45 | export const ANDROID_ALERT_MESSAGE_SELECTOR = '*//android.widget.TextView[@resource-id="android:id/message"]'; 46 | export const ANDROID_ACCEPT_ALERT_SELECTOR = '*//android.widget.Button[@text="OK"]'; 47 | export const IOS_ALERT_SELECTOR = '*//XCUIElementTypeAlert'; 48 | export const IOS_TEXT_SELECTOR = null; 49 | -------------------------------------------------------------------------------- /__tests__/appium/support/expectations.js: -------------------------------------------------------------------------------- 1 | import { ANDROID_TEXT_SELECTOR, IOS_TEXT_SELECTOR } from './constants'; 2 | 3 | /** 4 | * Expect that an element contains certain text 5 | * @param {object} element The element that should contain the text 6 | * @param {string} text The text 7 | */ 8 | export function expectElementContainsText(element, text) { 9 | expect(element.getText(device.isAndroid ? ANDROID_TEXT_SELECTOR : IOS_TEXT_SELECTOR)) 10 | .to.have.string(text, `Element '${element}' doesn't contain the text ${text}`); 11 | } 12 | -------------------------------------------------------------------------------- /__tests__/appium/support/utils.js: -------------------------------------------------------------------------------- 1 | import { argv } from 'yargs'; // eslint-disable-line 2 | import { 3 | ANDROID_ACCEPT_ALERT_SELECTOR, 4 | ANDROID_ALERT_MESSAGE_SELECTOR, 5 | ANDROID_ALERT_TITLE_SELECTOR, 6 | ANDROID_TEXT_SELECTOR, 7 | IOS_ALERT_SELECTOR, 8 | IOS_TEXT_SELECTOR, 9 | SWIPE_DIRECTION, TEST_PREFIX, 10 | } from './constants'; 11 | import labels from '../../../app/config/labels.json'; 12 | import { waitForScreenToBeVisible } from '../screen-objects/base'; 13 | 14 | 15 | let SCREEN_SIZE; 16 | 17 | /** 18 | * The app is opened by Appium by default, when we start a new scenario 19 | * the app needs to be restarted 20 | */ 21 | export function launchApp() { 22 | if (!device.options.firstAppStart) { 23 | restartApp(); 24 | } 25 | } 26 | 27 | /** 28 | * Restart the app, restarting is done with triggering react native restart 29 | */ 30 | export function restartApp() { 31 | const selector = TEST_PREFIX + labels.restartDot; 32 | try { 33 | device.pause(500); 34 | 35 | if (!argv.restartDot || (argv.restartDot && !device.isVisible(selector))) { 36 | throw new Error(); 37 | } 38 | 39 | device.click(selector); 40 | waitForScreenToBeVisible('Home'); 41 | } catch (e) { 42 | device.reset(); 43 | } 44 | } 45 | 46 | 47 | /** 48 | * Wait for a given element to be|not visible|exist 49 | * REMARK: if falseState = true it instead waits for the selector to not match any elements 50 | * @param {Object} data 51 | * @example 52 | *
 53 |  *   const data = {
 54 |  *     selector: '~test-Ja',
 55 |  *     milliseconds: 3000,
 56 |  *     falseState: false,
 57 |  *     state: 'visible|exist'
 58 |  *   }
 59 |  * 
60 | */ 61 | export function waitFor(data) { 62 | Object.assign({ 63 | state: 'exist', 64 | falseState: false, 65 | milliseconds: 11000, 66 | }, data); 67 | device[`waitFor${upperFirst(data.state)}`]( 68 | data.selector, 69 | data.milliseconds, 70 | data.falseState, 71 | ); 72 | } 73 | 74 | /** 75 | * Tap on a button 76 | * @param {string} element 77 | */ 78 | export function tapOnButton(element) { 79 | device.touchAction(element, 'tap'); 80 | } 81 | 82 | /** 83 | * Converts the first character of string to upper case 84 | * @param {string} string 85 | * @returns Returns the converted string 86 | */ 87 | export function upperFirst(string) { 88 | return string.charAt(0) 89 | .toUpperCase() + string.slice(1); 90 | } 91 | 92 | /** 93 | * Swipe from coordinates (from) to the new coordinates (to). The given coordinates are 94 | * percentages of the screen. 95 | * @param {object} from { x: 50, y: 50 } 96 | * @param {object} to { x: 25, y: 25 } 97 | * @example 98 | *
 99 |  *   // This is a swipe to the left
100 |  *   const from = { x: 50, y:50 }
101 |  *   const to = { x: 25, y:50 }
102 |  * 
103 | */ 104 | export function swipeOnPercentage(from, to) { 105 | SCREEN_SIZE = SCREEN_SIZE || device.windowHandleSize().value; 106 | const pressOptions = getDeviceScreenCoordinates(SCREEN_SIZE, from); 107 | const moveToScreenCoordinates = getDeviceScreenCoordinates(SCREEN_SIZE, to); 108 | swipe( 109 | pressOptions, 110 | moveToScreenCoordinates, 111 | ); 112 | } 113 | 114 | /** 115 | * Swipe from coordinates (from) to the new coordinates (to). The given coordinates are in pixels. 116 | * 117 | * @param {object} from { x: 50, y: 50 } 118 | * @param {object} to { x: 25, y: 25 } 119 | * 120 | * @example 121 | *
122 |  *   // This is a swipe to the left
123 |  *   const from = { x: 50, y:50 }
124 |  *   const to = { x: 25, y:50 }
125 |  * 
126 | */ 127 | export function swipe(from, to) { 128 | device.touchPerform([{ 129 | action: 'press', 130 | options: from, 131 | }, { 132 | action: 'wait', 133 | options: { ms: 1000 }, 134 | }, { 135 | action: 'moveTo', 136 | options: to, 137 | }, { 138 | action: 'release', 139 | }]); 140 | device.pause(1500); 141 | } 142 | 143 | /** 144 | * Swipe down based on a percentage 145 | * @param {float} percentage 146 | */ 147 | export function swipeDown(percentage = 1) { 148 | swipeOnPercentage(calculateXY(SWIPE_DIRECTION.down.start, percentage), calculateXY(SWIPE_DIRECTION.down.end, percentage)); 149 | } 150 | 151 | /** 152 | * Swipe Up based on a percentage 153 | * @param {float} percentage from 0 - 1 154 | */ 155 | export function swipeUp(percentage = 1) { 156 | swipeOnPercentage(calculateXY(SWIPE_DIRECTION.up.start, percentage), calculateXY(SWIPE_DIRECTION.up.end, percentage)); 157 | } 158 | 159 | /** 160 | * Swipe left based on a percentage 161 | * @param {float} percentage from 0 - 1 162 | */ 163 | export function swipeLeft(percentage = 1) { 164 | swipeOnPercentage(calculateXY(SWIPE_DIRECTION.left.start, percentage), calculateXY(SWIPE_DIRECTION.left.end, percentage)); 165 | } 166 | 167 | /** 168 | * Swipe right based on a percentage 169 | * @param {float} percentage from 0 - 1 170 | */ 171 | export function swipeRight(percentage = 1) { 172 | swipeOnPercentage(calculateXY(SWIPE_DIRECTION.right.start, percentage), calculateXY(SWIPE_DIRECTION.right.end, percentage)); 173 | } 174 | 175 | /** 176 | * Calculate the x y coordinates based on a percentage 177 | * @param {object} coordinates 178 | * @param {float} percentage 179 | * @return {{x: number, y: number}} 180 | */ 181 | function calculateXY({ x, y }, percentage) { 182 | return { 183 | x: x * percentage, 184 | y: y * percentage, 185 | }; 186 | } 187 | 188 | /** 189 | * Get the screen coordinates based on a device his screensize 190 | * @param {number} screenSize the size of the screen 191 | * @param {object} coordinates like { x: 50, y: 50 } 192 | * @return {{x: number, y: number}} 193 | */ 194 | function getDeviceScreenCoordinates(screenSize, coordinates) { 195 | return { 196 | x: Math.round(screenSize.width * (coordinates.x / 100)), 197 | y: Math.round(screenSize.height * (coordinates.y / 100)), 198 | }; 199 | } 200 | 201 | /** 202 | * Tap on a given location (coordinates) on the screen. The given coordinates are 203 | * percentages of the screen. 204 | * @param {object} location { x: 50, y: 25 } 205 | */ 206 | export function tapOnScreen(location = { x: 50, y: 25 }) { 207 | const screenSize = device.windowHandleSize().value; 208 | 209 | device.touchPerform([{ 210 | action: 'press', 211 | options: getDeviceScreenCoordinates(screenSize, location), 212 | }, { 213 | action: 'release', 214 | }]); 215 | } 216 | 217 | /** 218 | * Get the text of an element 219 | * @param element 220 | * @return {string} 221 | */ 222 | export function getTextOfElement(element) { 223 | const visualText = element.getText(device.isAndroid ? ANDROID_TEXT_SELECTOR : IOS_TEXT_SELECTOR); 224 | return typeof visualText === 'object' ? visualText.join(' ') : visualText; 225 | } 226 | 227 | /** 228 | * Accept the alert text on a cross-platform way 229 | */ 230 | export function acceptAlert() { 231 | return device.isAndroid ? tapOnButton(ANDROID_ACCEPT_ALERT_SELECTOR) : device.alertAccept(); 232 | } 233 | 234 | /** 235 | * Get the alert text on a cross-platform way 236 | * @return {string} 237 | */ 238 | export function getAlertText() { 239 | const alertText = device.isAndroid 240 | ? `${$(ANDROID_ALERT_TITLE_SELECTOR) 241 | .getText()} ${$(ANDROID_ALERT_MESSAGE_SELECTOR) 242 | .getText()}` 243 | : device.alertText(); 244 | return alertText.replace('\n', ' '); 245 | } 246 | 247 | /** 248 | * Wait for the alert to exist 249 | */ 250 | export function waitForAlert() { 251 | waitFor({ 252 | selector: device.isAndroid ? ANDROID_ALERT_TITLE_SELECTOR : IOS_ALERT_SELECTOR, 253 | state: 'exist', 254 | }); 255 | } 256 | -------------------------------------------------------------------------------- /__tests__/detox/config/detox.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDir": "../../../", 3 | "setupTestFrameworkScriptFile": "./__tests__/detox/config/setup.js", 4 | "testMatch": ["**/detox/**/*.spec.js"] 5 | } 6 | -------------------------------------------------------------------------------- /__tests__/detox/config/setup.js: -------------------------------------------------------------------------------- 1 | const detox = require('detox'); 2 | const config = require('../../../package.json').detox; 3 | 4 | // Set the default test timeout of 120s 5 | jest.setTimeout(120000); 6 | 7 | beforeAll(async () => { 8 | await detox.init(config); 9 | }); 10 | 11 | afterAll(async () => { 12 | await detox.cleanup(); 13 | }); 14 | -------------------------------------------------------------------------------- /__tests__/detox/features/chats.spec.js: -------------------------------------------------------------------------------- 1 | import { screens } from '../screen-objects/base'; 2 | import { addChat, getChatBubble, selectFirstChat } from '../screen-objects/chats'; 3 | import { selectScreenFromTabBar } from '../screen-objects/navigation'; 4 | import { SCREENS, TABBAR } from '../support/constants'; 5 | 6 | describe('Use the Chats', () => { 7 | beforeEach(async () => { 8 | await device.reloadReactNative(); 9 | await selectScreenFromTabBar(TABBAR.CHATS); 10 | await expect(screens(SCREENS.CHATS)).toBeVisible(); 11 | await selectFirstChat(); 12 | }); 13 | 14 | it('should validate the default chats', async () => { 15 | await expect(getChatBubble(0)).toHaveLabel('Hey wassup?'); 16 | await expect(getChatBubble(1)).toHaveLabel('So it seems like this internet thing is here to stay, huh?'); 17 | }); 18 | 19 | it('should able to chat and get a random response back', async () => { 20 | const chat = 'Hello'; 21 | await addChat(chat); 22 | await expect(getChatBubble(0)).toHaveLabel('Hey wassup?'); 23 | await expect(getChatBubble(1)).toHaveLabel('So it seems like this internet thing is here to stay, huh?'); 24 | await expect(getChatBubble(2)).toHaveLabel(chat); 25 | await expect(getChatBubble(3)).toBeVisible(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /__tests__/detox/features/navigation.spec.js: -------------------------------------------------------------------------------- 1 | import { screens } from '../screen-objects/base'; 2 | import { selectFirstChat } from '../screen-objects/chats'; 3 | import { headerBackButton, selectScreenFromTabBar } from '../screen-objects/navigation'; 4 | import { enterURL } from '../screen-objects/webview'; 5 | import { SCREENS, TABBAR } from '../support/constants'; 6 | 7 | describe('Navigate through the app', () => { 8 | beforeEach(async () => { 9 | await device.reloadReactNative(); 10 | }); 11 | 12 | it('should navigate through the app with the tabbar', async () => { 13 | await selectScreenFromTabBar(TABBAR.WEBVIEW); 14 | await expect(screens(SCREENS.WEBVIEW)).toBeVisible(); 15 | await selectScreenFromTabBar(TABBAR.CHATS); 16 | await expect(screens(SCREENS.CHATS)).toBeVisible(); 17 | await selectScreenFromTabBar(TABBAR.HOME); 18 | await expect(screens(SCREENS.HOME)).toBeVisible(); 19 | }); 20 | 21 | it('should navigate through the app by swiping', async () => { 22 | await screens(SCREENS.HOME).swipe('left'); 23 | await expect(screens(SCREENS.WEBVIEW)).toBeVisible(); 24 | await screens(SCREENS.WEBVIEW).swipe('left', 'fast', 0.9); 25 | await expect(screens(SCREENS.CHATS)).toBeVisible(); 26 | await screens(SCREENS.CHATS).swipe('right'); 27 | await expect(screens(SCREENS.WEBVIEW)).toBeVisible(); 28 | await screens(SCREENS.WEBVIEW).swipe('right'); 29 | await expect(screens(SCREENS.HOME)).toBeVisible(); 30 | }); 31 | 32 | it('should be able to use the back button in the webview header', async () => { 33 | await selectScreenFromTabBar(TABBAR.WEBVIEW); 34 | await expect(screens(SCREENS.WEBVIEW)).toBeVisible(); 35 | await enterURL('www.wswebcreation.nl'); 36 | await expect(headerBackButton()).toBeVisible(); 37 | await headerBackButton().tap(); 38 | await expect(screens(SCREENS.WEBVIEW)).toBeVisible(); 39 | }); 40 | 41 | it('should be able to use the back button in the chatbox header', async () => { 42 | await selectScreenFromTabBar(TABBAR.CHATS); 43 | await expect(screens(SCREENS.CHATS)).toBeVisible(); 44 | await selectFirstChat(); 45 | await expect(screens(SCREENS.CHAT_BOX)).toBeVisible(); 46 | await headerBackButton().tap(); 47 | await expect(screens(SCREENS.CHATS)).toBeVisible(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /__tests__/detox/features/webview.spec.js: -------------------------------------------------------------------------------- 1 | import { screens } from '../screen-objects/base'; 2 | import { selectScreenFromTabBar } from '../screen-objects/navigation'; 3 | import { enterURL, validateAlertIsShown } from '../screen-objects/webview'; 4 | import { SCREENS, TABBAR } from '../support/constants'; 5 | 6 | describe('Use the webview', () => { 7 | beforeEach(async () => { 8 | await device.reloadReactNative(); 9 | await selectScreenFromTabBar(TABBAR.WEBVIEW); 10 | await expect(screens(SCREENS.WEBVIEW)).toBeVisible(); 11 | }); 12 | 13 | it('should be able to swipe through the webview without switching context', async () => { 14 | console.warn('NOT SUPPORTED TO SWIPE THE WEBVIEW OR SWIPE BY COORDINATES'); 15 | }); 16 | 17 | it('should be able to scroll through the webview with a Javascript scroll', async () => { 18 | console.warn('NOT SUPPORTED TO MANIPULATE THE WEBVIEW'); 19 | }); 20 | 21 | it('Should be able to see all posts in the testautomation category', async () => { 22 | console.warn('NOT SUPPORTED TO VIEW ELEMENTS IN THE WEBVIEW'); 23 | }); 24 | 25 | it('should be able to validate the error pop', async () => { 26 | const url = 'wswebcreation.nl'; 27 | 28 | await enterURL(url); 29 | await validateAlertIsShown(url); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /__tests__/detox/screen-objects/base.js: -------------------------------------------------------------------------------- 1 | import * as labels from '../../../app/config/labels'; 2 | import { TEST_PREFIX } from '../support/constants'; 3 | 4 | const SCREEN_SELECTORS = { 5 | home: `${TEST_PREFIX}${labels.stackNavigatorTitle.home}`, 6 | webview: `${TEST_PREFIX}${labels.stackNavigatorTitle.webview}`, 7 | chats: `${TEST_PREFIX}${labels.chats.window}`, 8 | chatbox: `${TEST_PREFIX}${labels.stackNavigatorTitle.chatBox}`, 9 | }; 10 | 11 | /** 12 | * Wait for a specific screen to be visible 13 | * @param {string} screen see the 'SCREEN_SELECTORS' const in './constants.js' for all the 14 | * possible values 15 | */ 16 | export function screens(screen) { 17 | return element(by.id(SCREEN_SELECTORS[screen.toLowerCase()])); 18 | } 19 | -------------------------------------------------------------------------------- /__tests__/detox/screen-objects/chats.js: -------------------------------------------------------------------------------- 1 | import * as labels from '../../../app/config/labels'; 2 | import { TEST_PREFIX } from '../support/constants'; 3 | 4 | export const CHAT_SELECTORS = { 5 | FIRST_CHAT: `${TEST_PREFIX}Dick Tracy`, 6 | BUBBLE: `${TEST_PREFIX}${labels.components.messageBubble.accessibilityLabel}`, 7 | INPUT: `${TEST_PREFIX}${labels.components.chatInput.inputAccessibilityLabel}`, 8 | SUBMIT_BUTTON: `${TEST_PREFIX}${labels.components.chatInput.sendAccessibilityLabel}`, 9 | }; 10 | 11 | /** 12 | * Select a chat by clicking on it 13 | * 14 | * @param {string} selector 15 | * 16 | * @return {Promise} 17 | */ 18 | export function selectChat(selector){ 19 | return element(by.id(selector)).tap(); 20 | } 21 | 22 | /** 23 | * Select the first chat 24 | * 25 | * @return {Promise} 26 | */ 27 | export function selectFirstChat(){ 28 | return selectChat(CHAT_SELECTORS.FIRST_CHAT); 29 | } 30 | 31 | /** 32 | * Get back a chat bubble based on an index 33 | * 34 | * @param {number} index 35 | * 36 | * @return {Promise} 37 | */ 38 | export function getChatBubble(index=0){ 39 | return element(by.id(CHAT_SELECTORS.BUBBLE)).atIndex(index); 40 | } 41 | 42 | /** 43 | * Get back a chat bubble based on an index 44 | * 45 | * @param {number} index 46 | * 47 | * @return {Promise} 48 | */ 49 | export async function addChat(chatMessage){ 50 | await element(by.id(CHAT_SELECTORS.INPUT)).typeText(chatMessage); 51 | await element(by.id(CHAT_SELECTORS.SUBMIT_BUTTON)).tap(); 52 | } 53 | -------------------------------------------------------------------------------- /__tests__/detox/screen-objects/navigation.js: -------------------------------------------------------------------------------- 1 | import * as labels from '../../../app/config/labels'; 2 | import { TEST_PREFIX } from '../support/constants'; 3 | 4 | const TABBAR_SELECTORS = { 5 | home: `${TEST_PREFIX}${labels.tabNavigator.home}`, 6 | webview: `${TEST_PREFIX}${labels.tabNavigator.webview}`, 7 | chats: `${TEST_PREFIX}${labels.tabNavigator.chats}`, 8 | }; 9 | 10 | export const HEADER_BACK_BUTTON = `${TEST_PREFIX}${labels.stackNavigatorTitle.goBackAccessibilityLabel}`; 11 | 12 | /** 13 | * Select screen from the tabbar 14 | * @param {string} screen see the 'SCREEN_SELECTORS' const in './constants.js' for all the 15 | * possible values 16 | */ 17 | export function selectScreenFromTabBar(screen) { 18 | /** 19 | * When using an extra view with react-navigation to address the test-id and 20 | * disabling the `tabBarTestIDProps` it still finds multiple elements. 21 | * By adding `atIndex(#)` it will only click on element # 22 | */ 23 | return element(by.id(TABBAR_SELECTORS[screen.toLowerCase()])).atIndex(0).tap(); 24 | } 25 | 26 | /** 27 | * Get the header back button 28 | * 29 | * @return {Promise} 30 | */ 31 | export function headerBackButton(){ 32 | return element(by.id(HEADER_BACK_BUTTON)); 33 | } 34 | -------------------------------------------------------------------------------- /__tests__/detox/screen-objects/webview.js: -------------------------------------------------------------------------------- 1 | import * as labels from '../../../app/config/labels'; 2 | import { TEST_PREFIX } from '../support/constants'; 3 | 4 | const WEBVIEW_SELECTORS = { 5 | INPUT: `${TEST_PREFIX}${labels.webview.textAccessibilityLabel}`, 6 | BUTTON: `${TEST_PREFIX}${labels.webview.button}`, 7 | }; 8 | 9 | /** 10 | * Enter url to load in webview 11 | * 12 | * @param {string} url A url like `www.wswebcreation.nl` or with `http(s)://` in front of it 13 | */ 14 | export async function enterURL(url) { 15 | await element(by.id(WEBVIEW_SELECTORS.INPUT)).replaceText(url.toLowerCase()); 16 | await element(by.id(WEBVIEW_SELECTORS.BUTTON)).tap(); 17 | } 18 | 19 | /** 20 | * Check if the alert is shown 21 | * 22 | * @param {string} url 23 | */ 24 | export async function validateAlertIsShown(url) { 25 | await expect(element(by.text('Alert'))).toBeVisible(); 26 | await expect(element(by.text(`http://${url} is not a valid url!`))).toBeVisible(); 27 | await element(by.text('OK')).tap(); 28 | } 29 | -------------------------------------------------------------------------------- /__tests__/detox/support/constants.js: -------------------------------------------------------------------------------- 1 | export const TEST_PREFIX = 'test-'; 2 | export const SCREENS = { 3 | CHATS: 'chats', 4 | CHAT_BOX: 'chatbox', 5 | HOME: 'home', 6 | WEBVIEW: 'webview', 7 | }; 8 | export const TABBAR = { 9 | CHATS: 'chats', 10 | HOME: 'home', 11 | WEBVIEW: 'webview', 12 | }; 13 | 14 | // export const SCREENSHOTS_FOLDERS = { 15 | // TMP: '.tmp/screenshots/', 16 | // DIST: '.dist/screenshots/', 17 | // }; 18 | // export const SWIPE_DIRECTION = { 19 | // down: { 20 | // start: { x: 50, y: 15 }, 21 | // end: { x: 50, y: 85 }, 22 | // }, 23 | // left: { 24 | // start: { x: 95, y: 50 }, 25 | // end: { x: 15, y: 50 }, 26 | // }, 27 | // right: { 28 | // start: { x: 5, y: 50 }, 29 | // end: { x: 95, y: 50 }, 30 | // }, 31 | // up: { 32 | // start: { x: 50, y: 85 }, 33 | // end: { x: 50, y: 15 }, 34 | // }, 35 | // }; 36 | // export const CONTEXT_REF = { 37 | // NATIVE: 'native', 38 | // WEBVIEW: 'webview', 39 | // }; 40 | // export const DOCUMENT_READY_STATE = { 41 | // COMPLETE: 'complete', 42 | // INTERACTIVE: 'interactive', 43 | // LOADING: 'loading', 44 | // }; 45 | // export const NATIVE_APP = 'NATIVE_APP'; 46 | // export const WAIT_FOR_STATE = { 47 | // EXIST: 'exist', 48 | // VISIBLE: 'visible', 49 | // }; 50 | // export const INCORRECT_URL = 'in.correct.url'; 51 | // 52 | // /** 53 | // * Cross-platform Text selectors 54 | // */ 55 | // export const ANDROID_TEXT_SELECTOR = '*//android.widget.TextView'; 56 | // export const ANDROID_ALERT_TITLE_SELECTOR = '*//android.widget.TextView[@resource-id="android:id/alertTitle"]'; 57 | // export const ANDROID_ALERT_MESSAGE_SELECTOR = '*//android.widget.TextView[@resource-id="android:id/message"]'; 58 | // export const ANDROID_ACCEPT_ALERT_SELECTOR = '*//android.widget.Button[@text="OK"]'; 59 | // export const IOS_ALERT_SELECTOR = '*//XCUIElementTypeAlert'; 60 | // export const IOS_TEXT_SELECTOR = null; 61 | -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | lib_deps = [] 12 | 13 | for jarfile in glob(['libs/*.jar']): 14 | name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')] 15 | lib_deps.append(':' + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | 21 | for aarfile in glob(['libs/*.aar']): 22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')] 23 | lib_deps.append(':' + name) 24 | android_prebuilt_aar( 25 | name = name, 26 | aar = aarfile, 27 | ) 28 | 29 | android_library( 30 | name = "all-libs", 31 | exported_deps = lib_deps, 32 | ) 33 | 34 | android_library( 35 | name = "app-code", 36 | srcs = glob([ 37 | "src/main/java/**/*.java", 38 | ]), 39 | deps = [ 40 | ":all-libs", 41 | ":build_config", 42 | ":res", 43 | ], 44 | ) 45 | 46 | android_build_config( 47 | name = "build_config", 48 | package = "com.wswebcreation", 49 | ) 50 | 51 | android_resource( 52 | name = "res", 53 | package = "com.wswebcreation", 54 | res = "src/main/res", 55 | ) 56 | 57 | android_binary( 58 | name = "app", 59 | keystore = "//android/keystores:debug", 60 | manifest = "src/main/AndroidManifest.xml", 61 | package_type = "debug", 62 | deps = [ 63 | ":app-code", 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" 3 | 4 | import com.android.build.OutputFile 5 | 6 | /** 7 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 8 | * and bundleReleaseJsAndAssets). 9 | * These basically call `react-native bundle` with the correct arguments during the Android build 10 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 11 | * bundle directly from the development server. Below you can see all the possible configurations 12 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 13 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 14 | * 15 | * project.ext.react = [ 16 | * // the name of the generated asset file containing your JS bundle 17 | * bundleAssetName: "index.android.bundle", 18 | * 19 | * // the entry file for bundle generation 20 | * entryFile: "index.android.js", 21 | * 22 | * // whether to bundle JS and assets in debug mode 23 | * bundleInDebug: false, 24 | * 25 | * // whether to bundle JS and assets in release mode 26 | * bundleInRelease: true, 27 | * 28 | * // whether to bundle JS and assets in another build variant (if configured). 29 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 30 | * // The configuration property can be in the following formats 31 | * // 'bundleIn${productFlavor}${buildType}' 32 | * // 'bundleIn${buildType}' 33 | * // bundleInFreeDebug: true, 34 | * // bundleInPaidRelease: true, 35 | * // bundleInBeta: true, 36 | * 37 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 38 | * // for example: to disable dev mode in the staging build type (if configured) 39 | * devDisabledInStaging: true, 40 | * // The configuration property can be in the following formats 41 | * // 'devDisabledIn${productFlavor}${buildType}' 42 | * // 'devDisabledIn${buildType}' 43 | * 44 | * // the root of your project, i.e. where "package.json" lives 45 | * root: "../../", 46 | * 47 | * // where to put the JS bundle asset in debug mode 48 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 49 | * 50 | * // where to put the JS bundle asset in release mode 51 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 52 | * 53 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 54 | * // require('./image.png')), in debug mode 55 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 56 | * 57 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 58 | * // require('./image.png')), in release mode 59 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 60 | * 61 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 62 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 63 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 64 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 65 | * // for example, you might want to remove it from here. 66 | * inputExcludes: ["android/**", "ios/**"], 67 | * 68 | * // override which node gets called and with what additional arguments 69 | * nodeExecutableAndArgs: ["node"], 70 | * 71 | * // supply additional arguments to the packager 72 | * extraPackagerArgs: [] 73 | * ] 74 | */ 75 | 76 | project.ext.react = [ 77 | entryFile: "index.js" 78 | ] 79 | 80 | apply from: "../../node_modules/react-native/react.gradle" 81 | 82 | /** 83 | * Set this to true to create two separate APKs instead of one: 84 | * - An APK that only works on ARM devices 85 | * - An APK that only works on x86 devices 86 | * The advantage is the size of the APK is reduced by about 4MB. 87 | * Upload all the APKs to the Play Store and people will download 88 | * the correct one based on the CPU architecture of their device. 89 | */ 90 | def enableSeparateBuildPerCPUArchitecture = false 91 | 92 | /** 93 | * Run Proguard to shrink the Java bytecode in release builds. 94 | */ 95 | def enableProguardInReleaseBuilds = false 96 | 97 | android { 98 | compileSdkVersion rootProject.ext.compileSdkVersion 99 | buildToolsVersion rootProject.ext.buildToolsVersion 100 | 101 | defaultConfig { 102 | applicationId "com.wswebcreation" 103 | minSdkVersion rootProject.ext.minSdkVersion 104 | targetSdkVersion rootProject.ext.targetSdkVersion 105 | versionCode 1 106 | versionName "1.0" 107 | ndk { 108 | abiFilters "armeabi-v7a", "x86" 109 | } 110 | } 111 | splits { 112 | abi { 113 | reset() 114 | enable enableSeparateBuildPerCPUArchitecture 115 | universalApk false // If true, also generate a universal APK 116 | include "armeabi-v7a", "x86" 117 | } 118 | } 119 | buildTypes { 120 | release { 121 | minifyEnabled enableProguardInReleaseBuilds 122 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 123 | } 124 | } 125 | // applicationVariants are e.g. debug, release 126 | applicationVariants.all { variant -> 127 | variant.outputs.each { output -> 128 | // For each separate APK per architecture, set a unique version code as described here: 129 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 130 | def versionCodes = ["armeabi-v7a":1, "x86":2] 131 | def abi = output.getFilter(OutputFile.ABI) 132 | if (abi != null) { // null for the universal-debug, universal-release variants 133 | output.versionCodeOverride = 134 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 135 | } 136 | } 137 | } 138 | } 139 | 140 | dependencies { 141 | compile project(':react-native-restart') 142 | compile project(':react-native-vector-icons') 143 | compile project(':react-native-config') 144 | compile project(':react-native-vector-icons') 145 | compile fileTree(dir: "libs", include: ["*.jar"]) 146 | implementation fileTree(dir: "libs", include: ["*.jar"]) 147 | implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" 148 | implementation "com.facebook.react:react-native:+" // From node_modules 149 | } 150 | 151 | // Run this once to be able to run the application with BUCK 152 | // puts all compile dependencies into folder libs for BUCK to use 153 | task copyDownloadableDepsToLibs(type: Copy) { 154 | from configurations.compile 155 | into 'libs' 156 | } 157 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 11 | 12 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/Entypo.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/EvilIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/EvilIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Feather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/Feather.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Foundation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/Foundation.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/Octicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/RobotoMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/RobotoMono-BoldItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/RobotoMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/RobotoMono-Italic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/RobotoMono-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/RobotoMono-Light.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/RobotoMono-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/RobotoMono-LightItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/RobotoMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/RobotoMono-Medium.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/RobotoMono-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/RobotoMono-MediumItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/RobotoMono-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/RobotoMono-Thin.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/RobotoMono-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/RobotoMono-ThinItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/SimpleLineIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/SimpleLineIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Zocial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/assets/fonts/Zocial.ttf -------------------------------------------------------------------------------- /android/app/src/main/java/com/wswebcreation/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wswebcreation; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "wswebcreation"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/wswebcreation/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.wswebcreation; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactApplication; 6 | import com.avishayil.rnrestart.ReactNativeRestartPackage; 7 | import com.oblador.vectoricons.VectorIconsPackage; 8 | import com.lugg.ReactNativeConfig.ReactNativeConfigPackage; 9 | import com.facebook.react.ReactNativeHost; 10 | import com.facebook.react.ReactPackage; 11 | import com.facebook.react.shell.MainReactPackage; 12 | import com.facebook.soloader.SoLoader; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | public class MainApplication extends Application implements ReactApplication { 18 | 19 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 20 | @Override 21 | public boolean getUseDeveloperSupport() { 22 | return BuildConfig.DEBUG; 23 | } 24 | 25 | @Override 26 | protected List getPackages() { 27 | return Arrays.asList( 28 | new MainReactPackage(), 29 | new ReactNativeRestartPackage(), 30 | new ReactNativeConfigPackage(), 31 | new VectorIconsPackage() 32 | ); 33 | } 34 | 35 | @Override 36 | protected String getJSMainModuleName() { 37 | return "index"; 38 | } 39 | }; 40 | 41 | @Override 42 | public ReactNativeHost getReactNativeHost() { 43 | return mReactNativeHost; 44 | } 45 | 46 | @Override 47 | public void onCreate() { 48 | super.onCreate(); 49 | SoLoader.init(this, /* native exopackage */ false); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/res/drawable-hdpi/screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/res/drawable-mdpi/screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/res/drawable-xhdpi/screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/res/drawable-xxhdpi/screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/res/mipmap-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | wswebcreation 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "27.0.3" 6 | minSdkVersion = 16 7 | compileSdkVersion = 27 8 | targetSdkVersion = 26 9 | supportLibVersion = "27.1.1" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath 'com.android.tools.build:gradle:3.1.4' 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | mavenLocal() 26 | google() 27 | jcenter() 28 | maven { 29 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 30 | url "$rootDir/../node_modules/react-native/android" 31 | } 32 | } 33 | } 34 | 35 | 36 | task wrapper(type: Wrapper) { 37 | gradleVersion = '4.4' 38 | distributionUrl = distributionUrl.replace("bin", "all") 39 | } 40 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = "debug", 3 | properties = "debug.keystore.properties", 4 | store = "debug.keystore", 5 | visibility = [ 6 | "PUBLIC", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'wswebcreation' 2 | include ':react-native-restart' 3 | project(':react-native-restart').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-restart/android') 4 | include ':react-native-vector-icons' 5 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') 6 | include ':react-native-config' 7 | project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android') 8 | include ':react-native-config' 9 | project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android') 10 | include ':react-native-vector-icons' 11 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') 12 | 13 | include ':app' 14 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wswebcreation", 3 | "displayName": "wswebcreation" 4 | } -------------------------------------------------------------------------------- /app/assets/fonts/Roboto_Mono/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/fonts/Roboto_Mono/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Roboto_Mono/RobotoMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/fonts/Roboto_Mono/RobotoMono-BoldItalic.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Roboto_Mono/RobotoMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/fonts/Roboto_Mono/RobotoMono-Italic.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Roboto_Mono/RobotoMono-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/fonts/Roboto_Mono/RobotoMono-Light.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Roboto_Mono/RobotoMono-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/fonts/Roboto_Mono/RobotoMono-LightItalic.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Roboto_Mono/RobotoMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/fonts/Roboto_Mono/RobotoMono-Medium.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Roboto_Mono/RobotoMono-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/fonts/Roboto_Mono/RobotoMono-MediumItalic.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Roboto_Mono/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/fonts/Roboto_Mono/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Roboto_Mono/RobotoMono-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/fonts/Roboto_Mono/RobotoMono-Thin.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Roboto_Mono/RobotoMono-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/fonts/Roboto_Mono/RobotoMono-ThinItalic.ttf -------------------------------------------------------------------------------- /app/assets/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/app/assets/loader.gif -------------------------------------------------------------------------------- /app/components/BorderText.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Text, StyleSheet, Platform } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | class BorderText extends PureComponent { 6 | 7 | static propTypes = { 8 | text: PropTypes.string.isRequired, 9 | }; 10 | 11 | render() { 12 | const { text } = this.props; 13 | return ( 14 | 17 | {text.toUpperCase()} 18 | 19 | ); 20 | } 21 | } 22 | 23 | const styles = StyleSheet.create({ 24 | headerBorder: { 25 | backgroundColor: '#fff', 26 | borderColor: '#000', 27 | borderWidth: 3, 28 | ...Platform.select({ 29 | ios: { 30 | paddingBottom: 9, 31 | }, 32 | android: { 33 | paddingBottom: 4, 34 | }, 35 | }), 36 | paddingLeft: 13, 37 | paddingRight: 13, 38 | paddingTop: 9, 39 | textAlign: 'center' 40 | }, 41 | header: { 42 | color: '#000', 43 | fontFamily: 'RobotoMono-Bold', 44 | fontSize: 20, 45 | }, 46 | }); 47 | 48 | export { BorderText }; 49 | -------------------------------------------------------------------------------- /app/components/ChatInput.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, TouchableOpacity, TextInput } from 'react-native'; 3 | import { Icon } from 'react-native-elements'; 4 | import KeyboardSpacer from 'react-native-keyboard-spacer'; 5 | import PropTypes from 'prop-types'; 6 | import { IS_IOS, SCREEN_HEIGHT } from '../config/Constants'; 7 | import * as labels from '../config/labels.json'; 8 | import { testProperties } from '../config/TestProperties'; 9 | 10 | const defaultHeight = 30; 11 | 12 | class ChatInput extends Component { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | height: defaultHeight 17 | }; 18 | } 19 | 20 | static defaultProps = { 21 | onChangeText: () => { 22 | }, 23 | onPress: () => { 24 | }, 25 | }; 26 | 27 | static propTypes = { 28 | onChangeText: PropTypes.func.isRequired, 29 | onPress: PropTypes.func.isRequired, 30 | value: PropTypes.string.isRequired, 31 | }; 32 | 33 | updateSize(autoHeight) { 34 | const maxInputHeight = 0.25 * SCREEN_HEIGHT; 35 | autoHeight = autoHeight < defaultHeight ? defaultHeight : autoHeight; 36 | this.setState({ 37 | height: autoHeight > maxInputHeight ? maxInputHeight + 10 : autoHeight + 10 38 | }); 39 | } 40 | 41 | renderKeyboardSpacer = () => { 42 | if (IS_IOS) { 43 | return ; 44 | } 45 | }; 46 | 47 | render() { 48 | const { height } = this.state; 49 | let newStyle = { 50 | height 51 | }; 52 | return ( 53 | 54 | 55 | 58 | 63 | 64 | 71 | this.updateSize(e.nativeEvent.contentSize.height) 72 | } 73 | placeholder={labels.components.chatInput.placeholder} 74 | placeholderTextColor="#EDEDED" 75 | returnKeyType="next" 76 | selectionColor="white" 77 | style={[styles.input, newStyle]} 78 | underlineColorAndroid="transparent" 79 | {...testProperties(labels.components.chatInput.inputAccessibilityLabel)} 80 | /> 81 | 85 | 90 | 91 | 92 | {this.renderKeyboardSpacer()} 93 | 94 | ); 95 | } 96 | } 97 | 98 | const styles = StyleSheet.create({ 99 | container: { 100 | flexDirection: 'row', 101 | alignSelf: 'flex-end', 102 | marginTop: 10, 103 | backgroundColor: '#fff', 104 | borderColor: '#afafaf', 105 | borderTopWidth: 1, 106 | paddingBottom: 5, 107 | paddingTop: 5, 108 | }, 109 | input: { 110 | color: '#000', 111 | flex: 1, 112 | fontSize: 16, 113 | backgroundColor: '#fff', 114 | borderColor: '#afafaf', 115 | borderWidth: 1, 116 | borderRadius: 25, 117 | paddingTop: 10, 118 | paddingBottom: 5, 119 | paddingLeft: 17, 120 | paddingRight: 17, 121 | }, 122 | chatIcons: { 123 | color: '#1b73e3', 124 | marginLeft: 10, 125 | marginRight: 10, 126 | marginBottom: 5, 127 | marginTop: 5, 128 | fontSize: 28, 129 | width: 35, 130 | }, 131 | }); 132 | 133 | export { ChatInput }; 134 | -------------------------------------------------------------------------------- /app/components/CustomHeader.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Text, StyleSheet, TouchableOpacity } from 'react-native'; 3 | import { Icon } from 'react-native-elements'; 4 | import PropTypes from 'prop-types'; 5 | import { testProperties } from '../config/TestProperties'; 6 | import * as labels from '../config/labels.json'; 7 | 8 | class CustomHeader extends Component { 9 | constructor(props) { 10 | super(props); 11 | } 12 | 13 | static defaultProps = { 14 | onPress: () => { 15 | }, 16 | testID: labels.stackNavigatorTitle.goBackAccessibilityLabel, 17 | image: null 18 | }; 19 | 20 | static propTypes = { 21 | onPress: PropTypes.func.isRequired, 22 | text: PropTypes.string.isRequired, 23 | }; 24 | 25 | render() { 26 | return ( 27 | 32 | 37 | {this.props.image || null} 38 | 39 | {this.props.text} 40 | 41 | 42 | ); 43 | } 44 | } 45 | 46 | const styles = StyleSheet.create({ 47 | leftHeaderContainer: { 48 | alignItems: 'flex-start', 49 | flexDirection: 'row' 50 | }, 51 | headerText: { 52 | color: 'black', 53 | fontSize: 15, 54 | fontWeight: 'bold', 55 | alignSelf: 'center', 56 | paddingLeft: 7 57 | }, 58 | icon: { 59 | color: '#1b73e3', 60 | marginLeft: 10, 61 | marginRight: 10, 62 | marginBottom: 5, 63 | marginTop: 5, 64 | fontSize: 28, 65 | width: 35, 66 | }, 67 | }); 68 | 69 | export { CustomHeader }; 70 | -------------------------------------------------------------------------------- /app/components/MessageBubble.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Text, StyleSheet, View } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | import * as labels from '../config/labels.json'; 5 | import { testProperties } from '../config/TestProperties'; 6 | 7 | class MessageBubble extends PureComponent { 8 | static defaultProps = { 9 | placeRight: false, 10 | }; 11 | static propTypes = { 12 | placeRight: PropTypes.bool.isRequired, 13 | message: PropTypes.string.isRequired, 14 | }; 15 | 16 | render() { 17 | const { placeRight, message } = this.props; 18 | return ( 19 | 23 | 24 | {message} 25 | 26 | 27 | ); 28 | } 29 | } 30 | 31 | const styles = StyleSheet.create({ 32 | leftMessage: { 33 | backgroundColor: '#fff', 34 | padding: 10, 35 | borderRadius: 5, 36 | marginLeft: 15, 37 | marginBottom: 5, 38 | marginTop: 5, 39 | marginRight: 75, 40 | alignSelf: 'flex-start' 41 | }, 42 | rightMessage: { 43 | backgroundColor: '#dbf8c6', 44 | padding: 10, 45 | borderRadius: 5, 46 | marginRight: 15, 47 | marginBottom: 5, 48 | marginTop: 5, 49 | marginLeft: 75, 50 | alignSelf: 'flex-end' 51 | }, 52 | }); 53 | 54 | export { MessageBubble }; 55 | -------------------------------------------------------------------------------- /app/config/Api.js: -------------------------------------------------------------------------------- 1 | import { 2 | API_HEADERS, 3 | BASE_URL, 4 | CHAT_HISTORY_URL, 5 | CONVERSATION_URL, 6 | ONE_LINERS_URL 7 | } from './Constants'; 8 | 9 | const apiConfig = (path) => { 10 | return fetch(`${BASE_URL}${path}`, { 11 | method: 'GET', 12 | headers: { 13 | ...API_HEADERS, 14 | } 15 | }) 16 | .then(response => response.json()) 17 | .catch((error) => console.warn('error = ', error)); 18 | }; 19 | 20 | export const api = { 21 | getConversation() { 22 | return apiConfig(CONVERSATION_URL); 23 | }, 24 | getChatHistory() { 25 | return apiConfig(CHAT_HISTORY_URL); 26 | }, 27 | getOneLinersResponse() { 28 | return apiConfig(ONE_LINERS_URL); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /app/config/AutomationRestart.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, TouchableOpacity } from 'react-native'; 3 | import RNRestart from 'react-native-restart'; 4 | import { IS_AUTOMATION_BUILD } from './Constants'; 5 | import { testProperties } from './TestProperties'; 6 | import labels from './labels.json'; 7 | 8 | class AutomationRestartLink extends Component { 9 | handleRestartApp = () => RNRestart.Restart(); 10 | 11 | render() { 12 | return IS_AUTOMATION_BUILD ? ( 13 | 18 | ) : null; 19 | } 20 | } 21 | 22 | const styles = StyleSheet.create({ 23 | container: { 24 | flex: 1, 25 | position: 'absolute', 26 | backgroundColor: 'transparent', 27 | bottom: 0, 28 | right: 0, 29 | height: 1, 30 | width: 1, 31 | }, 32 | }); 33 | 34 | export default AutomationRestartLink; 35 | -------------------------------------------------------------------------------- /app/config/Constants.js: -------------------------------------------------------------------------------- 1 | import { Dimensions, ListView, Platform } from 'react-native'; 2 | import Config from 'react-native-config'; 3 | 4 | /** 5 | * API stuff 6 | */ 7 | export const BASE_URL = Config.BASE_URL; 8 | export const API_HEADERS = { 9 | 'Content-Type': 'application/x-www-form-urlencoded', 10 | }; 11 | export const CONVERSATION_URL = '649da4cc49beb5bc180534e039535168/raw/2975c9f42763917b1f12e90512472e5ca34623ca/conversation.json'; 12 | export const CHAT_HISTORY_URL = 'b1979ed905833186e5526a3bcf31bc76/raw/7d8219c2d6d1c5f37d535018fd6c56ec9eb2a849/chat.history.json'; 13 | export const ONE_LINERS_URL = '46101d9f04bbc90622e68c6f2ac2bdce/raw/88f23f10983d99dbf98524a487870ff9bf6684c7/onliners.response.json'; 14 | 15 | /** 16 | * Device stuff 17 | */ 18 | export const IS_IOS = Platform.OS === 'ios'; 19 | export const { 20 | width: SCREEN_WIDTH, 21 | height: SCREEN_HEIGHT, 22 | } = Dimensions.get('window'); 23 | export const ENV_STRINGS = { 24 | AUTOMATION: 'automation', 25 | DEV: 'development', 26 | }; 27 | export const ENVIRONMENT = Config.ENVIRONMENT; 28 | export const IS_AUTOMATION_BUILD = ENVIRONMENT === ENV_STRINGS.AUTOMATION; 29 | export const TESTING_ENVIRONMENTS = [ENV_STRINGS.DEV, ENV_STRINGS.AUTOMATION]; 30 | export const APP_URI = 'wswebcreationapp'; 31 | export const DEEPLINK_MOCKED_USER = { 32 | firstName: 'Dick', 33 | lastName: 'Tracy', 34 | conversation: [ 35 | { 36 | placeRight: true, 37 | message: 'So it seems like this internet thing is here to stay, huh?' 38 | }, 39 | { 40 | placeRight: false, 41 | message: 'Hey wassup?', 42 | }, 43 | ], 44 | read: true, 45 | lastMessage: 'So it seems like this internet thing is here to stay, huh?', 46 | date: '09-Dec-2017', 47 | time: '1:33 PM', 48 | image: 'https://randomuser.me/api/portraits/men/3.jpg' 49 | }; 50 | -------------------------------------------------------------------------------- /app/config/TestProperties.js: -------------------------------------------------------------------------------- 1 | import { Animated } from 'react-native'; 2 | import getTransitionConfig from 'react-navigation/src/views/CardStack/TransitionConfigs'; 3 | import { 4 | ENVIRONMENT, 5 | IS_AUTOMATION_BUILD, 6 | IS_IOS, 7 | TESTING_ENVIRONMENTS 8 | } from './Constants'; 9 | 10 | export function testProperties(id) { 11 | if (TESTING_ENVIRONMENTS.includes(ENVIRONMENT)) { 12 | // Detox checks if an element is accessible, so that's why `accessible: true,` needs to be added, 13 | // this will only be done with on the DEV build 14 | const accessible = { 15 | accessible: !IS_AUTOMATION_BUILD 16 | }; 17 | 18 | if (IS_IOS) { 19 | return { ...accessible, testID: `test-${id}` }; 20 | } 21 | 22 | return { ...accessible, accessibilityLabel: `test-${id}` }; 23 | } 24 | return null; 25 | } 26 | 27 | /** 28 | * Setup the app for a specific automation build 29 | */ 30 | export function setupAutomation() { 31 | if (!IS_AUTOMATION_BUILD) { 32 | return; 33 | } 34 | // Disable the yellow box 35 | console.disableYellowBox = true; // eslint-disable-line no-console 36 | disableAnimations(); 37 | } 38 | 39 | /** 40 | * Disable all animations 41 | */ 42 | function disableAnimations() { 43 | const stubs = require('stubs'); 44 | const AnimatedTiming = Animated.timing; 45 | stubs(Animated, 'timing', (...props) => { 46 | props[1].duration = 0; // eslint-disable-line no-param-reassign 47 | props[1].delay = 0; // eslint-disable-line no-param-reassign 48 | return AnimatedTiming(...props); 49 | }); 50 | // @todo: Dirty hack for now to stub the card duration, need to find a better way 51 | stubs(getTransitionConfig, 'getTransitionConfig', () => ({})); 52 | } 53 | -------------------------------------------------------------------------------- /app/config/labels.json: -------------------------------------------------------------------------------- 1 | { 2 | "chats": { 3 | "window": "Chats window", 4 | "loadingText": "Retrieving Chats..." 5 | }, 6 | "components": { 7 | "chatInput": { 8 | "addAccessibilityLabel": "Add", 9 | "inputAccessibilityLabel": "Message input", 10 | "placeholder": "Type a message...", 11 | "sendAccessibilityLabel": "Send" 12 | }, 13 | "messageBubble": { 14 | "accessibilityLabel": "Bubble", 15 | "your": "Your", 16 | "me": "My" 17 | } 18 | }, 19 | "stackNavigatorTitle": { 20 | "chatBox": "Chat box", 21 | "chats": "Chats screen", 22 | "home": "Home screen", 23 | "webview": "Enter an url and submit it.", 24 | "goBackAccessibilityLabel": "Go back" 25 | }, 26 | "tabNavigator": { 27 | "chats": "Chats", 28 | "home": "Home", 29 | "webview": "Webview" 30 | }, 31 | "webview": { 32 | "button": "Go", 33 | "errorMessage": "is not a valid url!", 34 | "loadingText": "Loading your requested website", 35 | "placeHolder": "http://www.wswebcreation.nl", 36 | "textAccessibilityLabel": "url input" 37 | }, 38 | "restartDot": "restartDot" 39 | } 40 | -------------------------------------------------------------------------------- /app/config/router.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Platform, View } from 'react-native'; 3 | import { StackNavigator, TabNavigator } from 'react-navigation'; 4 | import { Icon } from 'react-native-elements'; 5 | 6 | import Home from '../screens/Home'; 7 | import WebViewScreen from '../screens/Webview'; 8 | import Chats from '../screens/Chats'; 9 | import ChatBox from '../screens/ChatBox'; 10 | import WebViewSelection from '../screens/WebviewSelection'; 11 | import StorybookInApp from '../../storybook/storybook.app'; 12 | import { APP_URI, ENVIRONMENT, IS_IOS, TESTING_ENVIRONMENTS } from './Constants'; 13 | import { testProperties } from './TestProperties'; 14 | import * as labels from './labels.json'; 15 | 16 | const Tabs = TabNavigator({ 17 | Home: { 18 | screen: Home, 19 | navigationOptions: { 20 | tabBarLabel: labels.tabNavigator.home, 21 | tabBarIcon: ({ tintColor }) => 22 | 23 | 29 | , 30 | tabBarTestIDProps: { 31 | ...testProperties(labels.tabNavigator.home), 32 | }, 33 | }, 34 | }, 35 | Webview: { 36 | screen: WebViewSelection, 37 | navigationOptions: { 38 | tabBarLabel: labels.tabNavigator.webview, 39 | tabBarIcon: ({ tintColor }) => 40 | 41 | 47 | , 48 | tabBarTestIDProps: { 49 | ...testProperties(labels.tabNavigator.webview), 50 | }, 51 | }, 52 | }, 53 | Chats: { 54 | screen: Chats, 55 | navigationOptions: { 56 | tabBarLabel: labels.tabNavigator.chats, 57 | tabBarIcon: ({ tintColor }) => 58 | 59 | 65 | , 66 | tabBarTestIDProps: { 67 | ...testProperties(labels.tabNavigator.chats), 68 | }, 69 | }, 70 | }, 71 | }, 72 | { 73 | swipeEnabled: true, 74 | tabBarPosition: 'bottom', 75 | tabBarOptions: { 76 | showIcon: true, 77 | activeTintColor: '#2980b9', 78 | inactiveTintColor: '#999999', 79 | style: { 80 | backgroundColor: '#ffffff', 81 | ...Platform.select({ 82 | android: { 83 | height: 60, 84 | }, 85 | }), 86 | }, 87 | indicatorStyle: { 88 | backgroundColor: 'white' 89 | }, 90 | } 91 | } 92 | ); 93 | 94 | const Routes = { 95 | Root: { 96 | screen: Tabs, 97 | }, 98 | ChatBox: { 99 | screen: ChatBox, 100 | path:'chatbox/:person', 101 | }, 102 | WebViewScreen: { 103 | screen: WebViewScreen, 104 | }, 105 | }; 106 | 107 | if (TESTING_ENVIRONMENTS.includes(ENVIRONMENT)) { 108 | Routes.StoryBook = { 109 | screen: StorybookInApp, 110 | navigationOptions: { 111 | header: null 112 | }, 113 | path: 'storybook/' 114 | } 115 | } 116 | 117 | const Router = StackNavigator(Routes, { 118 | navigationOptions: { 119 | headerTitleStyle: { 120 | alignSelf: 'center', 121 | color: '#000', 122 | textAlign: 'center', 123 | }, 124 | } 125 | }); 126 | 127 | export class StackMainNavigation extends Component { 128 | render() { 129 | return ( 130 | 133 | ); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/screens/ChatBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | StyleSheet, 4 | Image, 5 | View, 6 | ListView, 7 | } from 'react-native'; 8 | import InvertibleScrollView from 'react-native-invertible-scroll-view'; 9 | import { ChatInput } from '../components/ChatInput'; 10 | import { MessageBubble } from '../components/MessageBubble'; 11 | import { CustomHeader } from '../components/CustomHeader'; 12 | import { api } from '../config/Api'; 13 | import { DEEPLINK_MOCKED_USER } from '../config/Constants'; 14 | import * as labels from '../config/labels'; 15 | import { testProperties } from '../config/TestProperties'; 16 | 17 | let conversation; 18 | const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 != r2 }); 19 | 20 | class ChatBox extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | conversation = this.props.navigation.state.params.person.conversation || DEEPLINK_MOCKED_USER.conversation; 24 | this.state = { 25 | message: '', 26 | placeRight: true, 27 | datasource: ds.cloneWithRows(conversation), 28 | }; 29 | } 30 | 31 | static navigationOptions({ navigation }) { 32 | const person = typeof navigation.state.params.person === 'string' ? DEEPLINK_MOCKED_USER : navigation.state.params.person; 33 | const headerLeft = ( 34 | navigation.goBack(null)} 36 | image={ 37 | } 42 | text={`${person.firstName} ${person.lastName}`} 43 | /> 44 | ); 45 | return { headerLeft }; 46 | }; 47 | 48 | responses() { 49 | return api.getOneLinersResponse(); 50 | } 51 | 52 | async send() { 53 | if (this.state.message.length > 0) { 54 | const replies = await this.responses(); 55 | const randomNumber = Math.floor(Math.random() * replies.length); 56 | conversation.unshift({ 57 | placeRight: false, 58 | message: replies[randomNumber], 59 | }, { 60 | placeRight: true, 61 | message: this.state.message 62 | }); 63 | this.setState({ 64 | datasource: ds.cloneWithRows(conversation), 65 | message: '' 66 | }); 67 | } 68 | } 69 | 70 | render() { 71 | return ( 72 | 76 | ( 82 | )} 86 | enableEmptySections 87 | noScroll 88 | renderScrollComponent={props => } 89 | /> 90 | this.setState({ message })} 92 | onPress={() => this.send()} 93 | value={this.state.message} 94 | /> 95 | 96 | ); 97 | } 98 | } 99 | 100 | export default ChatBox; 101 | 102 | const styles = StyleSheet.create({ 103 | leftHeaderContainer: { 104 | alignItems: 'flex-start', 105 | flexDirection: 'row' 106 | }, 107 | chatInitStyle: { 108 | borderRadius: 16, 109 | width: 35, 110 | height: 35 111 | }, 112 | nameText: { 113 | color: 'black', 114 | fontSize: 15, 115 | fontWeight: 'bold', 116 | alignSelf: 'center', 117 | paddingLeft: 7 118 | }, 119 | icon: { 120 | width: 300, 121 | height: 300, 122 | alignSelf: 'center', 123 | }, 124 | mainContainer: { 125 | flex: 1, 126 | backgroundColor: '#f2f2f2', 127 | }, 128 | chatIcons: { 129 | color: '#1b73e3', 130 | marginLeft: 10, 131 | marginRight: 10, 132 | marginBottom: 5, 133 | marginTop: 5, 134 | fontSize: 28, 135 | width: 35, 136 | }, 137 | }); 138 | -------------------------------------------------------------------------------- /app/screens/Chats.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | StyleSheet, 4 | Text, 5 | ListView, 6 | Image, 7 | View, TouchableOpacity 8 | } from 'react-native'; 9 | import Icon from 'react-native-vector-icons/MaterialIcons'; 10 | import { BorderText } from '../components/BorderText'; 11 | import { api } from '../config/Api'; 12 | import { testProperties } from '../config/TestProperties'; 13 | import ChatBox from './ChatBox'; 14 | import * as labels from '../config/labels.json'; 15 | 16 | const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 != r2 }) 17 | 18 | export default class Chats extends React.Component { 19 | static navigationOptions = { 20 | title: labels.stackNavigatorTitle.chats, 21 | }; 22 | 23 | constructor(props) { 24 | super(props); 25 | this.state = { 26 | peopleDataSource: ds.cloneWithRows([]), 27 | loaded: false 28 | } 29 | } 30 | 31 | renderChatsOverview = () => ( 32 | 35 | this.renderPersonRow(person)} 40 | /> 41 | 42 | ); 43 | 44 | renderRetrievingChatsContainer = () => ( 45 | 46 | 49 | 50 | ); 51 | 52 | render() { 53 | return this.state.loaded ? this.renderChatsOverview() : this.renderRetrievingChatsContainer(); 54 | } 55 | 56 | renderPersonRow(person) { 57 | return ( 58 | this.props.navigation.navigate('ChatBox', { person })} 60 | {...testProperties(`${person.firstName} ${person.lastName}`)} 61 | > 62 | 63 | 64 | 69 | 70 | 71 | 72 | 73 | 74 | {`${person.firstName} ${person.lastName}`} 75 | 76 | 77 | 78 | {person.time} 79 | 80 | 81 | 82 | 88 | 91 | {person.lastMessage} 92 | 93 | 94 | 95 | 96 | 97 | ) 98 | } 99 | 100 | async componentDidMount() { 101 | const chats = await api.getChatHistory(); 102 | this.setState({ 103 | peopleDataSource: ds.cloneWithRows(chats), 104 | loaded: true 105 | }) 106 | } 107 | } 108 | 109 | const styles = StyleSheet.create({ 110 | listItemContainer: { 111 | backgroundColor: '#fff', 112 | flex: 1, 113 | flexDirection: 'row', 114 | alignItems: 'center', 115 | padding: 10 116 | }, 117 | loaderContainer: { 118 | backgroundColor: '#fff', 119 | flex: 1, 120 | flexDirection: 'column', 121 | justifyContent: 'center', 122 | alignItems: 'center', 123 | }, 124 | iconContainer: { 125 | flex: 1, 126 | alignItems: 'flex-start' 127 | }, 128 | callerDetailsContainer: { 129 | flex: 4, 130 | justifyContent: 'center', 131 | borderBottomColor: 'rgba(92,94,94,0.5)', 132 | borderBottomWidth: 0.25 133 | }, 134 | callerDetailsContainerWrap: { 135 | flex: 1, 136 | alignItems: 'center', 137 | flexDirection: 'row' 138 | }, 139 | nameContainer: { 140 | alignItems: 'flex-start', 141 | flex: 1 142 | }, 143 | dateContainer: { 144 | alignItems: 'flex-end' 145 | }, 146 | messageContainer: { 147 | flex: 1, 148 | flexDirection: 'row', 149 | alignItems: 'flex-start' 150 | }, 151 | initStyle: { 152 | borderRadius: 30, 153 | width: 60, 154 | height: 60 155 | }, 156 | }); 157 | -------------------------------------------------------------------------------- /app/screens/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Dimensions, ScrollView, StyleSheet, Text, View, } from 'react-native'; 3 | import Hyperlink from 'react-native-hyperlink' 4 | import { BorderText } from '../components/BorderText'; 5 | 6 | const { height } = Dimensions.get('window'); 7 | import * as labels from '../config/labels.json'; 8 | import { testProperties } from '../config/TestProperties'; 9 | 10 | export default class Home extends Component { 11 | static navigationOptions = { 12 | title: labels.stackNavigatorTitle.home, 13 | }; 14 | 15 | render() { 16 | return ( 17 | 21 | 22 | 25 | 26 | 27 | Hi there, welcome to my demo app. 28 | 29 | 30 | This app is build with React Native and will be used by myself for test (automation) 31 | purposes and contains 3 TABS:{'\n'} 32 | - This homepage{'\n'} 33 | - A Webview{'\n'} 34 | - A Chats 35 | 36 | WEBVIEW 37 | 38 | In the Webview you can enter an URL and load it in the Webview 39 | 40 | CHATS 41 | 42 | 43 | In the chats I created multiple API calls to retrieve JSON data from 44 | https://gist.github.com/wswebcreation.{'\n'} 45 | When you select a chat you will get a chatbox. Here you can add chats and you will get a 46 | response (some movie onliners) from my gist. 47 | 48 | CREDITS 49 | 50 | I'd like to thank https://medium.com/@yllongboy for his clear article about creating a 51 | "WhatsApp Layout through React Native".{'\n'} 52 | I'd also like to thank https://randomuser.me/ for generating random users with avatars. 53 | 54 | QUESTION? 55 | 56 | If you have questions feel free to ask them om my GitHub 57 | (https://github.com/wswebcreation) or through my site (http://www.wswebcreation.nl/) 58 | 59 | 60 | {'\n'} 61 | {'\n'} 62 | Grtz, 63 | {'\n'} 64 | {'\n'} 65 | Wim Selles{'\n'} 66 | wswebcreation 67 | 68 | 69 | 70 | ); 71 | } 72 | } 73 | 74 | const styles = StyleSheet.create({ 75 | container: { 76 | flex: 1, 77 | backgroundColor: '#FFF', 78 | paddingBottom: 40, 79 | paddingLeft: 15, 80 | paddingRight: 15, 81 | }, 82 | headerContainer: { 83 | alignItems: 'center', 84 | justifyContent: 'center', 85 | paddingBottom: 0.10 * height, 86 | paddingTop: 0.20 * height, 87 | }, 88 | header: { 89 | color: '#000', 90 | fontFamily: 'RobotoMono-Bold', 91 | fontSize: 20, 92 | }, 93 | headerMargin: { 94 | marginBottom: 10, 95 | marginTop: 15, 96 | }, 97 | defaultFont: { 98 | color: '#000', 99 | fontFamily: 'RobotoMono-Regular', 100 | fontSize: 16, 101 | padding: 5, 102 | }, 103 | linkStyle: { 104 | color: '#2980b9', 105 | } 106 | }); 107 | -------------------------------------------------------------------------------- /app/screens/Webview.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, View, WebView } from 'react-native'; 3 | import { BorderText } from '../components/BorderText'; 4 | import { CustomHeader } from '../components/CustomHeader'; 5 | import * as labels from '../config/labels.json'; 6 | import { testProperties } from '../config/TestProperties'; 7 | 8 | export default class WebViewScreen extends Component { 9 | static navigationOptions({ navigation }) { 10 | const headerLeft = ( 11 | navigation.goBack(null)} 13 | text={labels.stackNavigatorTitle.webview} 14 | /> 15 | ); 16 | return { headerLeft }; 17 | }; 18 | 19 | renderLoading() { 20 | return ( 21 | 25 | 28 | 29 | ) 30 | } 31 | 32 | render() { 33 | return ( 34 | 39 | ); 40 | } 41 | } 42 | 43 | const styles = StyleSheet.create({ 44 | loaderContainer: { 45 | backgroundColor: '#fff', 46 | flex: 1, 47 | flexDirection: 'column', 48 | justifyContent: 'center', 49 | alignItems: 'center', 50 | }, 51 | }); 52 | -------------------------------------------------------------------------------- /app/screens/WebviewSelection.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Keyboard, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native'; 3 | import { BorderText } from '../components/BorderText'; 4 | import { SCREEN_WIDTH } from '../config/Constants'; 5 | import * as labels from '../config/labels.json'; 6 | import { testProperties } from '../config/TestProperties'; 7 | 8 | export default class WebViewSelection extends Component { 9 | static navigationOptions = { 10 | title: labels.stackNavigatorTitle.webview, 11 | }; 12 | constructor(props) { 13 | super(props); 14 | this.state = {siteUrl: ''}; 15 | } 16 | 17 | addhttp = (url) => { 18 | return !/^(f|ht)tps?:\/\//i.test(url) ? 'http://' + url : url; 19 | }; 20 | 21 | onSubmitEditing = () => { 22 | const url = this.addhttp(this.state.siteUrl); 23 | const pattern = /^((http(s)?):\/\/www.)+[a-zA-Z0-9\-.]{2,}\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?$/i; 24 | if (pattern.test(url)) { 25 | this._textInput.clear(); 26 | this.setSiteUrl(''); 27 | Keyboard.dismiss(); 28 | return this.props.navigation.navigate('WebViewScreen', { url: url }); 29 | } else { 30 | alert(`${url} ${labels.webview.errorMessage}`); 31 | } 32 | }; 33 | 34 | setSiteUrl = (siteUrl) => this.setState({siteUrl}); 35 | 36 | render() { 37 | return ( 38 | 42 | this._textInput = component} 50 | {...testProperties(labels.webview.textAccessibilityLabel)} 51 | /> 52 | 57 | 60 | 61 | 62 | ); 63 | } 64 | } 65 | 66 | const styles = StyleSheet.create({ 67 | container: { 68 | flex: 1, 69 | backgroundColor: '#FFF', 70 | alignItems: 'center', 71 | justifyContent: 'center', 72 | flexDirection: 'row', 73 | }, 74 | textInput: { 75 | borderColor: 'gray', 76 | borderBottomWidth: 1, 77 | height: 40, 78 | textAlign: 'center', 79 | width: SCREEN_WIDTH * 0.75, 80 | }, 81 | button:{ 82 | marginLeft: SCREEN_WIDTH * 0.03, 83 | width: SCREEN_WIDTH * 0.15, 84 | }, 85 | }); 86 | -------------------------------------------------------------------------------- /assets/wswebcreation-android.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/assets/wswebcreation-android.gif -------------------------------------------------------------------------------- /assets/wswebcreation-animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/assets/wswebcreation-animation.gif -------------------------------------------------------------------------------- /assets/wswebcreation-deeplinking-chatbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/assets/wswebcreation-deeplinking-chatbox.gif -------------------------------------------------------------------------------- /assets/wswebcreation-deeplinking.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/assets/wswebcreation-deeplinking.gif -------------------------------------------------------------------------------- /assets/wswebcreation-disable-animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/assets/wswebcreation-disable-animation.gif -------------------------------------------------------------------------------- /assets/wswebcreation-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/assets/wswebcreation-icon.png -------------------------------------------------------------------------------- /assets/wswebcreation-ios.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/assets/wswebcreation-ios.gif -------------------------------------------------------------------------------- /assets/wswebcreation-storybook.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/assets/wswebcreation-storybook.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './App'; 3 | import { setupAutomation } from './app/config/TestProperties'; 4 | 5 | setupAutomation(); 6 | 7 | AppRegistry.registerComponent('wswebcreation', () => App); 8 | -------------------------------------------------------------------------------- /ios/wswebcreation-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | NSLocationWhenInUseUsageDescription 40 | 41 | NSAppTransportSecurity 42 | 43 | 44 | NSExceptionDomains 45 | 46 | localhost 47 | 48 | NSExceptionAllowsInsecureHTTPLoads 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ios/wswebcreation-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/wswebcreation.xcodeproj/xcshareddata/xcschemes/automation.build.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 38 | 44 | 45 | 46 | 52 | 58 | 59 | 60 | 61 | 62 | 68 | 69 | 71 | 77 | 78 | 79 | 80 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 103 | 105 | 111 | 112 | 113 | 114 | 115 | 116 | 122 | 124 | 130 | 131 | 132 | 133 | 135 | 136 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /ios/wswebcreation.xcodeproj/xcshareddata/xcschemes/wswebcreation-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /ios/wswebcreation.xcodeproj/xcshareddata/xcschemes/wswebcreation.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 38 | 44 | 45 | 46 | 52 | 58 | 59 | 60 | 61 | 62 | 68 | 69 | 71 | 77 | 78 | 79 | 80 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 103 | 105 | 111 | 112 | 113 | 114 | 115 | 116 | 122 | 124 | 130 | 131 | 132 | 133 | 135 | 136 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /ios/wswebcreation/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | @property (nonatomic, strong) UIWindow *window; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /ios/wswebcreation/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | #import 11 | #import 12 | #import 13 | 14 | @implementation AppDelegate 15 | 16 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url 17 | sourceApplication:(NSString *)sourceApplication annotation:(id)annotation 18 | { 19 | return [RCTLinkingManager application:application openURL:url 20 | sourceApplication:sourceApplication annotation:annotation]; 21 | } 22 | 23 | // Only if your app is using [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html). 24 | - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity 25 | restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler 26 | { 27 | return [RCTLinkingManager application:application 28 | continueUserActivity:userActivity 29 | restorationHandler:restorationHandler]; 30 | } 31 | 32 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 33 | { 34 | NSURL *jsCodeLocation; 35 | 36 | #ifdef DEBUG 37 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 38 | #else 39 | jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 40 | #endif 41 | 42 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 43 | moduleName:@"wswebcreation" 44 | initialProperties:nil 45 | launchOptions:launchOptions]; 46 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 47 | 48 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 49 | UIViewController *rootViewController = [UIViewController new]; 50 | rootViewController.view = rootView; 51 | self.window.rootViewController = rootViewController; 52 | [self.window makeKeyAndVisible]; 53 | return YES; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /ios/wswebcreation/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "icon-20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "icon-20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "icon-29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "icon-29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "icon-29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "icon-40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "icon-40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "57x57", 47 | "idiom" : "iphone", 48 | "filename" : "icon-57@1x.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "57x57", 53 | "idiom" : "iphone", 54 | "filename" : "icon-57@2x.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "60x60", 59 | "idiom" : "iphone", 60 | "filename" : "icon-60@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "60x60", 65 | "idiom" : "iphone", 66 | "filename" : "icon-60@3x.png", 67 | "scale" : "3x" 68 | }, 69 | { 70 | "size" : "20x20", 71 | "idiom" : "ipad", 72 | "filename" : "icon-20@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "20x20", 77 | "idiom" : "ipad", 78 | "filename" : "icon-20@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "idiom" : "ipad", 84 | "filename" : "icon-29@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "29x29", 89 | "idiom" : "ipad", 90 | "filename" : "icon-29@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "40x40", 95 | "idiom" : "ipad", 96 | "filename" : "icon-40@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "40x40", 101 | "idiom" : "ipad", 102 | "filename" : "icon-40@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "50x50", 107 | "idiom" : "ipad", 108 | "filename" : "icon-50@1x.png", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "50x50", 113 | "idiom" : "ipad", 114 | "filename" : "icon-50@2x.png", 115 | "scale" : "2x" 116 | }, 117 | { 118 | "size" : "72x72", 119 | "idiom" : "ipad", 120 | "filename" : "icon-72@1x.png", 121 | "scale" : "1x" 122 | }, 123 | { 124 | "size" : "72x72", 125 | "idiom" : "ipad", 126 | "filename" : "icon-72@2x.png", 127 | "scale" : "2x" 128 | }, 129 | { 130 | "size" : "76x76", 131 | "idiom" : "ipad", 132 | "filename" : "icon-76@1x.png", 133 | "scale" : "1x" 134 | }, 135 | { 136 | "size" : "76x76", 137 | "idiom" : "ipad", 138 | "filename" : "icon-76@2x.png", 139 | "scale" : "2x" 140 | }, 141 | { 142 | "size" : "83.5x83.5", 143 | "idiom" : "ipad", 144 | "filename" : "icon-83.5@2x.png", 145 | "scale" : "2x" 146 | }, 147 | { 148 | "size" : "1024x1024", 149 | "idiom" : "ios-marketing", 150 | "filename" : "icon-1024@1x.png", 151 | "scale" : "1x" 152 | } 153 | ], 154 | "info" : { 155 | "version" : 1, 156 | "author" : "xcode" 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-1024@1x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-20@1x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-29@1x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-40@1x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-50@1x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-50@2x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-57@1x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-57@2x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-72@1x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-72@2x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-76@1x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "8.0", 8 | "subtype" : "736h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "extent" : "full-screen", 13 | "idiom" : "iphone", 14 | "subtype" : "667h", 15 | "filename" : "Default-750@2x~iphone6-portrait_750x1334.png", 16 | "minimum-system-version" : "8.0", 17 | "orientation" : "portrait", 18 | "scale" : "2x" 19 | }, 20 | { 21 | "orientation" : "portrait", 22 | "idiom" : "ipad", 23 | "filename" : "Default-Portrait~ipad_768x1024.png", 24 | "extent" : "full-screen", 25 | "minimum-system-version" : "7.0", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "orientation" : "landscape", 30 | "idiom" : "ipad", 31 | "filename" : "Default-Landscape~ipad_1024x768.png", 32 | "extent" : "full-screen", 33 | "minimum-system-version" : "7.0", 34 | "scale" : "1x" 35 | }, 36 | { 37 | "orientation" : "landscape", 38 | "idiom" : "ipad", 39 | "filename" : "Default-Landscape@2x~ipad_2048x1536.png", 40 | "extent" : "full-screen", 41 | "minimum-system-version" : "7.0", 42 | "scale" : "2x" 43 | }, 44 | { 45 | "orientation" : "portrait", 46 | "idiom" : "iphone", 47 | "filename" : "Default~iphone.png", 48 | "extent" : "full-screen", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "orientation" : "portrait", 53 | "idiom" : "iphone", 54 | "filename" : "Default@2x~iphone_640x960.png", 55 | "extent" : "full-screen", 56 | "scale" : "2x" 57 | }, 58 | { 59 | "orientation" : "portrait", 60 | "idiom" : "iphone", 61 | "filename" : "Default-568h@2x~iphone_640x1136.png", 62 | "extent" : "full-screen", 63 | "subtype" : "retina4", 64 | "scale" : "2x" 65 | }, 66 | { 67 | "orientation" : "portrait", 68 | "idiom" : "ipad", 69 | "filename" : "Default.png", 70 | "extent" : "to-status-bar", 71 | "scale" : "1x" 72 | }, 73 | { 74 | "orientation" : "landscape", 75 | "idiom" : "ipad", 76 | "filename" : "Default-Landscape~ipad_1024x748.png", 77 | "extent" : "to-status-bar", 78 | "scale" : "1x" 79 | }, 80 | { 81 | "orientation" : "portrait", 82 | "idiom" : "ipad", 83 | "filename" : "Default-Portrait@2x~ipad_1536x2008.png", 84 | "extent" : "to-status-bar", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "orientation" : "portrait", 89 | "idiom" : "ipad", 90 | "filename" : "Default-Portrait@2x~ipad_1536x2048.png", 91 | "extent" : "full-screen", 92 | "scale" : "2x" 93 | }, 94 | { 95 | "orientation" : "landscape", 96 | "idiom" : "ipad", 97 | "filename" : "Default-Landscape@2x~ipad_2048x1496.png", 98 | "extent" : "to-status-bar", 99 | "scale" : "2x" 100 | }, 101 | { 102 | "orientation" : "portrait", 103 | "idiom" : "iphone", 104 | "minimum-system-version" : "7.0", 105 | "scale" : "2x" 106 | }, 107 | { 108 | "orientation" : "portrait", 109 | "idiom" : "iphone", 110 | "minimum-system-version" : "7.0", 111 | "subtype" : "retina4", 112 | "scale" : "2x" 113 | }, 114 | { 115 | "orientation" : "portrait", 116 | "idiom" : "ipad", 117 | "filename" : "Default~ipad.png", 118 | "extent" : "to-status-bar", 119 | "unassigned" : true, 120 | "scale" : "2x" 121 | }, 122 | { 123 | "filename" : "Default-750@2x~iphone6-landscape_1334x750.png", 124 | "unassigned" : true 125 | }, 126 | { 127 | "filename" : "Default-1242@3x~iphone6s-landscape_2208x1242.png", 128 | "unassigned" : true 129 | }, 130 | { 131 | "filename" : "Default-1242@3x~iphone6s-portrait_1242x2208.png", 132 | "unassigned" : true 133 | } 134 | ], 135 | "info" : { 136 | "version" : 1, 137 | "author" : "xcode" 138 | } 139 | } -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-1242@3x~iphone6s-landscape_2208x1242.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-1242@3x~iphone6s-landscape_2208x1242.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-1242@3x~iphone6s-portrait_1242x2208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-1242@3x~iphone6s-portrait_1242x2208.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-568h@2x~iphone_640x1136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-568h@2x~iphone_640x1136.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-750@2x~iphone6-landscape_1334x750.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-750@2x~iphone6-landscape_1334x750.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-750@2x~iphone6-portrait_750x1334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-750@2x~iphone6-portrait_750x1334.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Landscape@2x~ipad_2048x1496.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Landscape@2x~ipad_2048x1496.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Landscape@2x~ipad_2048x1536.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Landscape@2x~ipad_2048x1536.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Landscape~ipad_1024x748.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Landscape~ipad_1024x748.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Landscape~ipad_1024x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Landscape~ipad_1024x768.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Portrait@2x~ipad_1536x2008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Portrait@2x~ipad_1536x2008.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Portrait@2x~ipad_1536x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Portrait@2x~ipad_1536x2048.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Portrait~ipad_768x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default-Portrait~ipad_768x1024.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default@2x~iphone_640x960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default@2x~iphone_640x960.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default~ipad.png -------------------------------------------------------------------------------- /ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wswebcreation/wswebcreation-react-native-app/f2b7d916deeafebf4d7e3156f706ffc98a082798/ios/wswebcreation/Images.xcassets/LaunchImage.launchimage/Default~iphone.png -------------------------------------------------------------------------------- /ios/wswebcreation/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleURLTypes 6 | 7 | 8 | CFBundleURLSchemes 9 | 10 | wswebcreationapp 11 | 12 | 13 | 14 | CFBundleDevelopmentRegion 15 | en 16 | CFBundleDisplayName 17 | wswebcreation 18 | CFBundleExecutable 19 | $(EXECUTABLE_NAME) 20 | CFBundleIdentifier 21 | $(PRODUCT_BUNDLE_IDENTIFIER) 22 | CFBundleInfoDictionaryVersion 23 | 6.0 24 | CFBundleName 25 | $(PRODUCT_NAME) 26 | CFBundlePackageType 27 | APPL 28 | CFBundleShortVersionString 29 | 1.0 30 | CFBundleSignature 31 | ???? 32 | CFBundleVersion 33 | 1 34 | LSRequiresIPhoneOS 35 | 36 | NSAppTransportSecurity 37 | 38 | NSAllowsArbitraryLoads 39 | 40 | 41 | NSLocationWhenInUseUsageDescription 42 | 43 | UIAppFonts 44 | 45 | Entypo.ttf 46 | EvilIcons.ttf 47 | Feather.ttf 48 | FontAwesome.ttf 49 | Foundation.ttf 50 | Ionicons.ttf 51 | MaterialCommunityIcons.ttf 52 | MaterialIcons.ttf 53 | Octicons.ttf 54 | SimpleLineIcons.ttf 55 | Zocial.ttf 56 | RobotoMono-Bold.ttf 57 | RobotoMono-BoldItalic.ttf 58 | RobotoMono-Italic.ttf 59 | RobotoMono-Light.ttf 60 | RobotoMono-LightItalic.ttf 61 | RobotoMono-Medium.ttf 62 | RobotoMono-MediumItalic.ttf 63 | RobotoMono-Regular.ttf 64 | RobotoMono-Thin.ttf 65 | RobotoMono-ThinItalic.ttf 66 | 67 | UIRequiredDeviceCapabilities 68 | 69 | armv7 70 | 71 | UISupportedInterfaceOrientations 72 | 73 | UIInterfaceOrientationPortrait 74 | UIInterfaceOrientationLandscapeLeft 75 | UIInterfaceOrientationLandscapeRight 76 | 77 | UIViewControllerBasedStatusBarAppearance 78 | 79 | NSLocationWhenInUseUsageDescription 80 | 81 | NSAppTransportSecurity 82 | 83 | 84 | NSAllowsArbitraryLoads 85 | 86 | NSExceptionDomains 87 | 88 | localhost 89 | 90 | NSExceptionAllowsInsecureHTTPLoads 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /ios/wswebcreation/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/wswebcreationTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/wswebcreationTests/wswebcreationTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | #import 12 | #import 13 | 14 | #define TIMEOUT_SECONDS 600 15 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 16 | 17 | @interface wswebcreationTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation wswebcreationTests 22 | 23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 24 | { 25 | if (test(view)) { 26 | return YES; 27 | } 28 | for (UIView *subview in [view subviews]) { 29 | if ([self findSubviewInView:subview matching:test]) { 30 | return YES; 31 | } 32 | } 33 | return NO; 34 | } 35 | 36 | - (void)testRendersWelcomeScreen 37 | { 38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 40 | BOOL foundElement = NO; 41 | 42 | __block NSString *redboxError = nil; 43 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 44 | if (level >= RCTLogLevelError) { 45 | redboxError = message; 46 | } 47 | }); 48 | 49 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 50 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 51 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 52 | 53 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 54 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 55 | return YES; 56 | } 57 | return NO; 58 | }]; 59 | } 60 | 61 | RCTSetLogFunction(RCTDefaultLogFunction); 62 | 63 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 64 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 65 | } 66 | 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wswebcreation", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "android.automation": "ENVFILE=.env.automation react-native run-android", 8 | "appium.android": "node_modules/.bin/wdio __tests__/appium/config/wdio.android.conf.js", 9 | "appium.ios": "node_modules/.bin/wdio __tests__/appium/config/wdio.ios.conf.js", 10 | "ios": "react-native run-ios", 11 | "ios.automation": "ENVFILE=.env.automation react-native run-ios", 12 | "start": "node node_modules/react-native/local-cli/cli.js start", 13 | "start.ios.simulator": "node ./start.ios.simulator.js", 14 | "storybook": "storybook start -p 7007" 15 | }, 16 | "rnpm": { 17 | "assets": [ 18 | "./app/assets/fonts/Roboto_Mono/" 19 | ] 20 | }, 21 | "dependencies": { 22 | "fetch": "^1.1.0", 23 | "prop-types": "^15.6.0", 24 | "react": "^16.6.3", 25 | "react-native": "^0.57.8", 26 | "react-native-config": "^0.11.5", 27 | "react-native-elements": "^0.19.0", 28 | "react-native-hyperlink": "0.0.12", 29 | "react-native-invertible-scroll-view": "^1.1.1", 30 | "react-native-keyboard-aware-scroll-view": "^0.4.3", 31 | "react-native-keyboard-spacer": "^0.4.1", 32 | "react-native-restart": "0.0.7", 33 | "react-native-vector-icons": "^4.5.0", 34 | "react-navigation": "^1.2.1" 35 | }, 36 | "devDependencies": { 37 | "@storybook/addon-actions": "^3.3.15", 38 | "@storybook/addon-links": "^3.3.15", 39 | "@storybook/addons": "^3.3.15", 40 | "@storybook/react-native": "^3.3.15", 41 | "babel-core": "^6.26.0", 42 | "babel-eslint": "^8.0.1", 43 | "babel-jest": "21.2.0", 44 | "babel-preset-react-native": "4.0.0", 45 | "babel-register": "^6.26.0", 46 | "chai": "^4.1.2", 47 | "cucumber": "^2.3.1", 48 | "eslint": "^4.3.0", 49 | "eslint-config-airbnb": "^15.1.0", 50 | "eslint-import-resolver-react-native": "^0.1.0", 51 | "eslint-plugin-import": "^2.7.0", 52 | "eslint-plugin-jsx-a11y": "^5.1.1", 53 | "eslint-plugin-react": "^7.1.0", 54 | "eslint-plugin-react-native": "^3.1.0", 55 | "fs-extra": "^5.0.0", 56 | "jest": "^22.4.2", 57 | "multiple-cucumber-html-reporter": "^1.11.2", 58 | "react-dom": "^16.2.0", 59 | "react-test-renderer": "16.0.0", 60 | "stubs": "^3.0.0", 61 | "wdio-appium-service": "^0.2.3", 62 | "wdio-cucumber-framework": "^1.0.3", 63 | "wdio-spec-reporter": "^0.1.3", 64 | "webdriverio": "^4.10.1" 65 | }, 66 | "repository": { 67 | "type": "git", 68 | "url": "https://github.com/wswebcreation/wswebcreation-react-native-app.git" 69 | }, 70 | "keywords": [ 71 | "testautomation", 72 | "react-native", 73 | "appium", 74 | "webdriver.io", 75 | "cucumberjs" 76 | ], 77 | "author": "Wim Selles - wswebcreation", 78 | "license": "MIT", 79 | "detox": { 80 | "test-runner": "jest", 81 | "runner-config": "./__tests__/detox/config/detox.json", 82 | "specs": ".", 83 | "configurations": { 84 | "ios.sim.debug": { 85 | "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/wswebcreation.app", 86 | "build": "xcodebuild -project ios/wswebcreation.xcodeproj -scheme wswebcreation -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", 87 | "type": "ios.simulator", 88 | "name": "iPhone 8" 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-links/register'; 3 | -------------------------------------------------------------------------------- /storybook/decorators/centerDecorator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View } from 'react-native'; 3 | 4 | export default storyFn => ( 5 | 13 | {storyFn()} 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /storybook/decorators/defaultDecorator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View } from 'react-native'; 3 | 4 | export default storyFn => ( 5 | 11 | {storyFn()} 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /storybook/index.android.js: -------------------------------------------------------------------------------- 1 | import StorybookUI from './storybook'; 2 | 3 | export default StorybookUI; 4 | -------------------------------------------------------------------------------- /storybook/index.ios.js: -------------------------------------------------------------------------------- 1 | import StorybookUI from './storybook'; 2 | 3 | export default StorybookUI; 4 | -------------------------------------------------------------------------------- /storybook/index.js: -------------------------------------------------------------------------------- 1 | import StorybookUI from './storybook'; 2 | 3 | export default StorybookUI; 4 | -------------------------------------------------------------------------------- /storybook/stories/Components/AnimationStory.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Animated, 4 | Easing, 5 | Platform, 6 | StyleSheet, 7 | Text, 8 | TouchableOpacity, 9 | View, 10 | } from 'react-native'; 11 | 12 | export default class AnimationStory extends Component { 13 | state = { 14 | animatedValue: new Animated.Value(0), 15 | }; 16 | 17 | animate = () => { 18 | this.state.animatedValue.setValue(0); 19 | Animated.timing( 20 | this.state.animatedValue, 21 | { 22 | toValue: 1, 23 | duration: 1500, 24 | easing: Easing.elastic(1) 25 | } 26 | ).start(); 27 | }; 28 | 29 | renderButton() { 30 | return ( 31 | 36 | CLICK ME 37 | 38 | ) 39 | } 40 | 41 | renderMessage() { 42 | const marginLeft = this.state.animatedValue.interpolate({ 43 | inputRange: [0, 1], 44 | outputRange: [-120, 0], 45 | }); 46 | return ( 47 | 55 | I'M AN ANIMATED BOX 56 | 57 | ); 58 | } 59 | 60 | render() { 61 | return ( 62 | 67 | {this.renderButton()} 68 | {this.renderMessage()} 69 | 70 | ); 71 | } 72 | } 73 | 74 | const styles = StyleSheet.create({ 75 | button: { 76 | backgroundColor: '#fff', 77 | borderColor: '#000', 78 | borderWidth: 3, 79 | ...Platform.select({ 80 | ios: { 81 | paddingBottom: 9, 82 | }, 83 | android: { 84 | paddingBottom: 4, 85 | }, 86 | }), 87 | marginBottom: 15, 88 | paddingLeft: 13, 89 | paddingRight: 13, 90 | paddingTop: 9, 91 | width: 150, 92 | }, 93 | text: { 94 | color: '#000', 95 | fontFamily: 'RobotoMono-Bold', 96 | fontSize: 20, 97 | textAlign: 'center', 98 | }, 99 | messageBox: { 100 | marginTop: 50, 101 | backgroundColor: '#fff', 102 | borderColor: '#000', 103 | borderWidth: 3, 104 | ...Platform.select({ 105 | ios: { 106 | paddingBottom: 9, 107 | }, 108 | android: { 109 | paddingBottom: 4, 110 | }, 111 | }), 112 | padding: 10, 113 | shadowColor: '#000', 114 | width: 275, 115 | shadowOpacity: 0.5, 116 | }, 117 | }); 118 | -------------------------------------------------------------------------------- /storybook/stories/Components/BorderTextStory.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View } from 'react-native'; 3 | import { BorderText } from '../../../app/components/BorderText'; 4 | 5 | export default class BorderTextStory extends Component { 6 | 7 | render() { 8 | return ( 9 | 14 | 17 | 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /storybook/stories/Components/ChatInputStory.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Alert, View } from 'react-native'; 3 | import { ChatInput } from '../../../app/components/ChatInput'; 4 | 5 | export default class BorderTextStory extends Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | message: 'I\'m already filled\nEmpty me or start typing.', 11 | }; 12 | } 13 | 14 | handleOnPress = () => { 15 | Alert.alert( 16 | 'Chat input alert', 17 | this.state.message, 18 | [ 19 | { text: 'OK', onPress: () => console.log('OK Pressed') }, 20 | ], 21 | ) 22 | }; 23 | 24 | render() { 25 | return ( 26 | 32 | 37 | this.setState({ message })} 39 | onPress={() => this.handleOnPress()} 40 | value={this.state.message} 41 | /> 42 | 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /storybook/stories/Components/CustomHeaderStory.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Alert } from 'react-native'; 3 | import { CustomHeader } from '../../../app/components/CustomHeader'; 4 | 5 | export default class CustomHeaderStory extends Component { 6 | handleOnPress = () => { 7 | Alert.alert( 8 | 'Custom header alert', 9 | 'You pressed the custom header', 10 | [ 11 | { text: 'OK', onPress: () => console.log('OK Pressed') }, 12 | ], 13 | ) 14 | }; 15 | 16 | render() { 17 | return ( 18 | this.handleOnPress()} 20 | text="This is a customer header" 21 | /> 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /storybook/stories/Components/MessageBubbleStory.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View } from 'react-native'; 3 | import { MessageBubble } from '../../../app/components/MessageBubble'; 4 | 5 | export default class MessageBubbleStory extends Component { 6 | render() { 7 | return ( 8 | 9 | 13 | 17 | 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /storybook/stories/Components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react-native'; 3 | import defaultDecorator from '../../decorators/defaultDecorator'; 4 | import AnimationStory from './AnimationStory'; 5 | import BorderTextStory from './BorderTextStory'; 6 | import ChatInputStory from './ChatInputStory'; 7 | import CustomHeaderStory from './CustomHeaderStory'; 8 | import MessageBubbleStory from './MessageBubbleStory'; 9 | 10 | storiesOf('Components', module) 11 | .addDecorator(defaultDecorator) 12 | .add('Animation example', () => ) 13 | .add('Border Text', () => ) 14 | .add('Chat Input', () => ) 15 | .add('Custom Header', () => ) 16 | .add('Message Bubbles', () => ); 17 | -------------------------------------------------------------------------------- /storybook/stories/Welcome/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | import { storiesOf } from '@storybook/react-native'; 4 | import centerDecorator from '../../decorators/centerDecorator'; 5 | 6 | const WelcomeScreen = () => { 7 | return ( 8 | 9 | Welcome to React Native Storybook 10 | 11 | This is a UI Component development environment for your React Native app. Here you can 12 | display and interact with your UI components as stories. A story is a single state of one 13 | or more UI components. You can have as many stories as you want. In other words a story is 14 | like a visual test case. 15 | 16 | 17 | ); 18 | }; 19 | 20 | const styles = StyleSheet.create({ 21 | wrapper: { 22 | padding: 15, 23 | }, 24 | header: { 25 | fontSize: 18, 26 | marginBottom: 18, 27 | }, 28 | content: { 29 | fontSize: 12, 30 | marginBottom: 10, 31 | lineHeight: 18, 32 | }, 33 | }); 34 | 35 | storiesOf('Welcome', module) 36 | .addDecorator(centerDecorator) 37 | .add('to Storybook', () => ); 38 | -------------------------------------------------------------------------------- /storybook/stories/index.js: -------------------------------------------------------------------------------- 1 | import './Welcome/'; 2 | import './Components/'; 3 | -------------------------------------------------------------------------------- /storybook/storybook.app.js: -------------------------------------------------------------------------------- 1 | import { getStorybookUI, configure } from '@storybook/react-native'; 2 | 3 | // import stories 4 | configure(() => { 5 | require('./stories'); 6 | }, module); 7 | 8 | // to set manually use, e.g. host: 'localhost' option 9 | const StorybookInApp = getStorybookUI({ port: 8081, onDeviceUI: true }); 10 | 11 | export default StorybookInApp; 12 | -------------------------------------------------------------------------------- /storybook/storybook.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | import React, { Component } from 'react'; 3 | import { AppRegistry } from 'react-native'; 4 | import { getStorybookUI, configure } from '@storybook/react-native'; 5 | 6 | // import stories 7 | configure(() => { 8 | require('./stories'); 9 | }, module); 10 | 11 | // This assumes that storybook is running on the same host as your RN packager, 12 | // to set manually use, e.g. host: 'localhost' option 13 | const StorybookUIRoot = getStorybookUI({ port: 7007, onDeviceUI: true }); 14 | 15 | // react-native hot module loader must take in a Class - https://github.com/facebook/react-native/issues/10991 16 | // https://github.com/storybooks/storybook/issues/2081 17 | // eslint-disable-next-line react/prefer-stateless-function 18 | class StorybookUIHMRRoot extends Component { 19 | render() { 20 | return ; 21 | } 22 | } 23 | 24 | AppRegistry.registerComponent('wswebcreation', () => StorybookUIHMRRoot); 25 | export default StorybookUIHMRRoot; 26 | --------------------------------------------------------------------------------