diff --git a/.gitignore b/.gitignore index c96a83f..ca6ae36 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,7 @@ demo-angular/report/stats.json /src/platforms/android/nativescript_imagepicker.aar /src/*.tgz !demo-vue/app/app.js + +// Tests +**/mochawesome-report/ +test-results.xml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 53cd73a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,123 +0,0 @@ -env: - global: - - ANDROID_PACKAGE='app-debug.apk' - - ANDROID_PACKAGE_FOLDER=$TRAVIS_BUILD_DIR/demo/platforms/android/app/build/outputs/apk/debug - - ANDROID_SAUCE_STORAGE="https://saucelabs.com/rest/v1/storage/$SAUCE_USER/$ANDROID_PACKAGE?overwrite=true" - - IOS_PACKAGE='imagepickerdemo.zip' - - IOS_PACKAGE_FOLDER=$TRAVIS_BUILD_DIR/demo/platforms/ios/build/emulator - - IOS_SAUCE_STORAGE="https://saucelabs.com/rest/v1/storage/$SAUCE_USER/$IOS_PACKAGE?overwrite=true" - -git: - depth: 1 - -branches: - only: - - master -matrix: - include: - - stage: "Lint" - language: node_js - os: linux - node_js: "8" - script: cd src && npm run ci.tslint && cd ../demo && npm run ci.tslint && cd ../demo-angular && npm run ci.tslint - - stage: "WebPack and Build" - os: osx - env: - - WebPackiOS="12.0" - osx_image: xcode10.0 - language: node_js - node_js: "8" - jdk: oraclejdk8 - script: - - cd demo && npm run build.plugin && npm i && tns build ios --bundle --env.uglify - - cd ../publish - - sh pack.sh - - cp package/*.tgz ../demo-angular/package.tgz - - cd ../demo-angular - - sed -i -e 's/\"..\/src\"/"package.tgz"/g' package.json - - npm i - - tns build ios --bundle --env.uglify --env.aot - - language: android - os: linux - env: - - WebPackAndroid="28" - jdk: oraclejdk8 - before_install: nvm install 8.11.4 - script: - - cd demo && npm run build.plugin && npm i && tns build android --bundle --env.uglify --env.snapshot - - cd ../publish - - sh pack.sh - - cp package/*.tgz ../demo-angular/package.tgz - - cd ../demo-angular - - sed -i -e 's/\"..\/src\"/"package.tgz"/g' package.json - - npm i - - tns build android --bundle --env.uglify --env.aot - - language: android - env: - - BuildAndroid="28" - os: linux - jdk: oraclejdk8 - before_install: nvm install 8.11.4 - script: - - cd demo && npm run ci.android.build && cd ../demo-angular && npm run ci.android.build - - "curl -u $SAUCE_USER:$SAUCE_KEY -X POST -H 'Content-Type: application/octet-stream' $ANDROID_SAUCE_STORAGE --data-binary @$ANDROID_PACKAGE_FOLDER/$ANDROID_PACKAGE" - - os: osx - env: - - BuildiOS="12.0" - - Xcode="10.0" - osx_image: xcode10.0 - language: node_js - node_js: "8" - jdk: oraclejdk8 - script: - - cd demo && npm run ci.ios.build && cd ../demo-angular && npm run ci.ios.build - - cd $IOS_PACKAGE_FOLDER && zip -r $IOS_PACKAGE demo.app - - "curl -u $SAUCE_USER:$SAUCE_KEY -X POST -H 'Content-Type: application/octet-stream' $IOS_SAUCE_STORAGE --data-binary @$IOS_PACKAGE_FOLDER/$IOS_PACKAGE" - - stage: "UI Tests" - env: - - AndroidEmulator="23" - language: node_js - os: linux - node_js: "8" - script: - - npm i -g appium - - cd demo && npm i - - travis_retry npm run e2e -- --runType android23 --sauceLab --appPath $ANDROID_PACKAGE - - os: linux - env: - - iOS="12" - language: node_js - node_js: "8" - script: - - npm i -g appium - - cd demo && npm i - - travis_wait travis_retry npm run e2e -- --runType sim11iPhone6 --sauceLab --appPath $IOS_PACKAGE - -android: - components: - - tools - - platform-tools - - build-tools-28.0.3 - - android-28 - - android-23 - - extra-android-m2repository - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - -cache: - directories: - - .nvm - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - -before_install: - - sudo pip install --upgrade pip - - sudo pip install six - -install: - - echo no | npm install -g nativescript - - tns usage-reporting disable - - tns error-reporting disable - - diff --git a/LICENSE b/LICENSE index 9d63a0a..061c440 100755 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (c) 2015-2018 Progress Software Corporation + Copyright (c) 2015-2019 Progress Software Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index e69fb4a..e1a4292 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +## NativeScript 7 or higher + +* Use `@nativescript/imagepicker`: `~1.0.0` +* [Source managed here](https://github.com/NativeScript/plugins) + +## If using 6 and below, see the following: + # NativeScript Image Picker ![apple](https://cdn3.iconfinder.com/data/icons/picons-social/57/16-apple-32.png) ![android](https://cdn4.iconfinder.com/data/icons/logos-3/228/android-32.png) @@ -22,8 +29,6 @@ Imagepicker plugin supporting both single and multiple selection. - [Request permissions, show the images list and process the selection](#request-permissions-show-the-images-list-and-process-the-selection) - [API](#api) - [Methods](#methods) - - [Properties](#properties) - - [Image properties](#image-properties) - [Contribute](#contribute) - [Get Help](#get-help) @@ -111,6 +116,18 @@ context > **NOTE**: To request permissions for Android 6+ (API 23+) we use [nativescript-permissions](https://www.npmjs.com/package/nativescript-permissions). +> **NOTE**: To be sure to have permissions add the following lines in AndroidManifest.xml +``` + + + + + ... + + +``` + + > **NOTE**: Using the plugin on iOS requres photo library permission. Your app might be rejected from the Apple App Store if you do not provide a description about why you need this permission. The default message "Requires access to photo library." might not be enough for the App Store reviewers. You can customize it by editing the `app/App_Resources/iOS/Info.plist` file in your app and adding the following key: ```xml @@ -133,7 +150,8 @@ context | prompt | iOS | undefined | Display prompt text when selecting assets. | | numberOfColumnsInPortrait | iOS | 4 | Set the number of columns in Portrait orientation. | | numberOfColumnsInLandscape | iOS | 7 | Set the number of columns in Landscape orientation. | -| mediaType | iOS | Any | Choose whether to pick Image/Video/Any type of assets. | +| mediaType | both | Any | Choose whether to pick Image/Video/Any type of assets. | +| showAdvanced | Android | false | Show internal and removable storage options on Android (**WARNING**: [not supported officially](https://issuetracker.google.com/issues/72053350)). | The **hostView** parameter can be set to the view that hosts the image picker. Applicable in iOS only, intended to be used when open picker from a modal page. diff --git a/demo/app/App_Resources/Android/AndroidManifest.xml b/demo-angular/app/App_Resources/Android/src/main/AndroidManifest.xml similarity index 97% rename from demo/app/App_Resources/Android/AndroidManifest.xml rename to demo-angular/app/App_Resources/Android/src/main/AndroidManifest.xml index 9db8321..976f83f 100644 --- a/demo/app/App_Resources/Android/AndroidManifest.xml +++ b/demo-angular/app/App_Resources/Android/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ { + if (!options.preprocessors[file]) { + options.preprocessors[file] = []; + } + options.preprocessors[file].push('webpack'); + }); + } +} + +function setWebpack(config, options) { + if (config && config.bundle) { + const env = {}; + env[config.platform] = true; + env.sourceMap = config.debugBrk; + options.webpack = require('./webpack.config')(env); + delete options.webpack.entry; + delete options.webpack.output.libraryTarget; + const invalidPluginsForUnitTesting = ["GenerateBundleStarterPlugin", "GenerateNativeScriptEntryPointsPlugin"]; + options.webpack.plugins = options.webpack.plugins.filter(p => !invalidPluginsForUnitTesting.includes(p.constructor.name)); + } +} diff --git a/demo-angular/package.json b/demo-angular/package.json index 243be7a..297475e 100644 --- a/demo-angular/package.json +++ b/demo-angular/package.json @@ -2,40 +2,40 @@ "nativescript": { "id": "org.nativescript.imagepickerdemoangular", "tns-ios": { - "version": "5.1.0" + "version": "6.2.0" }, "tns-android": { - "version": "5.1.0" + "version": "6.2.0" } }, "dependencies": { - "@angular/common": "~7.1.0", - "@angular/compiler": "~7.1.0", - "@angular/core": "~7.1.0", - "@angular/forms": "~7.1.0", - "@angular/http": "~7.1.0", - "@angular/platform-browser": "~7.1.0", - "@angular/platform-browser-dynamic": "~7.1.0", - "@angular/router": "~7.1.0", - "nativescript-angular": "~7.1.0", + "@angular/common": "~8.2.0", + "@angular/compiler": "~8.2.0", + "@angular/core": "~8.2.0", + "@angular/forms": "~8.2.0", + "@angular/platform-browser": "~8.2.0", + "@angular/platform-browser-dynamic": "~8.2.0", + "@angular/router": "~8.2.0", + "nativescript-angular": "~8.20.0", "nativescript-imagepicker": "../src", "nativescript-theme-core": "^1.0.4", - "nativescript-unit-test-runner": "^0.3.4", + "nativescript-unit-test-runner": "0.7.0", "reflect-metadata": "~0.1.8", "rxjs": "^6.3.3", - "tns-core-modules": "^5.1.0" + "tns-core-modules": "^6.0.0" }, "devDependencies": { "jasmine-core": "^2.5.2", - "karma": "^1.3.0", - "karma-jasmine": "^1.0.2", + "karma": "4.1.0", + "karma-jasmine": "2.0.1", "karma-nativescript-launcher": "^0.4.0", "nativescript-css-loader": "~0.26.0", - "nativescript-dev-typescript": "^0.7.0", - "nativescript-dev-webpack": "~0.18.0", - "tns-platform-declarations": "^5.1.0", + "nativescript-dev-webpack": "^1.3.0", + "tns-platform-declarations": "^6.0.0", "tslint": "~5.11.0", - "zone.js": "^0.8.4" + "zone.js": "^0.8.4", + "typescript": "~3.5.3", + "karma-webpack": "3.0.5" }, "scripts": { "build.plugin": "cd ../src && npm run build", @@ -50,4 +50,4 @@ "build-android-bundle": "npm run ns-bundle --android --build-app", "build-ios-bundle": "npm run ns-bundle --ios --build-app" } -} \ No newline at end of file +} diff --git a/demo-vue/.gitignore b/demo-vue/.gitignore index 0cdaa5c..a9e686d 100644 --- a/demo-vue/.gitignore +++ b/demo-vue/.gitignore @@ -6,7 +6,6 @@ platforms/ # NativeScript Template *.js.map -!webpack.config.js *.css # Logs diff --git a/demo-vue/app/App_Resources/Android/AndroidManifest.xml b/demo-vue/app/App_Resources/Android/src/main/AndroidManifest.xml similarity index 97% rename from demo-vue/app/App_Resources/Android/AndroidManifest.xml rename to demo-vue/app/App_Resources/Android/src/main/AndroidManifest.xml index 9961000..fefdc0c 100644 --- a/demo-vue/app/App_Resources/Android/AndroidManifest.xml +++ b/demo-vue/app/App_Resources/Android/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ { - // Add your custom Activities, Services and other android app components here. - const appComponents = [ - "tns-core-modules/ui/frame", - "tns-core-modules/ui/frame/activity", - ]; - - const platform = env && (env.android && "android" || env.ios && "ios"); - if (!platform) { - throw new Error("You need to provide a target platform!"); - } - - const platforms = ["ios", "android"]; - const projectRoot = __dirname; - - // Default destination inside platforms//... - const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot)); - const appResourcesPlatformDir = platform === "android" ? "Android" : "iOS"; - - const { - // The 'appPath' and 'appResourcesPath' values are fetched from - // the nsconfig.json configuration file - // when bundling with `tns run android|ios --bundle`. - appPath = "app", - appResourcesPath = "app/App_Resources", - - // You can provide the following flags when running 'tns run android|ios' - snapshot, // --env.snapshot - production, // --env.production - report, // --env.report - hmr, // --env.hmr - } = env; - - const externals = (env.externals || []).map((e) => { // --env.externals - return new RegExp(e + ".*"); - }); - - const mode = production ? "production" : "development" - - const appFullPath = resolve(projectRoot, appPath); - const appResourcesFullPath = resolve(projectRoot, appResourcesPath); - - const entryModule = nsWebpack.getEntryModule(appFullPath); - const entryPath = `.${sep}${entryModule}.js`; - console.log(`Bundling application for entryPath ${entryPath}...`); - - const config = { - mode: mode, - context: appFullPath, - externals, - watchOptions: { - ignored: [ - appResourcesFullPath, - // Don't watch hidden files - "**/.*", - ], - }, - target: nativescriptTarget, - // target: nativeScriptVueTarget, - entry: { - bundle: entryPath, - }, - output: { - pathinfo: false, - path: dist, - libraryTarget: "commonjs2", - filename: "[name].js", - globalObject: "global", - }, - resolve: { - extensions: [".vue", ".js", ".scss", ".css"], - // Resolve {N} system modules from tns-core-modules - modules: [ - resolve(__dirname, "node_modules/tns-core-modules"), - resolve(__dirname, "node_modules"), - "node_modules/tns-core-modules", - "node_modules", - ], - alias: { - '~': appFullPath, - '@': appFullPath, - 'vue': 'nativescript-vue' - }, - // don't resolve symlinks to symlinked modules - symlinks: false, - }, - resolveLoader: { - // don't resolve symlinks to symlinked loaders - symlinks: false, - }, - node: { - // Disable node shims that conflict with NativeScript - "http": false, - "timers": false, - "setImmediate": false, - "fs": "empty", - "__dirname": false, - }, - devtool: "none", - optimization: { - splitChunks: { - cacheGroups: { - vendor: { - name: "vendor", - chunks: "all", - test: (module) => { - const moduleName = module.nameForCondition ? module.nameForCondition() : ''; - return /[\\/]node_modules[\\/]/.test(moduleName) || - appComponents.some(comp => comp === moduleName); - - }, - enforce: true, - }, - }, - }, - minimize: Boolean(production), - minimizer: [ - new UglifyJsPlugin({ - parallel: true, - cache: true, - uglifyOptions: { - output: { - comments: false, - }, - compress: { - // The Android SBG has problems parsing the output - // when these options are enabled - 'collapse_vars': platform !== "android", - sequences: platform !== "android", - }, - }, - }), - ], - }, - module: { - rules: [{ - test: new RegExp(entryPath), - use: [ - // Require all Android app components - platform === "android" && { - loader: "nativescript-dev-webpack/android-app-components-loader", - options: { modules: appComponents }, - }, - - { - loader: "nativescript-dev-webpack/bundle-config-loader", - options: { - registerPages: true, // applicable only for non-angular apps - loadCss: !snapshot, // load the application css if in debug mode - }, - }, - ].filter(loader => Boolean(loader)), - }, - { - test: /\.css$/, - use: [ - 'nativescript-dev-webpack/style-hot-loader', - 'nativescript-dev-webpack/apply-css-loader.js', - { loader: "css-loader", options: { minimize: false, url: false } }, - ], - }, - { - test: /\.scss$/, - use: [ - 'nativescript-dev-webpack/style-hot-loader', - 'nativescript-dev-webpack/apply-css-loader.js', - { loader: "css-loader", options: { minimize: false, url: false } }, - "sass-loader", - ], - }, - { - test: /\.js$/, - loader: 'babel-loader', - }, - { - test: /\.vue$/, - loader: "vue-loader", - options: { - compiler: NsVueTemplateCompiler, - }, - }, - ], - }, - plugins: [ - // ... Vue Loader plugin omitted - // make sure to include the plugin! - new VueLoaderPlugin(), - // Define useful constants like TNS_WEBPACK - new webpack.DefinePlugin({ - "global.TNS_WEBPACK": "true", - "TNS_ENV": JSON.stringify(mode) - }), - // Remove all files from the out dir. - new CleanWebpackPlugin([`${dist}/**/*`]), - // Copy native app resources to out dir. - new CopyWebpackPlugin([{ - from: `${appResourcesFullPath}/${appResourcesPlatformDir}`, - to: `${dist}/App_Resources/${appResourcesPlatformDir}`, - context: projectRoot, - }]), - // Copy assets to out dir. Add your own globs as needed. - new CopyWebpackPlugin([ - { from: { glob: "fonts/**" } }, - { from: { glob: "**/*.+(jpg|png)" } }, - { from: { glob: "assets/**/*" } }, - ], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }), - // Generate a bundle starter script and activate it in package.json - new nsWebpack.GenerateBundleStarterPlugin([ - "./vendor", - "./bundle", - ]), - // For instructions on how to set up workers with webpack - // check out https://github.com/nativescript/worker-loader - new NativeScriptWorkerPlugin(), - new nsWebpack.PlatformFSPlugin({ - platform, - platforms, - }), - // Does IPC communication with the {N} CLI to notify events when running in watch mode. - new nsWebpack.WatchStateLoggerPlugin(), - ], - }; - - if (report) { - // Generate report files for bundles content - config.plugins.push(new BundleAnalyzerPlugin({ - analyzerMode: "static", - openAnalyzer: false, - generateStatsFile: true, - reportFilename: resolve(projectRoot, "report", `report.html`), - statsFilename: resolve(projectRoot, "report", `stats.json`), - })); - } - - if (snapshot) { - config.plugins.push(new nsWebpack.NativeScriptSnapshotPlugin({ - chunk: "vendor", - requireModules: [ - "tns-core-modules/bundle-entry-points", - ], - projectRoot, - webpackConfig: config, - })); - } - - if (hmr) { - config.plugins.push(new webpack.HotModuleReplacementPlugin()); - } - - return config; -}; \ No newline at end of file diff --git a/demo-angular/app/App_Resources/Android/AndroidManifest.xml b/demo/app/App_Resources/Android/src/main/AndroidManifest.xml similarity index 97% rename from demo-angular/app/App_Resources/Android/AndroidManifest.xml rename to demo/app/App_Resources/Android/src/main/AndroidManifest.xml index 9db8321..976f83f 100644 --- a/demo-angular/app/App_Resources/Android/AndroidManifest.xml +++ b/demo/app/App_Resources/Android/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ + diff --git a/demo/app/app.ts b/demo/app/app.ts index 51d53f2..f171f58 100644 --- a/demo/app/app.ts +++ b/demo/app/app.ts @@ -1,4 +1,4 @@ import * as application from 'tns-core-modules/application'; application.setCssFileName("./app.css"); -application.start({ moduleName: "main-page" }); +application.run({ moduleName: "app-root" }); diff --git a/demo/app/package.json b/demo/app/package.json index 84c391d..ffa066e 100644 --- a/demo/app/package.json +++ b/demo/app/package.json @@ -1,9 +1,8 @@ { "android": { "v8Flags": "--expose_gc", - "requireModules": ["nativescript-imagepicker"] + "requireModules": ["nativescript-imagepicker"], + "markingMode": "none" }, - "main": "app.js", - "name": "tns-template-hello-world", - "version": "3.1.0" + "main": "app.js" } diff --git a/demo/e2e/config/appium.capabilities.json b/demo/e2e/config/appium.capabilities.json deleted file mode 100644 index 0d81c9b..0000000 --- a/demo/e2e/config/appium.capabilities.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "android23.local": { - "platformName": "Android", - "platformVersion": "6.0", - "deviceName": "Emulator_Api23_Default", - "avd": "Emulator_Api23_Default", - "noReset": true - }, - "android23": { - "platformName": "Android", - "platformVersion": "6.0", - "deviceName": "Android Emulator", - "appium-version": "1.7.1", - "noReset": true - }, - "android25": { - "platformName": "Android", - "platformVersion": "7.1", - "deviceName": "Android GoogleAPI Emulator", - "appium-version": "1.7.1", - "noReset": true - }, - "sim11iPhone6": { - "platformName": "iOS", - "platformVersion": "11.0", - "deviceName": "iPhone 6", - "appium-version": "1.7.1", - "app": "" - }, - "sim103iPhone6": { - "browserName": "", - "appium-version": "1.7.1", - "platformName": "iOS", - "platformVersion": "10.3", - "deviceName": "iPhone 6", - "app": "" - }, - "sim10iPhone6": { - "platformName": "iOS", - "platformVersion": "10.0", - "deviceName": "iPhone 6", - "appium-version": "1.7.1", - "app": "" - } -} \ No newline at end of file diff --git a/demo/e2e/config/mocha.opts b/demo/e2e/config/mocha.opts deleted file mode 100644 index 308e22f..0000000 --- a/demo/e2e/config/mocha.opts +++ /dev/null @@ -1,3 +0,0 @@ ---timeout 800000 ---recursive e2e ---exit \ No newline at end of file diff --git a/demo/karma.conf.js b/demo/karma.conf.js index 91d9c28..7974602 100644 --- a/demo/karma.conf.js +++ b/demo/karma.conf.js @@ -1,5 +1,5 @@ -module.exports = function(config) { - config.set({ +module.exports = function (config) { + const options = { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', @@ -11,9 +11,7 @@ module.exports = function(config) { // list of files / patterns to load in the browser - files: [ - 'app/**/*.js', - ], + files: ['app/tests/**/*.*'], // list of files to exclude @@ -72,6 +70,39 @@ module.exports = function(config) { // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits - singleRun: true - }); -}; + singleRun: false + }; + + setWebpackPreprocessor(config, options); + setWebpack(config, options); + + config.set(options); +} + +function setWebpackPreprocessor(config, options) { + if (config && config.bundle) { + if (!options.preprocessors) { + options.preprocessors = {}; + } + + options.files.forEach(file => { + if (!options.preprocessors[file]) { + options.preprocessors[file] = []; + } + options.preprocessors[file].push('webpack'); + }); + } +} + +function setWebpack(config, options) { + if (config && config.bundle) { + const env = {}; + env[config.platform] = true; + env.sourceMap = config.debugBrk; + options.webpack = require('./webpack.config')(env); + delete options.webpack.entry; + delete options.webpack.output.libraryTarget; + const invalidPluginsForUnitTesting = ["GenerateBundleStarterPlugin", "GenerateNativeScriptEntryPointsPlugin"]; + options.webpack.plugins = options.webpack.plugins.filter(p => !invalidPluginsForUnitTesting.includes(p.constructor.name)); + } +} diff --git a/demo/package.json b/demo/package.json index 41c25ba..076077f 100644 --- a/demo/package.json +++ b/demo/package.json @@ -2,17 +2,17 @@ "nativescript": { "id": "org.nativescript.imagepickerdemo", "tns-android": { - "version": "5.1.0" + "version": "6.2.0" }, "tns-ios": { - "version": "5.1.0" + "version": "6.2.0" } }, "dependencies": { "nativescript-imagepicker": "../src", "nativescript-theme-core": "^1.0.4", - "nativescript-unit-test-runner": "^0.3.4", - "tns-core-modules": "^5.1.0" + "nativescript-unit-test-runner": "0.7.0", + "tns-core-modules": "^6.0.0" }, "devDependencies": { "@types/chai": "^4.1.4", @@ -21,16 +21,20 @@ "chai": "~4.1.2", "chai-as-promised": "~7.1.1", "jasmine-core": "^3.2.0", - "karma": "^2.0.5", - "karma-jasmine": "^1.1.2", + "karma": "4.1.0", + "karma-jasmine": "2.0.1", "karma-nativescript-launcher": "^0.4.0", - "mocha": "~5.2.0", + "mocha": "^3.3.0", + "mocha-junit-reporter": "^1.18.0", + "mocha-multi": "^1.0.1", + "mochawesome": "^3.1.1", "nativescript-css-loader": "~0.26.0", - "nativescript-dev-appium": "4.0.9", - "nativescript-dev-typescript": "~0.7.0", - "nativescript-dev-webpack": "~0.18.0", - "tns-platform-declarations": "^5.1.0", - "tslint": "~5.11.0" + "nativescript-dev-appium": "~5.2.0", + "nativescript-dev-webpack": "^1.3.0", + "tns-platform-declarations": "^6.0.0", + "tslint": "~5.11.0", + "typescript": "~3.5.3", + "karma-webpack": "3.0.5" }, "scripts": { "build.plugin": "cd ../src && npm run build", diff --git a/src/imagepicker.android.ts b/src/imagepicker.android.ts index 8a927e9..245c415 100644 --- a/src/imagepicker.android.ts +++ b/src/imagepicker.android.ts @@ -2,6 +2,9 @@ import * as application from "tns-core-modules/application"; import * as imageAssetModule from "tns-core-modules/image-asset"; import * as permissions from "nativescript-permissions"; +import { ImagePickerMediaType, Options } from "./imagepicker.common"; +export * from "./imagepicker.common"; + class UriHelper { public static _calculateFileUri(uri: android.net.Uri) { let DocumentsContract = (android.provider).DocumentsContract; @@ -19,22 +22,24 @@ class UriHelper { if ("primary" === type.toLowerCase()) { return android.os.Environment.getExternalStorageDirectory() + "/" + id; + } else { + if (android.os.Build.VERSION.SDK_INT > 23) { + (this.getContentResolver() as any).takePersistableUriPermission( + uri, + android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION | android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + ); + const externalMediaDirs = application.android.context.getExternalMediaDirs(); + if (externalMediaDirs.length > 1) { + let filePath = externalMediaDirs[1].getAbsolutePath(); + filePath = filePath.substring(0, filePath.indexOf("Android")) + id; + return filePath; + } + } } - - // TODO handle non-primary volumes } // DownloadsProvider else if (UriHelper.isDownloadsDocument(uri)) { - id = DocumentsContract.getDocumentId(uri); - // Since Oreo the downloads id may be a raw string, - // containing the file path: - if (id.indexOf("raw:") !== -1) { - return id.substring(4, id.length); - } - contentUri = android.content.ContentUris.withAppendedId( - android.net.Uri.parse("content://downloads/public_downloads"), long(id)); - - return UriHelper.getDataColumn(contentUri, null, null); + return UriHelper.getDataColumn(uri, null, null, true); } // MediaProvider else if (UriHelper.isMediaDocument(uri)) { @@ -54,13 +59,13 @@ class UriHelper { let selection = "_id=?"; let selectionArgs = [id]; - return UriHelper.getDataColumn(contentUri, selection, selectionArgs); + return UriHelper.getDataColumn(contentUri, selection, selectionArgs, false); } } else { // MediaStore (and general) if ("content" === uri.getScheme()) { - return UriHelper.getDataColumn(uri, null, null); + return UriHelper.getDataColumn(uri, null, null, false); } // FILE else if ("file" === uri.getScheme()) { @@ -71,31 +76,57 @@ class UriHelper { return undefined; } - private static getDataColumn(uri: android.net.Uri, selection, selectionArgs) { + private static getDataColumn(uri: android.net.Uri, selection, selectionArgs, isDownload: boolean) { let cursor = null; - let columns = [android.provider.MediaStore.MediaColumns.DATA]; let filePath; - - try { - cursor = this.getContentResolver().query(uri, columns, selection, selectionArgs, null); - if (cursor != null && cursor.moveToFirst()) { - let column_index = cursor.getColumnIndexOrThrow(columns[0]); - filePath = cursor.getString(column_index); - if (filePath) { - return filePath; + if (isDownload) { + let columns = ["_display_name"]; + try { + cursor = this.getContentResolver().query(uri, columns, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + let column_index = cursor.getColumnIndexOrThrow(columns[0]); + filePath = cursor.getString(column_index); + if (filePath) { + const dl = android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS); + filePath = `${dl}/${filePath}`; + return filePath; + } + } + } + catch (e) { + console.log(e); + } + finally { + if (cursor) { + cursor.close(); } } } - catch (e) { - console.log(e); - } - finally { - if (cursor) { - cursor.close(); + else { + let columns = [android.provider.MediaStore.MediaColumns.DATA]; + let filePath; + + try { + cursor = this.getContentResolver().query(uri, columns, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + let column_index = cursor.getColumnIndexOrThrow(columns[0]); + filePath = cursor.getString(column_index); + if (filePath) { + return filePath; + } + } + } + catch (e) { + console.log(e); + } + finally { + if (cursor) { + cursor.close(); + } } } - return undefined; + } private static isExternalStorageDocument(uri: android.net.Uri) { @@ -116,9 +147,9 @@ class UriHelper { } export class ImagePicker { - private _options; + private _options: Options; - constructor(options) { + constructor(options: Options) { this._options = options; } @@ -126,6 +157,31 @@ export class ImagePicker { return this._options && this._options.mode && this._options.mode.toLowerCase() === 'single' ? 'single' : 'multiple'; } + get mediaType(): string { + const mediaType = this._options && 'mediaType' in this._options ? this._options.mediaType : ImagePickerMediaType.Any; + if (mediaType === ImagePickerMediaType.Image) { + return "image/*"; + } else if (mediaType === ImagePickerMediaType.Video) { + return "video/*"; + } else { + return "*/*"; + } + } + + get mimeTypes() { + let length = this.mediaType === "*/*" ? 2 : 1; + let mimeTypes = Array.create(java.lang.String, length); + + if (this.mediaType === "*/*") { + mimeTypes[0] = "image/*"; + mimeTypes[1] = "video/*"; + } + else { + mimeTypes[0] = this.mediaType; + } + return mimeTypes; + } + authorize(): Promise { if ((android).os.Build.VERSION.SDK_INT >= 23) { return permissions.requestPermission([(android).Manifest.permission.READ_EXTERNAL_STORAGE]); @@ -140,7 +196,7 @@ export class ImagePicker { // WARNING: If we want to support multiple pickers we will need to have a range of IDs here: let RESULT_CODE_PICKER_IMAGES = 9192; - let application = require("application"); + let application = require("tns-core-modules/application"); application.android.on(application.AndroidApplication.activityResultEvent, onResult); function onResult(args) { @@ -194,13 +250,20 @@ export class ImagePicker { let Intent = android.content.Intent; let intent = new Intent(); - intent.setType("image/*"); + intent.setType(this.mediaType); + + // not in platform-declaration typings + intent.putExtra((android.content.Intent as any).EXTRA_MIME_TYPES, this.mimeTypes); // TODO: Use (android).content.Intent.EXTRA_ALLOW_MULTIPLE if (this.mode === 'multiple') { intent.putExtra("android.intent.extra.ALLOW_MULTIPLE", true); } + if (this._options.showAdvanced) { + intent.putExtra("android.content.extra.SHOW_ADVANCED", true); + } + intent.putExtra(android.content.Intent.EXTRA_LOCAL_ONLY, true); intent.setAction("android.intent.action.OPEN_DOCUMENT"); let chooser = Intent.createChooser(intent, "Select Picture"); @@ -209,6 +272,6 @@ export class ImagePicker { } } -export function create(options?): ImagePicker { +export function create(options?: Options): ImagePicker { return new ImagePicker(options); } diff --git a/src/imagepicker.common.ts b/src/imagepicker.common.ts new file mode 100644 index 0000000..0d904f4 --- /dev/null +++ b/src/imagepicker.common.ts @@ -0,0 +1,63 @@ +export enum ImagePickerMediaType { + Any = 0, + Image = 1, + Video = 2 +} + +/** + * Provide options for the image picker. + */ +export interface Options { + /** + * Set the picker mode. Supported modes: "single" or "multiple" (default). + */ + mode?: string; + + /** + * Set the minumum number of selected assets in iOS + */ + minimumNumberOfSelection?: number; + + /** + * Set the maximum number of selected assets in iOS + */ + maximumNumberOfSelection?: number; + + /** + * Display the number of selected assets in iOS + */ + showsNumberOfSelectedAssets?: boolean; + + /** + * Display prompt text when selecting assets in iOS + */ + prompt?: string; + + /** + * Set the number of columns in Portrait in iOS + */ + numberOfColumnsInPortrait?: number; + + /** + * Set the number of columns in Landscape in iOS + */ + numberOfColumnsInLandscape?: number; + + /** + * Set the media type (image/video/any) to pick + */ + mediaType?: ImagePickerMediaType; + + /** + * Show internal and removable storage options on Android. + * Not supported officially, see https://issuetracker.google.com/issues/72053350 | + */ + showAdvanced?: boolean; + + android?: { + /** + * Provide a reason for permission request to access external storage on api levels above 23. + */ + read_external_storage?: string; + }; +} \ No newline at end of file diff --git a/src/imagepicker.ios.ts b/src/imagepicker.ios.ts index 3af82d2..6127e63 100644 --- a/src/imagepicker.ios.ts +++ b/src/imagepicker.ios.ts @@ -1,8 +1,9 @@ import * as data_observable from "tns-core-modules/data/observable"; import * as imageAssetModule from "tns-core-modules/image-asset"; -import { Options, ImagePickerMediaType } from "."; +import { Options, ImagePickerMediaType } from "./imagepicker.common"; import { View } from "tns-core-modules/ui/core/view/view"; import * as utils from "tns-core-modules/utils/utils"; +export * from "./imagepicker.common"; const defaultAssetCollectionSubtypes: NSArray = NSArray.arrayWithArray([ PHAssetCollectionSubtype.SmartAlbumRecentlyAdded, @@ -19,7 +20,6 @@ const defaultAssetCollectionSubtypes: NSArray = NSArray.arrayWithArray(options.mediaType.valueOf() : QBImagePickerMediaType.Any; - imagePickerController.delegate = this._imagePickerControllerDelegate; - imagePickerController.allowsMultipleSelection = options.mode === 'multiple'; + imagePickerController.allowsMultipleSelection = options.mode !== 'single'; imagePickerController.minimumNumberOfSelection = options.minimumNumberOfSelection || 0; imagePickerController.maximumNumberOfSelection = options.maximumNumberOfSelection || 0; imagePickerController.showsNumberOfSelectedAssets = options.showsNumberOfSelectedAssets || true; @@ -77,8 +76,11 @@ export class ImagePicker extends data_observable.Observable { present() { return new Promise((resolve, reject) => { - this._imagePickerControllerDelegate._resolve = resolve; - this._imagePickerControllerDelegate._reject = reject; + const imagePickerControllerDelegate = ImagePickerControllerDelegate.new(); + imagePickerControllerDelegate._resolve = resolve; + imagePickerControllerDelegate._reject = reject; + + this._imagePickerController.delegate = imagePickerControllerDelegate; this.hostController.presentViewControllerAnimatedCompletion(this._imagePickerController, true, null); }); @@ -92,6 +94,8 @@ export class ImagePickerControllerDelegate extends NSObject implements QBImagePi qb_imagePickerControllerDidCancel?(imagePickerController: QBImagePickerController): void { imagePickerController.dismissViewControllerAnimatedCompletion(true, null); this._reject(new Error("Selection canceled.")); + + this.deRegisterFromGlobal(); } qb_imagePickerControllerDidFinishPickingAssets?(imagePickerController: QBImagePickerController, iosAssets: NSArray): void { @@ -111,6 +115,7 @@ export class ImagePickerControllerDelegate extends NSObject implements QBImagePi this._resolve(assets); imagePickerController.dismissViewControllerAnimatedCompletion(true, () => { + this.deRegisterFromGlobal(); // FIX: possible memory issue when picking images many times. // Not the best solution, but the only one working for now // https://github.com/NativeScript/nativescript-imagepicker/issues/222 @@ -119,14 +124,27 @@ export class ImagePickerControllerDelegate extends NSObject implements QBImagePi } + // FIX: stores a reference to global scope, so that the delegate is not collected in native + // https://github.com/NativeScript/nativescript-imagepicker/issues/251 + private registerToGlobal(): any { + (global).imagePickerControllerDelegate = this; + } + + private deRegisterFromGlobal(): any { + (global).imagePickerControllerDelegate = null; + } + public static ObjCProtocols = [QBImagePickerControllerDelegate]; static new(): ImagePickerControllerDelegate { - return super.new(); // calls new() on the NSObject + const instance = super.new(); // calls new() on the NSObject + + instance.registerToGlobal(); + + return instance; } } export function create(options?: Options, hostView?: View): ImagePicker { return new ImagePicker(options, hostView); } - diff --git a/src/index.d.ts b/src/index.d.ts index 50cf59b..8a5b1a8 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -4,6 +4,9 @@ import { ImageAsset } from "tns-core-modules/image-asset"; import { View } from "tns-core-modules/ui/core/view/view"; export class ImagePicker { + + constructor(options?: Options); + /** * Call this before 'present' to request any additional permissions that may be necessary. * In case of failed authorization consider notifying the user for degraded functionality. @@ -63,16 +66,23 @@ interface Options { numberOfColumnsInLandscape?: number; /** - * Set the media type (image/video/both) to pick in iOS + * Set the media type (image/video/any) to pick */ mediaType?: ImagePickerMediaType; + /** + * Show internal and removable storage options on Android. + * Not supported officially, see https://issuetracker.google.com/issues/72053350 | + */ + showAdvanced?: boolean; + android?: { /** * Provide a reason for permission request to access external storage on api levels above 23. */ read_external_storage?: string; }; + } /** diff --git a/src/package.json b/src/package.json index aa10970..f97ecb9 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "nativescript-imagepicker", - "version": "6.0.6", + "version": "7.1.0", "description": "A plugin for the NativeScript framework implementing multiple image picker", "repository": { "type": "git", @@ -26,10 +26,10 @@ "tslint": "cd .. && tslint \"**/*.ts\" --config tslint.json --exclude \"**/node_modules/**\"", "plugin.link": "npm link && cd ../demo && npm link nativescript-imagepicker && cd ../demo-angular && npm link nativescript-imagepicker", "plugin.tscwatch": "npm run tsc -- -w", - "demo.ios": "npm i && npm run tsc && cd ../demo && tns run ios --syncAllFiles", - "demo.android": "npm i && npm run tsc && cd ../demo && tns run android --syncAllFiles", - "demo.ng.ios": "npm i && npm run tsc && cd ../demo-angular && tns run ios --syncAllFiles", - "demo.ng.android": "npm i && npm run tsc && cd ../demo-angular && tns run android --syncAllFiles", + "demo.ios": "npm i && npm run tsc && cd ../demo && tns run ios", + "demo.android": "npm i && npm run tsc && cd ../demo && tns run android", + "demo.ng.ios": "npm i && npm run tsc && cd ../demo-angular && tns run ios", + "demo.ng.android": "npm i && npm run tsc && cd ../demo-angular && tns run android", "clean": "rm -rf node_modules && cd ../demo && rm -rf hooks node_modules platforms && cd ../src && npm run plugin.link" }, "keywords": [ @@ -46,13 +46,13 @@ "homepage": "https://github.com/NativeScript/nativescript-imagepicker", "readmeFilename": "README.md", "devDependencies": { - "tns-core-modules": "^5.1.0", - "tns-platform-declarations": "^5.1.0", - "typescript": "~3.1.6", + "tns-core-modules": "^6.0.0", + "tns-platform-declarations": "^6.0.0", + "typescript": "~3.5.3", "tslint": "~5.11.0" }, "dependencies": { - "nativescript-permissions": "~1.2.3" + "nativescript-permissions": "~1.3.0" }, "bootstrapper": "nativescript-plugin-seed" } diff --git a/src/platforms/android/AndroidManifest.xml b/src/platforms/android/AndroidManifest.xml index 5a0779c..4df3b7f 100644 --- a/src/platforms/android/AndroidManifest.xml +++ b/src/platforms/android/AndroidManifest.xml @@ -6,5 +6,5 @@ - + diff --git a/src/platforms/ios/Podfile b/src/platforms/ios/Podfile index 4bf1aa1..66bb1d8 100644 --- a/src/platforms/ios/Podfile +++ b/src/platforms/ios/Podfile @@ -1 +1 @@ -pod 'QBImagePickerController', '~> 3.4.0' \ No newline at end of file +pod "QBImagePickerController", :git => 'https://github.com/flypapertech/QBImagePicker.git', :commit => '47aa21d32f8e6db0df79b089ad64e43d498b5951' diff --git a/tests/e2e/config/appium.capabilities.json b/tests/e2e/config/appium.capabilities.json new file mode 100644 index 0000000..0347667 --- /dev/null +++ b/tests/e2e/config/appium.capabilities.json @@ -0,0 +1,81 @@ +{ + "android23.local": { + "platformName": "Android", + "platformVersion": "6.0", + "deviceName": "Emulator-Api23-Default", + "avd": "Emulator-Api23-Default", + "noReset": true + }, + "android23": { + "platformName": "Android", + "platformVersion": "6.0", + "deviceName": "Android Emulator", + "appium-version": "1.7.1", + "noReset": true + }, + "android24": { + "platformName": "Android", + "platformVersion": "7.0", + "deviceName": "Android GoogleAPI Emulator", + "appiumVersion": "1.9.1", + "noReset": true, + "app": "" + }, + "android24.local": { + "platformName": "Android", + "platformVersion": "7.0", + "deviceName": "Emulator-Api24-Default", + "avd": "Emulator-Api24-Default", + "appiumVersion": "1.9.1", + "noReset": true + }, + "android25": { + "platformName": "Android", + "platformVersion": "7.1", + "deviceName": "Android GoogleAPI Emulator", + "appium-version": "1.7.1", + "noReset": true + }, + "android28": { + "platformName": "Android", + "platformVersion": "9.0", + "deviceName": "Android GoogleAPI Emulator", + "appiumVersion": "1.9.1", + "noReset": true + }, + "sim11iPhone6": { + "platformName": "iOS", + "platformVersion": "11.0", + "deviceName": "iPhone 6", + "appium-version": "1.7.1", + "app": "" + }, + "sim103iPhone6": { + "browserName": "", + "appium-version": "1.7.1", + "platformName": "iOS", + "platformVersion": "10.3", + "deviceName": "iPhone 6", + "app": "" + }, + "sim10iPhone6": { + "platformName": "iOS", + "platformVersion": "10.0", + "deviceName": "iPhone 6", + "appium-version": "1.7.1", + "app": "" + }, + "sim12iPhoneX":{ + "platformName": "iOS", + "platformVersion": "12.0", + "deviceName": "iPhone X", + "appium-version": "1.9.1", + "app": "", + "noReset": true, + "fullReset": false, + "density": 3, + "offsetPixels": 87, + "idleTimeout": 120, + "automationName": "Appium" + } +} \ No newline at end of file diff --git a/tests/e2e/config/mocha.opts b/tests/e2e/config/mocha.opts new file mode 100644 index 0000000..d7d26a6 --- /dev/null +++ b/tests/e2e/config/mocha.opts @@ -0,0 +1,5 @@ +--timeout 800000 +--recursive e2e +--reporter mocha-multi +--reporter-options mochawesome=-,mocha-junit-reporter=test-results.xml +--exit \ No newline at end of file diff --git a/tests/e2e/helper.ts b/tests/e2e/helper.ts new file mode 100644 index 0000000..6f7a49f --- /dev/null +++ b/tests/e2e/helper.ts @@ -0,0 +1,10 @@ +import { AppiumDriver } from "nativescript-dev-appium"; + +export async function findAndroidImages(driver: AppiumDriver) { + const sidedrawer = await driver.findElementByAccessibilityId("Show roots"); + await sidedrawer.click(); + const images = await driver.findElementByText("Images"); + await images.click(); + const dcimFolder = await driver.findElementByText("DCIM"); + await dcimFolder.click(); +} \ No newline at end of file diff --git a/demo/e2e/setup.ts b/tests/e2e/setup.ts similarity index 100% rename from demo/e2e/setup.ts rename to tests/e2e/setup.ts diff --git a/demo/e2e/test.e2e.ts b/tests/e2e/test.e2e.ts similarity index 62% rename from demo/e2e/test.e2e.ts rename to tests/e2e/test.e2e.ts index 9419a34..d6d718a 100644 --- a/demo/e2e/test.e2e.ts +++ b/tests/e2e/test.e2e.ts @@ -1,6 +1,10 @@ import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium"; import { isSauceLab, runType } from "nativescript-dev-appium/lib/parser"; import { expect } from "chai"; +import { findAndroidImages } from "./helper"; +const fs = require('fs'); +const addContext = require('mochawesome/addContext'); +const rimraf = require('rimraf'); const isSauceRun = isSauceLab; const isAndroid: boolean = runType.includes("android"); @@ -14,6 +18,11 @@ describe("Imagepicker", async function () { before(async () => { driver = await createDriver(); driver.defaultWaitTime = 10000; + let dir = "mochawesome-report"; + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + rimraf('mochawesome-report/*', function () { }); }); after(async () => { @@ -26,6 +35,19 @@ describe("Imagepicker", async function () { console.log("Driver quits!"); }); + afterEach(async function () { + if (this.currentTest.state && this.currentTest.state === "failed") { + let png = await driver.logScreenshot(this.currentTest.title); + fs.copyFile(png, './mochawesome-report/' + this.currentTest.title + '.png', function (err) { + if (err) { + throw err; + } + console.log('Screenshot saved.'); + }); + addContext(this, './' + this.currentTest.title + '.png'); + } + }); + it("should pick one image", async function () { // await driver.driver.resetApp(); const pickSingleButtonText = "Pick Single"; @@ -36,23 +58,19 @@ describe("Imagepicker", async function () { const confirmButton = await driver.findElementByText(confirmButtonText); await confirmButton.click(); + // TODO remove when SauceLabs fix images location if (isAndroid) { - const imagesFolderXpath = await driver.elementHelper.getXPathByText(imagesFolderName, SearchOptions.contains); - await driver.driver.sleep(3000); - let imagesFolder = await driver.driver.elementByXPathIfExists(imagesFolderXpath, 10000); - - if (isSauceRun && imagesFolder) { - await imagesFolder.click(); - imagesFolder = await driver.findElementByClassName(driver.locators.image); - await imagesFolder.click(); - } - } else { - const cameraRollFolder = await driver.findElementByText(imagesFolderNameIos); + await findAndroidImages(driver); + } + + if (!isAndroid) { + const cameraRollFolder = await driver.findElementByAccessibilityId(imagesFolderNameIos); await cameraRollFolder.click(); } - const pickedImage = await driver.findElementByClassName(driver.locators.image); - await pickedImage.click(); + const imageLocator = isAndroid ? "android.widget.ImageView" : "XCUIElementTypeCell"; + const images = await driver.findElementsByClassName(imageLocator); + await images[1].tap(); pickSingleButton = await driver.findElementByText(pickSingleButtonText, SearchOptions.contains); expect(pickSingleButton).to.exist; @@ -70,17 +88,22 @@ describe("Imagepicker", async function () { const pickMultipleButton = await driver.findElementByText(pickMultipleButtonText, SearchOptions.contains); await pickMultipleButton.click(); + // TODO remove when SauceLabs fix images location + if (isAndroid) { + await findAndroidImages(driver); + } + if (!isAndroid) { const cameraRollFolder = await driver.findElementByText(imagesFolderNameIos); await cameraRollFolder.click(); } - const allImages = await driver.findElementsByClassName(driver.locators.image); - if (isAndroid) { - await allImages[8].hold(); // third image - await allImages[4].click(); // second image + const allImages = await driver.findElementsByClassName("android.widget.ImageView"); + await allImages[1].hold(); // second Image + await allImages[4].click(); // first image } else { + const allImages = await driver.findElementsByClassName("XCUIElementTypeCell"); await allImages[0].click(); // first image await allImages[1].click(); // second image } diff --git a/demo/e2e/tsconfig.json b/tests/e2e/tsconfig.json similarity index 100% rename from demo/e2e/tsconfig.json rename to tests/e2e/tsconfig.json diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 0000000..4370321 --- /dev/null +++ b/tests/package.json @@ -0,0 +1,22 @@ + +{ + "devDependencies": { + "@types/chai": "^4.1.3", + "@types/mocha": "^5.2.0", + "@types/node": "^10.1.2", + "babel-loader": "~8.0.0", + "chai": "~4.1.2", + "chai-as-promised": "~7.1.1", + "karma": "^2.0.2", + "karma-nativescript-launcher": "^0.4.0", + "mocha": "^3.3.0", + "mocha-junit-reporter": "^1.18.0", + "mocha-multi": "1.1.0", + "mochawesome": "^3.1.1", + "nativescript-dev-appium": "~5.2.0", + "typescript": "~3.5.3" + }, + "scripts": { + "e2e": "tsc -p e2e && mocha --opts ./e2e/config/mocha.opts" + } +}