diff --git a/.babelrc b/.babelrc index 3b8efe7..dac7dc9 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,3 @@ { - "presets": ["airbnb", "react-native"], + "presets": ["react-native"], } diff --git a/.eslintrc b/.eslintrc index 63d3749..ef52fb4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,10 +7,9 @@ "env": { "mocha": true }, - "parser": "babel-eslint", "rules": { "no-unused-vars": [2, {"vars": "all", "args": "none"}], - "comma-dangle": 0, + "comma-dangle": ["error", "never"], "func-names": 0, "prefer-arrow-callback": 0, "global-require": 0, @@ -20,7 +19,11 @@ "guard-for-in": 0, "no-restricted-syntax": 0, "prefer-template": 0, - "no-console": 0, + "no-unused-expressions": 0, + "vars-on-top": 0, + "react/jsx-no-bind": 0, + "one-var": 0, + "one-var-declaration-per-line": 0, "max-len": [ 2, 120, @@ -28,6 +31,12 @@ { "ignoreComments": true } - ] + ], + "no-multiple-empty-lines": [2, {"max": 3}], + "import/no-extraneous-dependencies": 0, + "react/jsx-filename-extension": 0, + "import/no-dynamic-require": 0, + "no-multi-assign": 0, + "o-undef": 0, } } diff --git a/.gitignore b/.gitignore index 51d9ef1..9db71bb 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ node_modules .node_repl_history /build +haste-map.json +.nyc_output/ diff --git a/.nycrc b/.nycrc new file mode 100644 index 0000000..6046117 --- /dev/null +++ b/.nycrc @@ -0,0 +1,23 @@ +{ + "lines": 0, + "statements": 0, + "functions": 0, + "branches": 0, + "include": [ + "src/**/*.js", + "mock.js" + ], + "extension": [ + ".js" + ], + "reporter": [ + "lcov", + "text", + "text-summary" + ], + "all": true, + "check-coverage": true, + "sourceMap": true, + "instrument": true, + "report-dir": "./coverage" +} diff --git a/.travis.yml b/.travis.yml index e0db197..932b332 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,17 @@ language: node_js node_js: - - "4.0.0" - "6.0.0" - - "4" - "6" + - "8.9.1" + - "8" + + +# values taken from react-native's package.json +env: + - REACT_NATIVE_VERSION=0.50 REACT_VERSION=16.0.0 + +before_install: + - npm install react@~$REACT_VERSION react-dom@~$REACT_VERSION react-native@~$REACT_NATIVE_VERSION + +before_script: + - npm run build-haste diff --git a/LICENSE b/LICENSE index 63e0bfb..dca202a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Jake Howard +Copyright (c) 2017 Jake Howard Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 94089a5..541723c 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,37 @@ -# react-native-mock [![Build Status](https://travis-ci.org/RealOrangeOne/react-native-mock.svg?branch=master)](https://travis-ci.org/RealOrangeOne/react-native-mock) +# React-Native-Mock + +[![Travis](https://img.shields.io/travis/RealOrangeOne/react-native-mock.svg?style=flat-square)](https://travis-ci.org/RealOrangeOne/react-native-mock) +[![CircleCI](https://img.shields.io/circleci/project/github/RealOrangeOne/react-native-mock.svg?style=flat-square)](https://circleci.com/gh/RealOrangeOne/react-native-mock) +[![npm](https://img.shields.io/npm/dm/react-native-mock.svg?style=flat-square)](https://www.npmjs.com/package/react-native-mock) +[![npm](https://img.shields.io/npm/v/react-native-mock.svg?style=flat-square)](https://www.npmjs.com/package/react-native-mock) +[![Gitter](https://img.shields.io/gitter/room/RealOrangeOne/react-native-mock.svg?style=flat-square)](https://gitter.im/RealOrangeOne/react-native-mock) +[![Codecov](https://img.shields.io/codecov/c/github/RealOrangeOne/react-native-mock.svg?style=flat-square)](https://codecov.io/gh/RealOrangeOne/react-native-mock) -[![Join the chat at https://gitter.im/RealOrangeOne/react-native-mock](https://badges.gitter.im/RealOrangeOne/react-native-mock.svg)](https://gitter.im/RealOrangeOne/react-native-mock?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) A fully mocked and test-friendly version of react native ## Requirements -- Node.js 4+ +- Node.js 6+ (dropping support for Node 4 as some dev dependencies don't support it) - The latest version of react-native -__Note__: This library is designed to work with the most recent version of react-native. If you aren't using the most recent version, you will need to download an older version of this library, as the API is likely to be different, and the dependencies are likely to break. +__Note__: This library is designed to work with the most recent version of react-native. If you aren't using the most recent version, you will probably need to download an older version of this library, as the API is likely to be different, and the dependencies are likely to break. ## How Am I Supposed To Use This? +##### Install it + ```bash -npm i react-native-mock --save-dev +npm install react-native-mock --save-dev ``` +##### Add it to your test pipeline _(there's multiple ways)_ ```js /* file-that-runs-before-all-of-my-tests.js */ - -// This will mutate `react-native`'s require cache with `react-native-mock`'s. require('react-native-mock/mock'); // <-- side-effects!!! ``` -## Why? - -Testing React Native components is *hard*. I'm hoping this makes it easier. - -I wrote a React Testing Library that works really well for React "Web", but didn't really work for React "Native" without something like this. - - -## Wait... Is this actually a terrible idea? - -I don't know. Maybe. - -I'd love to figure that out though... feel free to file an issue if you have opinions. - +```bash +mocha --require react-native-mock/mock ... +``` ## Contributing Discovered a bug, got a new feature, or found something that needs improving? __Submit a PR!__ @@ -44,6 +41,3 @@ Make sure to read through the CONTRIBUTING.md file before submitting your PR! ### Core Contributors - [Jake Howard](https://github.com/RealOrangeOne) - [Leland Richardson](https://github.com/lelandrichardson) (Original Creator) - -## What do the labels mean? -See [this wiki page](https://github.com/RealOrangeOne/react-native-mock/wiki/Labels---What-do-they-mean%3F). diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..aa99799 --- /dev/null +++ b/circle.yml @@ -0,0 +1,9 @@ +machine: + node: + version: 6 + +test: + post: + - bash ./scripts/run_jest.sh + - bash <(curl -s https://codecov.io/bash) + diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..4106c3e --- /dev/null +++ b/codecov.yml @@ -0,0 +1,11 @@ +coverage: + precision: 2 + round: down + range: "70...100" + + status: + project: false + patch: false + changes: false + +comment: off diff --git a/mock.js b/mock.js index 124d710..fb689dc 100644 --- a/mock.js +++ b/mock.js @@ -1,12 +1,5 @@ -const ReactNativeMock = require('./build/react-native'); +/* + Shortcut file to allow use via `react-native-mock/mock` +*/ -// the cache key that real react native would get -const key = require.resolve('react-native'); - -// make sure the cache is filled with our lib -require.cache[key] = { - id: key, - filename: key, - loaded: true, - exports: ReactNativeMock, -}; +module.exports = require('./build/react-native-mock.js'); diff --git a/package.json b/package.json index 08eb627..7685433 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,20 @@ { "name": "react-native-mock", - "version": "0.3.1", + "version": "1.0.0", "description": "A fully mocked and test-friendly version of react native", - "main": "build/react-native.js", + "main": "build/no-import.js", "scripts": { "prepublish": "npm run build", - "test": "npm run lint && npm run mocha", - "mocha": "mocha --require babel-core/register 'test/**/*.js'", - "mocha:watch": "npm run test -- --watch", - "build": "rm -rf build/ && babel src --out-dir build", - "lint": "eslint 'src/' 'test/' 'mock.js'" + "postinstall": "npm run build-haste", + "test": "npm run lint && npm run coverage", + "mocha": "mocha --require scripts/test-helper.js mock.js --bail --timeout 60000 'tests/**/*.test.js'", + "lint": "eslint 'src/' 'tests/' 'scripts/' 'mock.js'", + "coverage": "nyc npm run mocha", + "build": "babel src --out-dir build", + "build-haste": "node src/haste.js" + }, + "jest": { + "preset": "react-native" }, "repository": { "type": "git", @@ -27,39 +32,39 @@ "url": "https://github.com/RealOrangeOne/react-native-mock/issues" }, "homepage": "https://github.com/RealOrangeOne/react-native-mock#readme", - "devDependencies": { - "babel-cli": "6.9.0", - "babel-core": "6.9.0", - "babel-eslint": "6.0.4", - "babel-preset-airbnb": "2.0.0", - "babel-preset-react-native": "1.8.0", - "chai": "3.5.0", - "eslint": "2.10.2", - "eslint-config-airbnb": "9.0.1", - "eslint-plugin-import": "1.8.0", - "eslint-plugin-jsx-a11y": "1.2.2", - "eslint-plugin-react": "5.1.1", - "eslint-plugin-react-native": "1.0.2", - "mocha": "2.5.3", - "react": "^15.4.0", - "react-native": "^0.38.0" - }, "dependencies": { - "cubic-bezier": "^0.1.2", - "invariant": "^2.2.1", - "keymirror": "^0.1.1", - "raf": "^3.2.0", - "react-addons-create-fragment": "^15.4.0", - "react-addons-perf": "^15.4.0", - "react-addons-pure-render-mixin": "^15.4.0", - "react-addons-test-utils": "^15.4.0", - "react-addons-update": "^15.4.0", - "react-dom": "^15.4.0", - "react-timer-mixin": "^0.13.3", - "warning": "^2.1.0" + "glob": "7.1.2", + "mockery": "2.1.0", + "perfy": "1.1.2", + "promise": "8.0.1", + "regenerator-runtime": "0.11.0", + "sinon": "4.1.1", + "underscore": "1.8.3" + }, + "devDependencies": { + "babel-cli": "6.26.0", + "chai": "4.1.2", + "chai-as-promised": "7.1.1", + "enzyme": "3.1.1", + "enzyme-adapter-react-16": "1.0.4", + "eslint": "4.10.0", + "eslint-config-airbnb": "16.1.0", + "eslint-plugin-import": "2.8.0", + "eslint-plugin-jsx-a11y": "6.0.2", + "eslint-plugin-react": "7.4.0", + "eslint-plugin-react-native": "3.1.0", + "jsdom": "11.3.0", + "mocha": "4.0.1", + "mocha-assume": "1.0.0", + "nyc": "11.3.0", + "semver": "5.4.1", + "sinon-chai": "2.14.0" }, "peerDependencies": { - "react": "*", - "react-native": "*" + "babel-core": "*", + "babel-preset-react-native": "*", + "react": ">=16.0.0", + "react-dom": ">=16.0.0", + "react-native": ">=0.50.0" } } diff --git a/scripts/run_jest.sh b/scripts/run_jest.sh new file mode 100755 index 0000000..fd10479 --- /dev/null +++ b/scripts/run_jest.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# run Jest tests to check against speed and API coverage + +npm install jest@18.1.0 babel-jest@18.0.0 + +node_modules/.bin/jest tests/integration/**/*.test.js --runInBand --verbose + +exit 0 diff --git a/scripts/test-helper.js b/scripts/test-helper.js new file mode 100644 index 0000000..7830cb9 --- /dev/null +++ b/scripts/test-helper.js @@ -0,0 +1,33 @@ +const chai = require('chai'); +const sinonChai = require('sinon-chai'); +const chaiAsPromised = require('chai-as-promised'); +const jsdom = require('jsdom'); +const Enzyme = require('enzyme'); +const React16Adapter = require('enzyme-adapter-react-16'); + +Enzyme.configure({ adapter: new React16Adapter() }); + +chai.expect(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +// Jsdom document & window +const { JSDOM } = jsdom; +const dom = new JSDOM(''); +const win = dom.window; + +// Add to global +global.document = win.document; +global.window = win; + +// Add window keys to global window +Object.keys(window).forEach((key) => { // eslint-disable-line no-undef + if (!(key in global)) { + global[key] = window[key]; // eslint-disable-line no-undef + } +}); + +if (!global.navigator.userAgent || !global.window.navigator.userAgent) { + // react-dom needs a useragent + global.navigator.userAgent = global.window.navigator.userAgent = 'node.js'; +} diff --git a/src/Libraries/EventEmitter/EmitterSubscription.js b/src/Libraries/EventEmitter/EmitterSubscription.js deleted file mode 100644 index f7140e9..0000000 --- a/src/Libraries/EventEmitter/EmitterSubscription.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -const EventSubscription = require('./EventSubscription'); - -/** - * EmitterSubscription represents a subscription with listener and context data. - */ -class EmitterSubscription extends EventSubscription { - - /** - * @param {EventEmitter} emitter - The event emitter that registered this - * subscription - * @param {EventSubscriptionVendor} subscriber - The subscriber that controls - * this subscription - * @param {function} listener - Function to invoke when the specified event is - * emitted - * @param {*} context - Optional context object to use when invoking the - * listener - */ - constructor(emitter, subscriber, listener, context) { - super(subscriber); - this.emitter = emitter; - this.listener = listener; - this.context = context; - } - - /** - * Removes this subscription from the emitter that registered it. - * Note: we're overriding the `remove()` method of EventSubscription here - * but deliberately not calling `super.remove()` as the responsibility - * for removing the subscription lies with the EventEmitter. - */ - remove() { - this.emitter.removeSubscription(this); - } -} - -module.exports = EmitterSubscription; diff --git a/src/Libraries/EventEmitter/EventEmitter.js b/src/Libraries/EventEmitter/EventEmitter.js deleted file mode 100644 index 315f575..0000000 --- a/src/Libraries/EventEmitter/EventEmitter.js +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -const EmitterSubscription = require('./EmitterSubscription'); -const EventSubscriptionVendor = require('./EventSubscriptionVendor'); -const invariant = require('invariant'); - -/** - * @class EventEmitter - * @description - * An EventEmitter is responsible for managing a set of listeners and publishing - * events to them when it is told that such events happened. In addition to the - * data for the given event it also sends a event control object which allows - * the listeners/handlers to prevent the default behavior of the given event. - * - * The emitter is designed to be generic enough to support all the different - * contexts in which one might want to emit events. It is a simple multicast - * mechanism on top of which extra functionality can be composed. For example, a - * more advanced emitter may use an EventHolder and EventFactory. - */ -class EventEmitter { - /** - * @constructor - * - * @param {EventSubscriptionVendor} subscriber - Optional subscriber instance - * to use. If omitted, a new subscriber will be created for the emitter. - */ - constructor(subscriber) { - this._subscriber = subscriber || new EventSubscriptionVendor(); - } - - /** - * Adds a listener to be invoked when events of the specified type are - * emitted. An optional calling context may be provided. The data arguments - * emitted will be passed to the listener function. - * - * TODO: Annotate the listener arg's type. This is tricky because listeners - * can be invoked with varargs. - * - * @param {string} eventType - Name of the event to listen to - * @param {function} listener - Function to invoke when the specified event is - * emitted - * @param {*} context - Optional context object to use when invoking the - * listener - */ - addListener(eventType, listener, context) { - return (this._subscriber.addSubscription( - eventType, - new EmitterSubscription(this, this._subscriber, listener, context) - )); - } - - /** - * Similar to addListener, except that the listener is removed after it is - * invoked once. - * - * @param {string} eventType - Name of the event to listen to - * @param {function} listener - Function to invoke only once when the - * specified event is emitted - * @param {*} context - Optional context object to use when invoking the - * listener - */ - once(eventType, listener, context) { - return this.addListener(eventType, (...args) => { - this.removeCurrentListener(); - listener.apply(context, args); - }); - } - - /** - * Removes all of the registered listeners, including those registered as - * listener maps. - * - * @param {?string} eventType - Optional name of the event whose registered - * listeners to remove - */ - removeAllListeners(eventType) { - this._subscriber.removeAllSubscriptions(eventType); - } - - /** - * Provides an API that can be called during an eventing cycle to remove the - * last listener that was invoked. This allows a developer to provide an event - * object that can remove the listener (or listener map) during the - * invocation. - * - * If it is called when not inside of an emitting cycle it will throw. - * - * @throws {Error} When called not during an eventing cycle - * - * @example - * var subscription = emitter.addListenerMap({ - * someEvent: function(data, event) { - * console.log(data); - * emitter.removeCurrentListener(); - * } - * }); - * - * emitter.emit('someEvent', 'abc'); // logs 'abc' - * emitter.emit('someEvent', 'def'); // does not log anything - */ - removeCurrentListener() { - invariant( - !!this._currentSubscription, - 'Not in an emitting cycle; there is no current subscription' - ); - this.removeSubscription(this._currentSubscription); - } - - /** - * Removes a specific subscription. Called by the `remove()` method of the - * subscription itself to ensure any necessary cleanup is performed. - */ - removeSubscription(subscription) { - invariant( - subscription.emitter === this, - 'Subscription does not belong to this emitter.' - ); - this._subscriber.removeSubscription(subscription); - } - - /** - * Returns an array of listeners that are currently registered for the given - * event. - * - * @param {string} eventType - Name of the event to query - * @returns {array} - */ - listeners(eventType) { - const subscriptions = (this._subscriber.getSubscriptionsForType(eventType)); - return subscriptions ? subscriptions.map(subscription => subscription.listener) : []; - } - - /** - * Emits an event of the given type with the given data. All handlers of that - * particular type will be notified. - * - * @param {string} eventType - Name of the event to emit - * @param {...*} Arbitrary arguments to be passed to each registered listener - * - * @example - * emitter.addListener('someEvent', function(message) { - * console.log(message); - * }); - * - * emitter.emit('someEvent', 'abc'); // logs 'abc' - */ - emit(eventType) { - const subscriptions = (this._subscriber.getSubscriptionsForType(eventType)); - if (subscriptions) { - for (let i = 0, l = subscriptions.length; i < l; i++) { - const subscription = subscriptions[i]; - - // The subscription may have been removed during this event loop. - if (subscription) { - this._currentSubscription = subscription; - subscription.listener.apply( - subscription.context, - Array.prototype.slice.call(arguments, 1) - ); - } - } - this._currentSubscription = null; - } - } - - /** - * Removes the given listener for event of specific type. - * - * @param {string} eventType - Name of the event to emit - * @param {function} listener - Function to invoke when the specified event is - * emitted - * - * @example - * emitter.removeListener('someEvent', function(message) { - * console.log(message); - * }); // removes the listener if already registered - * - */ - removeListener(eventType, listener) { - const subscriptions = (this._subscriber.getSubscriptionsForType(eventType)); - if (subscriptions) { - for (let i = 0, l = subscriptions.length; i < l; i++) { - const subscription = subscriptions[i]; - - // The subscription may have been removed during this event loop. - // its listener matches the listener in method parameters - if (subscription && subscription.listener === listener) { - subscription.remove(); - } - } - } - } -} - -module.exports = EventEmitter; diff --git a/src/Libraries/EventEmitter/EventSubscription.js b/src/Libraries/EventEmitter/EventSubscription.js deleted file mode 100644 index ea45450..0000000 --- a/src/Libraries/EventEmitter/EventSubscription.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/** - * EventSubscription represents a subscription to a particular event. It can - * remove its own subscription. - */ -class EventSubscription { - /** - * @param {EventSubscriptionVendor} subscriber the subscriber that controls - * this subscription. - */ - constructor(subscriber) { - this.subscriber = subscriber; - } - - /** - * Removes this subscription from the subscriber that controls it. - */ - remove() { - this.subscriber.removeSubscription(this); - } -} - -module.exports = EventSubscription; diff --git a/src/Libraries/EventEmitter/EventSubscriptionVendor.js b/src/Libraries/EventEmitter/EventSubscriptionVendor.js deleted file mode 100644 index 4c18c3c..0000000 --- a/src/Libraries/EventEmitter/EventSubscriptionVendor.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -const invariant = require('invariant'); - -/** - * EventSubscriptionVendor stores a set of EventSubscriptions that are - * subscribed to a particular event type. - */ -class EventSubscriptionVendor { - - constructor() { - this._subscriptionsForType = {}; - this._currentSubscription = null; - } - - /** - * Adds a subscription keyed by an event type. - * - * @param {string} eventType - * @param {EventSubscription} subscription - */ - addSubscription(eventType, subscription) { - /* eslint-disable no-param-reassign */ - invariant( - subscription.subscriber === this, - 'The subscriber of the subscription is incorrectly set.'); - if (!this._subscriptionsForType[eventType]) { - this._subscriptionsForType[eventType] = []; - } - const key = this._subscriptionsForType[eventType].length; - this._subscriptionsForType[eventType].push(subscription); - subscription.eventType = eventType; - subscription.key = key; - return subscription; - } - - /** - * Removes a bulk set of the subscriptions. - * - * @param {?string} eventType - Optional name of the event type whose - * registered supscriptions to remove, if null remove all subscriptions. - */ - removeAllSubscriptions(eventType) { - if (eventType === undefined) { - this._subscriptionsForType = {}; - } else { - delete this._subscriptionsForType[eventType]; - } - } - - /** - * Removes a specific subscription. Instead of calling this function, call - * `subscription.remove()` directly. - * - * @param {object} subscription - */ - removeSubscription(subscription) { - const eventType = subscription.eventType; - const key = subscription.key; - - const subscriptionsForType = this._subscriptionsForType[eventType]; - if (subscriptionsForType) { - delete subscriptionsForType[key]; - } - } - - /** - * Returns the array of subscriptions that are currently registered for the - * given event type. - * - * Note: This array can be potentially sparse as subscriptions are deleted - * from it when they are removed. - * - * TODO: This returns a nullable array. wat? - * - * @param {string} eventType - * @returns {?array} - */ - getSubscriptionsForType(eventType) { - return this._subscriptionsForType[eventType]; - } -} - -module.exports = EventSubscriptionVendor; diff --git a/src/Libraries/EventEmitter/NativeEventEmitter.js b/src/Libraries/EventEmitter/NativeEventEmitter.js deleted file mode 100644 index 5fd9339..0000000 --- a/src/Libraries/EventEmitter/NativeEventEmitter.js +++ /dev/null @@ -1,29 +0,0 @@ -const invariant = require('invariant'); -const EventEmitter = require('./EventEmitter'); -const EmitterSubscription = require('./EmitterSubscription'); -const EventSubscriptionVender = require('./EventSubscriptionVendor'); - -const sharedSubscriber = new EventSubscriptionVender(); - -class NativeEventEmitter extends EventEmitter { - constructor(nativeModule) { - super(sharedSubscriber); - invariant(nativeModule, 'Native module cannot be null.'); - this._nativeModule = nativeModule; - } - - addListener(eventType, listener, context) { - return super.addListener(eventType, listener, context); - } - - removeAllListeners(eventType: string) { - invariant(eventType, 'eventType argument is required.'); - super.removeAllListeners(eventType); - } - - removeSubscription(subscription: EmitterSubscription) { - super.removeSubscription(subscription); - } -} - -module.exports = NativeEventEmitter; diff --git a/src/Libraries/NavigationExperimental/NavigationCard.js b/src/Libraries/NavigationExperimental/NavigationCard.js deleted file mode 100644 index 01563d6..0000000 --- a/src/Libraries/NavigationExperimental/NavigationCard.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; - -class CardStackPanResponder { -} - -class PagerPanResponder { -} - -class NavigationCard extends React.Component { - static CardStackPanResponder = CardStackPanResponder; - static CardStackStyleInterpolator = { - forHorizontal: () => ({}), - forVertical: () => ({}), - }; - static PagerPanResponder = PagerPanResponder; - static PagerStyleInterpolator = { - forHorizontal: () => ({}), - }; -} - -module.exports = NavigationCard; diff --git a/src/Libraries/NavigationExperimental/NavigationPropTypes.js b/src/Libraries/NavigationExperimental/NavigationPropTypes.js deleted file mode 100644 index 8caa73e..0000000 --- a/src/Libraries/NavigationExperimental/NavigationPropTypes.js +++ /dev/null @@ -1,98 +0,0 @@ -class Animated {} - -import React from 'react'; - -const { PropTypes } = React; - -/* NavigationAction */ -const action = PropTypes.shape({ - type: PropTypes.string.isRequired, -}); - -/* NavigationAnimatedValue */ -const animatedValue = PropTypes.instanceOf(Animated.Value); - -/* NavigationRoute */ -const navigationRoute = PropTypes.shape({ - key: PropTypes.string.isRequired, -}); - -/* navigationRoute */ -const navigationState = PropTypes.shape({ - index: PropTypes.number.isRequired, - routes: PropTypes.arrayOf(navigationRoute), -}); - -/* NavigationLayout */ -const layout = PropTypes.shape({ - height: animatedValue, - initHeight: PropTypes.number.isRequired, - initWidth: PropTypes.number.isRequired, - isMeasured: PropTypes.bool.isRequired, - width: animatedValue, -}); - -/* NavigationScene */ -const scene = PropTypes.shape({ - index: PropTypes.number.isRequired, - isStale: PropTypes.bool.isRequired, - key: PropTypes.string.isRequired, - route: navigationRoute.isRequired, -}); - -/* NavigationSceneRendererProps */ -const SceneRendererProps = { - layout: layout.isRequired, - navigationState: navigationState.isRequired, - position: animatedValue.isRequired, - progress: animatedValue.isRequired, - scene: scene.isRequired, - scenes: PropTypes.arrayOf(scene).isRequired, -}; - -const SceneRenderer = PropTypes.shape(SceneRendererProps); - -/* NavigationPanPanHandlers */ -const panHandlers = PropTypes.shape({ - onMoveShouldSetResponder: PropTypes.func.isRequired, - onMoveShouldSetResponderCapture: PropTypes.func.isRequired, - onResponderEnd: PropTypes.func.isRequired, - onResponderGrant: PropTypes.func.isRequired, - onResponderMove: PropTypes.func.isRequired, - onResponderReject: PropTypes.func.isRequired, - onResponderRelease: PropTypes.func.isRequired, - onResponderStart: PropTypes.func.isRequired, - onResponderTerminate: PropTypes.func.isRequired, - onResponderTerminationRequest: PropTypes.func.isRequired, - onStartShouldSetResponder: PropTypes.func.isRequired, - onStartShouldSetResponderCapture: PropTypes.func.isRequired, -}); - -/** - * Helper function that extracts the props needed for scene renderer. - */ -function extractSceneRendererProps(props) { - return { - layout: props.layout, - navigationState: props.navigationState, - position: props.position, - progress: props.progress, - scene: props.scene, - scenes: props.scenes, - }; -} - -module.exports = { - // helpers - extractSceneRendererProps, - - // Bundled propTypes. - SceneRendererProps, - - // propTypes - SceneRenderer, - action, - navigationState, - navigationRoute, - panHandlers, -}; diff --git a/src/Libraries/NavigationExperimental/NavigationStateUtils.js b/src/Libraries/NavigationExperimental/NavigationStateUtils.js deleted file mode 100644 index d0c2f50..0000000 --- a/src/Libraries/NavigationExperimental/NavigationStateUtils.js +++ /dev/null @@ -1,123 +0,0 @@ -function get(state, key) { - return state.routes.find(route => route.key === key) || null; -} - -function indexOf(state, key) { - return state.routes.map(route => route.key).indexOf(key); -} - -function has(state, key) { - return !!state.routes.some(route => route.key === key); -} - -function push(state, route) { - if (indexOf(state, route.key) !== -1) { - throw new Error('should not push route with duplicated key ' + route.key); - } - - const routes = [ - ...state.routes, - route, - ]; - - return { - ...state, - index: routes.length - 1, - routes, - }; -} - -function pop(state) { - if (state.index <= 0) { - return state; - } - const routes = state.routes.slice(0, -1); - return { - ...state, - index: routes.length - 1, - routes, - }; -} - -function jumpToIndex(state, index: number) { - if (index === state.index) { - return state; - } - - if (!state.routes[index]) { - throw new Error('invalid index ' + index + ' to jump to'); - } - - return { - ...state, - index, - }; -} - - -function jumpTo(state, key) { - const index = indexOf(state, key); - return jumpToIndex(state, index); -} - -function replaceAtIndex(state, index, route) { - if (!state.routes[index]) { - throw new Error('invalid index ' + index + ' for replacing route ' + route.key); - } - - if (state.routes[index] === route) { - return state; - } - - const routes = state.routes.slice(); - routes[index] = route; - - return { - ...state, - index, - routes, - }; -} - -function replaceAt(state, key, route) { - const index = indexOf(state, key); - return replaceAtIndex(state, index, route); -} - -function reset(state, routes, index = null) { - if (!routes.length && Array.isArray(routes)) { - throw new Error('invalid routes to replace'); - } - - const nextIndex = index === undefined ? routes.length - 1 : index; - - if (state.routes.length === routes.length && state.index === nextIndex) { - const compare = (route, ii) => routes[ii] === route; - if (state.routes.every(compare)) { - return state; - } - } - - if (!routes[nextIndex]) { - throw new Error('invalid index ' + nextIndex + ' to reset'); - } - - return { - ...state, - index: nextIndex, - routes, - }; -} - -module.exports = { - get, - has, - indexOf, - jumpTo, - jumpToIndex, - pop, - push, - replaceAt, - replaceAtIndex, - reset, -}; diff --git a/src/Libraries/NavigationExperimental/index.js b/src/Libraries/NavigationExperimental/index.js deleted file mode 100644 index 303cb39..0000000 --- a/src/Libraries/NavigationExperimental/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @see https://github.com/facebook/react-native/blob/master/Libraries/NavigationExperimental/NavigationExperimental.js - */ -import createMockComponent from '../../components/createMockComponent'; -import StateUtils from './NavigationStateUtils'; -import Card from './NavigationCard'; -import PropTypes from './NavigationPropTypes'; - -module.exports = { - StateUtils, - - AnimatedView: createMockComponent('NavigationAnimatedView'), - Transitioner: createMockComponent('NavigationTransitioner'), - - Card, - CardStack: createMockComponent('NavigationCardStack'), - Header: createMockComponent('NavigationHeader'), - - PropTypes, -}; diff --git a/src/Libraries/Network/FormData.js b/src/Libraries/Network/FormData.js deleted file mode 100644 index 6705ccd..0000000 --- a/src/Libraries/Network/FormData.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule FormData - * @flow - */ - -type FormDataValue = any; -type FormDataNameValuePair = [string, FormDataValue]; - -type Headers = {[name: string]: string}; -type FormDataPart = { - string: string, - headers: Headers, -} | { - uri: string, - headers: Headers, - name?: string, - type?: string, -}; - -/** - * Polyfill for XMLHttpRequest2 FormData API, allowing multipart POST requests - * with mixed data (string, native files) to be submitted via XMLHttpRequest. - * - * Example: - * - * var photo = { - * uri: uriFromCameraRoll, - * type: 'image/jpeg', - * name: 'photo.jpg', - * }; - * - * var body = new FormData(); - * body.append('authToken', 'secret'); - * body.append('photo', photo); - * body.append('title', 'A beautiful photo!'); - * - * xhr.open('POST', serverURL); - * xhr.send(body); - */ -class FormData { - _parts: Array; - - constructor() { - this._parts = []; - } - - append(key: string, value: FormDataValue) { - // The XMLHttpRequest spec doesn't specify if duplicate keys are allowed. - // MDN says that any new values should be appended to existing values. - // In any case, major browsers allow duplicate keys, so that's what we'll do - // too. They'll simply get appended as additional form data parts in the - // request body, leaving the server to deal with them. - this._parts.push([key, value]); - } - - getParts(): Array { - return this._parts.map(([name, value]) => { - const contentDisposition = 'form-data; name="' + name + '"'; - - const headers: Headers = { 'content-disposition': contentDisposition }; - - // The body part is a "blob", which in React Native just means - // an object with a `uri` attribute. Optionally, it can also - // have a `name` and `type` attribute to specify filename and - // content type (cf. web Blob interface.) - if (typeof value === 'object') { - if (typeof value.name === 'string') { - headers['content-disposition'] += '; filename="' + value.name + '"'; - } - if (typeof value.type === 'string') { - headers['content-type'] = value.type; - } - return { ...value, headers, fieldName: name }; - } - // Convert non-object values to strings as per FormData.append() spec - return { string: String(value), headers, fieldName: name }; - }); - } -} - -module.exports = FormData; diff --git a/src/Libraries/Network/Headers.js b/src/Libraries/Network/Headers.js deleted file mode 100644 index daef279..0000000 --- a/src/Libraries/Network/Headers.js +++ /dev/null @@ -1,54 +0,0 @@ -class Headers { - _headers: Object; - - constructor() { - this._headers = []; - } - - append(name: string, value: string) { - const normalName: string = name.toLowerCase(); - this._headers.push({ name: normalName, value }); - } - - delete(name: string) { - const normalName: string = name.toLowerCase(); - this._headers = this._headers.filter((pair) => pair.name !== normalName); - } - - entries() { - return this._headers.entries(); - } - - get(name: string) { - const normalName: string = name.toLowerCase(); - const header = this._headers.find((pair) => pair.name === normalName); - return header ? header.value : undefined; - } - - getAll(name: string) { - const normalName: string = name.toLowerCase(); - const headers = this._headers.filter((pair) => pair.name === normalName); - return headers.map((pair) => pair.value); - } - - has(name: string) { - const normalName: string = name.toLowerCase(); - return this.get(normalName); - } - - keys() { - return this._headers.map((pair) => pair.name); - } - - set(name: string, value: string) { - const normalName: string = name.toLowerCase(); - this.delete(normalName); - this.append(normalName, value); - } - - values() { - return this._headers.map((pair) => pair.value); - } -} - -module.exports = Headers; diff --git a/src/Libraries/Network/Response.js b/src/Libraries/Network/Response.js deleted file mode 100644 index d8390b3..0000000 --- a/src/Libraries/Network/Response.js +++ /dev/null @@ -1,25 +0,0 @@ -class Response { - _status: number; - _headers: Headers; - _body: string; - - constructor() { - this._status = 200; - this._headers = new Headers(); - this._body = ''; - } - - get status(): number { - return this._status; - } - - get headers(): Headers { - return this._headers; - } - - get body(): string { - return this._body; - } -} - -module.exports = Response; diff --git a/src/Libraries/Network/XMLHttpRequest.js b/src/Libraries/Network/XMLHttpRequest.js deleted file mode 100644 index 581edc7..0000000 --- a/src/Libraries/Network/XMLHttpRequest.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule XMLHttpRequest - * @flow - */ - -const UNSENT = 0; -const OPENED = 1; -const HEADERS_RECEIVED = 2; -const LOADING = 3; -const DONE = 4; - -class XMLHttpRequest { - static UNSENT: number = UNSENT; - static OPENED: number = OPENED; - static HEADERS_RECEIVED: number = HEADERS_RECEIVED; - static LOADING: number = LOADING; - static DONE: number = DONE; - - UNSENT: number = UNSENT; - OPENED: number = OPENED; - HEADERS_RECEIVED: number = HEADERS_RECEIVED; - LOADING: number = LOADING; - DONE: number = DONE; - - onload: ?Function; - onloadstart: ?Function; - onprogress: ?Function; - ontimeout: ?Function; - onerror: ?Function; - onloadend: ?Function; - onreadystatechange: ?Function; - - readyState: number = UNSENT; - responseHeaders: ?Object; - status: number = 0; - timeout: number = 0; - responseURL: ?string; - - upload: { - addEventListener: ?Function; - }; - - open(method: string, url: string, async: ?boolean): void { - } - - send(data: any): void { - } - - abort(): void { - } -} - -module.exports = XMLHttpRequest; diff --git a/src/NativeModules.js b/src/NativeModules.js new file mode 100644 index 0000000..39cc84f --- /dev/null +++ b/src/NativeModules.js @@ -0,0 +1,172 @@ +import sinon from 'sinon'; + +const ReactNativeVersion = require('react-native/Libraries/Core/ReactNativeVersion'); + +module.exports = { + AlertManager: { + alertWithArgs: sinon.spy() + }, + AppState: { + addEventListener: sinon.spy(), + removeEventListener: sinon.spy() + }, + AsyncLocalStorage: { + clear: sinon.spy(), + getItem: sinon.spy(), + multiGet: sinon.spy(), + removeItem: sinon.spy(), + setItem: sinon.spy() + }, + BuildInfo: { + appVersion: '0', + buildVersion: '0' + }, + Clipboard: { + setString: sinon.spy(), + getString: sinon.spy() + }, + DataManager: { + queryData: sinon.spy() + }, + FacebookSDK: { + login: sinon.spy(), + logout: sinon.spy(), + queryGraphPath: sinon.spy((path, method, params, callback) => callback()) + }, + FbRelayNativeAdapter: { + updateCLC: sinon.spy() + }, + GraphPhotoUpload: { + upload: sinon.spy() + }, + I18n: { + translationsDictionary: JSON.stringify({ + 'Good bye, {name}!|Bye message': '\u{00A1}Adi\u{00F3}s {name}!' + }) + }, + ImageLoader: { + getSize: sinon.spy((uri, success) => process.nextTick(() => success(320, 240))), + prefetchImage: sinon.spy() + }, + ImagePickerIOS: { + canRecordVideos: sinon.spy(callback => callback()), + canUseCamera: sinon.spy(callback => callback()), + openCameraDialog: sinon.spy(), + openSelectDialog: sinon.spy() + + }, + ImageViewManager: { + getSize: sinon.spy((uri, success) => process.nextTick(() => success(320, 240))), + prefetchImage: sinon.spy() + }, + KeyboardObserver: { + addListener: sinon.spy(), + removeListeners: sinon.spy() + }, + IntentAndroid: { + openURL: sinon.spy(), + canOpenURL: sinon.spy(url => new Promise(resolve => resolve(true))) + }, + LinkingManager: { + openURL: sinon.spy(), + canOpenURL: sinon.spy(url => new Promise(resolve => resolve(true))) + }, + ModalFullscreenViewManager: {}, + Networking: { + sendRequest: sinon.spy(), + abortRequest: sinon.spy(), + addListener: sinon.spy(), + removeListeners: sinon.spy() + }, + LocationObserver: { + getCurrentPosition: sinon.spy(), + startObserving: sinon.spy(), + stopObserving: sinon.spy() + }, + SourceCode: { + scriptURL: null + }, + StatusBarManager: { + setColor: sinon.spy(), + setStyle: sinon.spy(), + setHidden: sinon.spy(), + setNetworkActivityIndicatorVisible: sinon.spy(), + setBackgroundColor: sinon.spy(), + setTranslucent: sinon.spy() + }, + Timing: { + createTimer: sinon.spy(), + deleteTimer: sinon.spy() + }, + UIManager: { + createView: sinon.spy(), + removeRootView: sinon.spy(), + setChildren: sinon.spy(), + manageChildren: sinon.spy(), + updateView: sinon.spy(), + removeSubviewsFromContainerWithID: sinon.spy(), + replaceExistingNonRootView: sinon.spy(), + customBubblingEventTypes: {}, + customDirectEventTypes: {}, + Dimensions: { + window: { + fontScale: 2, + height: 1334, + scale: 2, + width: 750 + } + }, + ModalFullscreenView: { + Constants: {} + }, + ScrollView: { + Constants: {} + }, + View: { + Constants: {} + }, + AndroidDrawerLayout: { + Constants: { + DrawerPosition: { + Right: 'RIGHT', + Left: 'LEFT' + } + } + } + }, + WebSocketModule: { + connect: sinon.spy(), + send: sinon.spy(), + sendBinary: sinon.spy(), + ping: sinon.spy(), + close: sinon.spy(), + addListener: sinon.spy(), + removeListeners: sinon.spy() + }, + PlatformConstants: { // https://github.com/facebook/react-native/pull/11651 + osVersion: '10', + interfaceIdiom: 'pad', + isTesting: true, + version: '7', + reactNativeVersion: ReactNativeVersion.version + }, + AndroidConstants: { + Version: '7', + isTesting: true + }, + IOSConstants: { + osVersion: '10', + interfaceIdiom: 'pad', + isTesting: true + }, + DeviceInfo: { + Dimensions: { + windowPhysicalPixels: { + fontScale: 2, + height: 1334, + scale: 2, + width: 750 + } + } + } +}; diff --git a/src/NativeModules/ActionSheetManager.js b/src/NativeModules/ActionSheetManager.js deleted file mode 100644 index 24c3a2f..0000000 --- a/src/NativeModules/ActionSheetManager.js +++ /dev/null @@ -1,11 +0,0 @@ - -const ActionSheetManager = { - showActionSheetWithOptions(options, callback) { - - }, - showShareActionSheetWithOptions(options, failure, success) { - - }, -}; - -module.exports = ActionSheetManager; diff --git a/src/NativeModules/AlertManager.js b/src/NativeModules/AlertManager.js deleted file mode 100644 index 3a1ea6b..0000000 --- a/src/NativeModules/AlertManager.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/React/Modules/RCTAlertManager.m - */ -const AlertManager = { - alertWithArgs(args, callback) { - - }, -}; - -module.exports = AlertManager; diff --git a/src/NativeModules/AppState.js b/src/NativeModules/AppState.js deleted file mode 100644 index dfd705b..0000000 --- a/src/NativeModules/AppState.js +++ /dev/null @@ -1,19 +0,0 @@ -import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; - -let _appState = 'active'; - -DeviceEventEmitter.on('appStateDidChange', data => { - _appState = data._appState; -}); - -const AppState = { - getCurrentAppState(callback, error) { - Promise.resolve({ _appState }).then(callback); - }, - - __setAppState(appState) { - DeviceEventEmitter.emit('appStateDidChange', { _appState: appState }); - }, -}; - -module.exports = AppState; diff --git a/src/NativeModules/CameraRollManager.js b/src/NativeModules/CameraRollManager.js deleted file mode 100644 index 9780c26..0000000 --- a/src/NativeModules/CameraRollManager.js +++ /dev/null @@ -1,13 +0,0 @@ - -const CameraRollManager = { - saveImageWithTag(imageTag) { - return Promise.resolve(['/asset/url']); - }, - getPhotos(params) { - return Promise.resolve([ - // TODO(lmr): - ]); - }, -}; - -module.exports = CameraRollManager; diff --git a/src/NativeModules/Clipboard.js b/src/NativeModules/Clipboard.js deleted file mode 100644 index ee1d8fe..0000000 --- a/src/NativeModules/Clipboard.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Components/Clipboard/Clipboard.js - */ -let _content = null; - -const Clipboard = { - getString() { - return Promise.resolve(_content); - }, - - setString(content) { - _content = content; - }, -}; - -module.exports = Clipboard; diff --git a/src/NativeModules/DatePickerAndroid.js b/src/NativeModules/DatePickerAndroid.js deleted file mode 100644 index 3b9bb67..0000000 --- a/src/NativeModules/DatePickerAndroid.js +++ /dev/null @@ -1,8 +0,0 @@ -// TODO(lmr): figure out a good way to have separate responses like "dismissed" vs "set". -const DatePickerAndroid = { - open(options) { - return Promise.resolve().then({ action: 'dismissedAction' }); - }, -}; - -module.exports = DatePickerAndroid; diff --git a/src/NativeModules/DeviceEventManager.js b/src/NativeModules/DeviceEventManager.js deleted file mode 100644 index 1b7fc91..0000000 --- a/src/NativeModules/DeviceEventManager.js +++ /dev/null @@ -1,7 +0,0 @@ -const DeviceEventManager = { - invokeDefaultBackPressHandler() { - - }, -}; - -module.exports = DeviceEventManager; diff --git a/src/NativeModules/ImagePickerIOS.js b/src/NativeModules/ImagePickerIOS.js deleted file mode 100644 index e3c5c7d..0000000 --- a/src/NativeModules/ImagePickerIOS.js +++ /dev/null @@ -1,19 +0,0 @@ -const _canRecordVideos = true; -const _canUseCamera = true; - -const ImagePickerIOS = { - canRecordVideos(callback) { - Promise.resolve(_canRecordVideos).then(callback); - }, - canUseCamera(callback) { - Promise.resolve(_canUseCamera).then(callback); - }, - openCameraDialog(config, success, cancel) { - // TODO(lmr): - }, - openSelectDialog(config, success, cancel) { - // TODO(lmr): - }, -}; - -module.exports = ImagePickerIOS; diff --git a/src/NativeModules/LinkingManager.js b/src/NativeModules/LinkingManager.js deleted file mode 100644 index 805685f..0000000 --- a/src/NativeModules/LinkingManager.js +++ /dev/null @@ -1,15 +0,0 @@ -let _test = url => true; -const LinkingManger = { - openURL(url) { - return Promise.resolve(true); - }, - canOpenURL(url) { - return Promise.resolve(_test(url)); - }, - - __setCanOpenURLTest(test) { - _test = test; - } -}; - -module.exports = LinkingManger; diff --git a/src/NativeModules/ScrollViewManager.js b/src/NativeModules/ScrollViewManager.js deleted file mode 100644 index 761fee1..0000000 --- a/src/NativeModules/ScrollViewManager.js +++ /dev/null @@ -1,29 +0,0 @@ - -const ScrollViewManager = { - getContentSize(reactTag, callback) { - Promise.resolve().then(() => callback({ - width: 20, - height: 20, - })); - }, - calculateChildFrames(reactTag, callback) { - Promise.resolve().then(() => callback({ - // TODO(lmr): - })); - }, - endRefreshing(reactTag) { - - }, - scrollTo(reactTag, offset, animated) { - - }, - zoomToRect(reactTag, rect, animated) { - - }, - DecelerationRate: { - normal: 0, - fast: 1, - }, -}; - -module.exports = ScrollViewManager; diff --git a/src/NativeModules/SourceCode.js b/src/NativeModules/SourceCode.js deleted file mode 100644 index a420ce9..0000000 --- a/src/NativeModules/SourceCode.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/React/Modules/RCTSourceCode.m - */ -let _sourceCode = null; - -const SourceCode = { - getScriptText() { - return _sourceCode - ? Promise.resolve(_sourceCode) - : Promise.reject(new Error('Source code is not available')); - }, - __setScriptText(url, text) { - _sourceCode = !!url && !!text - ? { url, text } - : null; - }, -}; - -module.exports = SourceCode; diff --git a/src/NativeModules/TestModule.js b/src/NativeModules/TestModule.js deleted file mode 100644 index fb1bbe2..0000000 --- a/src/NativeModules/TestModule.js +++ /dev/null @@ -1,24 +0,0 @@ -import NativeAppEventEmitter from '../plugins/NativeAppEventEmitter'; - -const TestModule = { - verifySnapshot(callback) { - Promise.resolve().then(() => callback(true)); - }, - sendAppEvent(name, body) { - NativeAppEventEmitter.emit(name, body); - }, - shouldResolve() { - return Promise.resolve(1); - }, - shouldReject() { - return Promise.reject(null); - }, - markTestCompleted() { - - }, - markTestPassed(success) { - - }, -}; - -module.exports = TestModule; diff --git a/src/NativeModules/TimePickerAndroid.js b/src/NativeModules/TimePickerAndroid.js deleted file mode 100644 index 1b61601..0000000 --- a/src/NativeModules/TimePickerAndroid.js +++ /dev/null @@ -1,14 +0,0 @@ -// TODO(lmr): figure out a good way to toggle between timeSetAction and dismissedAction -let _resolver = () => ({ action: 'timeSetAction', hour: 2, minute: 30 }); -const TimePickerAndroid = { - open(options) { - const result = _resolver(options) || { action: 'dismissedAction' }; - return Promise.resolve(result); - }, - - __setResolverFunction(resolver) { - _resolver = resolver; - } -}; - -module.exports = TimePickerAndroid; diff --git a/src/NativeModules/Timing.js b/src/NativeModules/Timing.js deleted file mode 100644 index 1b2fa46..0000000 --- a/src/NativeModules/Timing.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/React/Modules/RCTTiming.m - */ -const Timing = { - createTimer(callbackId, duration, jsSchedulingTime, repeats) { - - }, - deleteTimer(timerId) { - - }, -}; - -module.exports = Timing; diff --git a/src/NativeModules/UIManager.js b/src/NativeModules/UIManager.js deleted file mode 100644 index a7be40a..0000000 --- a/src/NativeModules/UIManager.js +++ /dev/null @@ -1,71 +0,0 @@ - -const UIManager = { - removeSubviewsFromContainerWithID(containerId) { - - }, - removeRootView(rootReactTag) { - - }, - replaceExistingNonRootView(reactTag, newReactTag) { - - }, - setChildren(containerTag, reactTags) { - - }, - manageChildren( - containerReactTag, - moveFromIndices, - moveToIndices, - addChildReactTags, - addAtIndices, - removeAtIndices - ) { - - }, - createView(reactTag, viewName, rootTag, props) { - - }, - updateView(reactTag, viewName, props) { - - }, - focus(reactTag) { - - }, - blur(reactTag) { - - }, - findSubviewIn(reactTag, atPoint, callback) { - - }, - dispatchViewManagerCommand(reactTag, commandID, commandArgs) { - - }, - measure(reactTag, callback) { - - }, - measureLayout(reactTag, relativeTo, errorCallback, callback) { - - }, - measureLayoutRelativeToParent(reactTag, errorCallback, callback) { - - }, - measureViewsInRect(rect, parentView, errorCallback, callback) { - - }, - setJSResponder(reactTag, blockNativeResponder) { - - }, - clearJSResponder() { - - }, - configureNextLayoutAnimation(callback, errorCallback) { - - }, - AndroidDrawerLayout: { - Constants: { - DrawerPosition: { Left: 8388611, Right: 8388613 }, - }, - }, -}; - -module.exports = UIManager; diff --git a/src/NativeModules/Vibration.js b/src/NativeModules/Vibration.js deleted file mode 100644 index 8345c50..0000000 --- a/src/NativeModules/Vibration.js +++ /dev/null @@ -1,7 +0,0 @@ -const Vibration = { - vibrate() { - - }, -}; - -module.exports = Vibration; diff --git a/src/NativeModules/WebViewManager.js b/src/NativeModules/WebViewManager.js deleted file mode 100644 index 6c1ce39..0000000 --- a/src/NativeModules/WebViewManager.js +++ /dev/null @@ -1,26 +0,0 @@ - -const WebViewManager = { - goBack(reactTag) { - - }, - goForward(reactTag) { - - }, - reload(reactTag) { - - }, - startLoadWithResult(result, lockIdentifier) { - - }, - JSNavigationScheme: 'react-js-navigation', - NavigationType: { - LinkClicked: 0, - FormSubmitted: 1, - BackForward: 2, - Reload: 3, - FormResubmitted: 4, - Other: 5, - } -}; - -module.exports = WebViewManager; diff --git a/src/NativeModules/index.js b/src/NativeModules/index.js deleted file mode 100644 index 31b1c1f..0000000 --- a/src/NativeModules/index.js +++ /dev/null @@ -1,23 +0,0 @@ - -const NativeModules = { - Timing: require('./Timing'), - UIManager: require('./UIManager'), - AsyncLocalStorage: require('../api/AsyncStorage'), - SourceCode: require('./SourceCode'), - AlertManager: require('./AlertManager'), - Clipboard: require('./Clipboard'), - CameraRollManager: require('./CameraRollManager'), - TestModule: require('./TestModule'), - WebViewManager: require('./WebViewManager'), - ScrollViewManager: require('./ScrollViewManager'), - ActionSheetManager: require('./ActionSheetManager'), - AppState: require('./AppState'), - ImagePickerIOS: require('./ImagePickerIOS'), - DeviceEventManager: require('./DeviceEventManager'), - DatePickerAndroid: require('./DatePickerAndroid'), - LinkingManager: require('./LinkingManager'), - TimePickerAndroid: require('./TimePickerAndroid'), - Vibration: require('./Vibration'), -}; - -module.exports = NativeModules; diff --git a/src/api/ActionSheetIOS.js b/src/api/ActionSheetIOS.js deleted file mode 100644 index 88477dc..0000000 --- a/src/api/ActionSheetIOS.js +++ /dev/null @@ -1,46 +0,0 @@ -import ActionSheetManager from '../NativeModules/ActionSheetManager'; -import invariant from 'invariant'; -import processColor from '../plugins/processColor'; - -const ActionSheetIOS = { - showActionSheetWithOptions(options, callback) { - invariant( - typeof options === 'object' && options !== null, - 'Options must a valid object' - ); - invariant( - typeof callback === 'function', - 'Must provide a valid callback' - ); - ActionSheetManager.showActionSheetWithOptions( - { ...options, tintColor: processColor(options.tintColor) }, - callback - ); - }, - - showShareActionSheetWithOptions( - options, - failureCallback, - successCallback - ) { - invariant( - typeof options === 'object' && options !== null, - 'Options must a valid object' - ); - invariant( - typeof failureCallback === 'function', - 'Must provide a valid failureCallback' - ); - invariant( - typeof successCallback === 'function', - 'Must provide a valid successCallback' - ); - ActionSheetManager.showShareActionSheetWithOptions( - { ...options, tintColor: processColor(options.tintColor) }, - failureCallback, - successCallback - ); - } -}; - -module.exports = ActionSheetIOS; diff --git a/src/api/Alert.js b/src/api/Alert.js deleted file mode 100644 index ac2c918..0000000 --- a/src/api/Alert.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Utilities/Alert.js - */ -const Alert = { - alert(title, message, buttons, type) { - - }, -}; - -module.exports = Alert; diff --git a/src/api/AlertIOS.js b/src/api/AlertIOS.js deleted file mode 100644 index 9d86ad5..0000000 --- a/src/api/AlertIOS.js +++ /dev/null @@ -1,143 +0,0 @@ -import AlertManager from '../NativeModules/AlertManager'; -/** - * The AlertsIOS utility provides two functions: `alert` and `prompt`. All - * functionality available through `AlertIOS.alert` is also available in the - * cross-platform `Alert.alert`, which we recommend you use if you don't need - * iOS-specific functionality. - * - * `AlertIOS.prompt` allows you to prompt the user for input inside of an - * alert popup. - * - */ -class AlertIOS { - /** - * Creates a popup to alert the user. See - * [Alert](/react-native/docs/alert.html). - * - * - title: string -- The dialog's title. - * - message: string -- An optional message that appears above the text input. - * - callbackOrButtons -- This optional argument should be either a - * single-argument function or an array of buttons. If passed a function, - * it will be called when the user taps 'OK'. - * - * If passed an array of button configurations, each button should include - * a `text` key, as well as optional `onPress` and `style` keys. - * `style` should be one of 'default', 'cancel' or 'destructive'. - * - type -- *deprecated, do not use* - * - * Example: - * - * ``` - * AlertIOS.alert( - * 'Sync Complete', - * 'All your data are belong to us.' - * ); - * ``` - */ - static alert(title, message, callbackOrButtons, type) { - if (typeof type !== 'undefined') { - console.warn( - 'AlertIOS.alert() with a 4th "type" parameter is deprecated and will be removed. Use AlertIOS.prompt() instead.' - ); - this.prompt(title, message, callbackOrButtons, type); - return; - } - this.prompt(title, message, callbackOrButtons, 'default'); - } - - /** - * Prompt the user to enter some text. - * - * - title: string -- The dialog's title. - * - message: string -- An optional message that appears above the text input. - * - callbackOrButtons -- This optional argument should be either a - * single-argument function or an array of buttons. If passed a function, - * it will be called with the prompt's value when the user taps 'OK'. - * - * If passed an array of button configurations, each button should include - * a `text` key, as well as optional `onPress` and `style` keys (see example). - * `style` should be one of 'default', 'cancel' or 'destructive'. - * - type: string -- This configures the text input. One of 'plain-text', - * 'secure-text' or 'login-password'. - * - defaultValue: string -- the default value for the text field. - * - * Example with custom buttons: - * ``` - * AlertIOS.prompt( - * 'Enter password', - * 'Enter your password to claim your $1.5B in lottery winnings', - * [ - * {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'}, - * {text: 'OK', onPress: password => console.log('OK Pressed, password: ' + password)}, - * ], - * 'secure-text' - * ); - * ``` - * - * Example with the default button and a custom callback: - * ``` - * AlertIOS.prompt( - * 'Update username', - * null, - * text => console.log("Your username is "+text), - * null, - * 'default' - * ) - * ``` - */ - static prompt(title, message, callbackOrButtons, type, defaultValue) { - if (typeof type === 'function') { - const callback = type; - AlertManager.alertWithArgs({ - title: title || undefined, - type: 'plain-text', - message, - }, (id, value) => { - callback(value); - }); - return; - } - - let callbacks = []; - const buttons = []; - let cancelButtonKey; - let destructiveButtonKey; - if (typeof callbackOrButtons === 'function') { - callbacks = [callbackOrButtons]; - } else if (callbackOrButtons instanceof Array) { - callbackOrButtons.forEach((btn, index) => { - callbacks[index] = btn.onPress; - if (btn.style === 'cancel') { - cancelButtonKey = String(index); - } else if (btn.style === 'destructive') { - destructiveButtonKey = String(index); - } - if (btn.text || index < (callbackOrButtons || []).length - 1) { - const btnDef = {}; - btnDef[index] = btn.text || ''; - buttons.push(btnDef); - } - }); - } - - AlertManager.alertWithArgs( - { - title: title || undefined, - message: message || undefined, - buttons, - type: type || undefined, - defaultValue, - cancelButtonKey, - destructiveButtonKey, - }, - (id, value) => { - const cb = callbacks[id]; - if (cb) { - cb(value); - } - } - ); - } -} - -module.exports = AlertIOS; diff --git a/src/api/Animated/AnimatedImplementation.js b/src/api/Animated/AnimatedImplementation.js deleted file mode 100644 index 682d9ae..0000000 --- a/src/api/Animated/AnimatedImplementation.js +++ /dev/null @@ -1,1182 +0,0 @@ -import invariant from 'invariant'; -import Interpolation from './Interpolation'; -import Easing from './Easing'; -import InteractionManager from '../InteractionManager'; -import SpringConfig from './SpringConfig'; -import requestAnimationFrame from 'raf'; -import flattenStyle from '../../propTypes/flattenStyle'; - -class Animated { - __attach() {} - __detach() {} - __getValue() {} - __getAnimatedValue() { return this.__getValue(); } - __addChild(child) {} - __removeChild(child) {} - __getChildren() { return []; } -} - -class Animation { - start(fromValue, onUpdate, onEnd, previousAnimation) {} - stop() {} - __debouncedOnEnd(result) { - const onEnd = this.__onEnd; - this.__onEnd = null; - if (onEnd) { - onEnd(result); - } - } -} - -class AnimatedWithChildren extends Animated { - constructor() { - super(); - this._children = []; - } - - __addChild(child) { - if (this._children.length === 0) { - this.__attach(); - } - this._children.push(child); - } - - __removeChild(child) { - const index = this._children.indexOf(child); - if (index === -1) { - console.warn( - 'Trying to remove a child that doesn\'t exist' - ); - return; - } - this._children.splice(index, 1); - if (this._children.length === 0) { - this.__detach(); - } - } - - __getChildren() { - return this._children; - } -} - -/** - * Animated works by building a directed acyclic graph of dependencies - * transparently when you render your Animated components. - * - * new Animated.Value(0) - * .interpolate() .interpolate() new Animated.Value(1) - * opacity translateY scale - * style transform - * View#234 style - * View#123 - * - * A) Top Down phase - * When an Animated.Value is updated, we recursively go down through this - * graph in order to find leaf nodes: the views that we flag as needing - * an update. - * - * B) Bottom Up phase - * When a view is flagged as needing an update, we recursively go back up - * in order to build the new value that it needs. The reason why we need - * this two-phases process is to deal with composite props such as - * transform which can receive values from multiple parents. - */ -function _flush(rootNode) { - const animatedStyles = new Set(); - function findAnimatedStyles(node) { - if (typeof node.update === 'function') { - animatedStyles.add(node); - } else { - node.__getChildren().forEach(findAnimatedStyles); - } - } - findAnimatedStyles(rootNode); - animatedStyles.forEach(animatedStyle => animatedStyle.update()); -} - -const easeInOut = Easing.inOut(Easing.ease); - -class TimingAnimation extends Animation { - constructor(config) { - super(); - this._toValue = config.toValue; - this._easing = config.easing || easeInOut; - this._duration = config.duration !== undefined ? config.duration : 500; - this._delay = config.delay || 0; - this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true; - } - - start(fromValue, onUpdate, onEnd) { - this.__active = true; - this._fromValue = fromValue; - this._onUpdate = onUpdate; - this.__onEnd = onEnd; - - const start = () => { - if (this._duration === 0) { - this._onUpdate(this._toValue); - this.__debouncedOnEnd({ finished: true }); - } else { - this._startTime = Date.now(); - this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); - } - }; - if (this._delay) { - this._timeout = setTimeout(start, this._delay); - } else { - start(); - } - } - - onUpdate() { - const now = Date.now(); - if (now >= this._startTime + this._duration) { - if (this._duration === 0) { - this._onUpdate(this._toValue); - } else { - this._onUpdate( - this._fromValue + this._easing(1) * (this._toValue - this._fromValue) - ); - } - this.__debouncedOnEnd({ finished: true }); - return; - } - - this._onUpdate( - this._fromValue + - this._easing((now - this._startTime) / this._duration) * - (this._toValue - this._fromValue) - ); - if (this.__active) { - this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); - } - } - - stop() { - this.__active = false; - clearTimeout(this._timeout); - if (global && global.cancelAnimationFrame) { - global.cancelAnimationFrame(this._animationFrame); - } - this.__debouncedOnEnd({ finished: false }); - } -} - -class DecayAnimation extends Animation { - constructor(config) { - super(); - this._deceleration = config.deceleration || 0.998; - this._velocity = config.velocity; - this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true; - } - - start(fromValue, onUpdate, onEnd) { - this.__active = true; - this._lastValue = fromValue; - this._fromValue = fromValue; - this._onUpdate = onUpdate; - this.__onEnd = onEnd; - this._startTime = Date.now(); - this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); - } - - onUpdate() { - const now = Date.now(); - - const value = this._fromValue + - (this._velocity / (1 - this._deceleration)) * - (1 - Math.exp(-(1 - this._deceleration) * (now - this._startTime))); - - this._onUpdate(value); - - if (Math.abs(this._lastValue - value) < 0.1) { - this.__debouncedOnEnd({ finished: true }); - return; - } - - this._lastValue = value; - if (this.__active) { - this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); - } - } - - stop() { - this.__active = false; - if (global && global.cancelAnimationFrame) { - global.cancelAnimationFrame(this._animationFrame); - } - this.__debouncedOnEnd({ finished: false }); - } -} - -function withDefault(value, defaultValue) { - if (value === undefined || value === null) { - return defaultValue; - } - return value; -} - -class SpringAnimation extends Animation { - constructor(config) { - super(); - - this._overshootClamping = withDefault(config.overshootClamping, false); - this._restDisplacementThreshold = withDefault(config.restDisplacementThreshold, 0.001); - this._restSpeedThreshold = withDefault(config.restSpeedThreshold, 0.001); - this._initialVelocity = config.velocity; - this._lastVelocity = withDefault(config.velocity, 0); - this._toValue = config.toValue; - this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true; - - let springConfig; - if (config.bounciness !== undefined || config.speed !== undefined) { - invariant( - config.tension === undefined && config.friction === undefined, - 'You can only define bounciness/speed or tension/friction but not both' - ); - springConfig = SpringConfig.fromBouncinessAndSpeed( - withDefault(config.bounciness, 8), - withDefault(config.speed, 12), - ); - } else { - springConfig = SpringConfig.fromOrigamiTensionAndFriction( - withDefault(config.tension, 40), - withDefault(config.friction, 7), - ); - } - this._tension = springConfig.tension; - this._friction = springConfig.friction; - } - - start(fromValue, onUpdate, onEnd, previousAnimation) { - this.__active = true; - this._startPosition = fromValue; - this._lastPosition = this._startPosition; - - this._onUpdate = onUpdate; - this.__onEnd = onEnd; - this._lastTime = Date.now(); - - if (previousAnimation instanceof SpringAnimation) { - const internalState = previousAnimation.getInternalState(); - this._lastPosition = internalState.lastPosition; - this._lastVelocity = internalState.lastVelocity; - this._lastTime = internalState.lastTime; - } - if (this._initialVelocity !== undefined && this._initialVelocity !== null) { - this._lastVelocity = this._initialVelocity; - } - this.onUpdate(); - } - - getInternalState() { - return { - lastPosition: this._lastPosition, - lastVelocity: this._lastVelocity, - lastTime: this._lastTime, - }; - } - - onUpdate() { - let position = this._lastPosition; - let velocity = this._lastVelocity; - - let tempPosition = this._lastPosition; - let tempVelocity = this._lastVelocity; - - // If for some reason we lost a lot of frames (e.g. process large payload or - // stopped in the debugger), we only advance by 4 frames worth of - // computation and will continue on the next frame. It's better to have it - // running at faster speed than jumping to the end. - const MAX_STEPS = 64; - let now = Date.now(); - if (now > this._lastTime + MAX_STEPS) { - now = this._lastTime + MAX_STEPS; - } - - // We are using a fixed time step and a maximum number of iterations. - // The following post provides a lot of thoughts into how to build this - // loop: http://gafferongames.com/game-physics/fix-your-timestep/ - const TIMESTEP_MSEC = 1; - const numSteps = Math.floor((now - this._lastTime) / TIMESTEP_MSEC); - - for (let i = 0; i < numSteps; ++i) { - // Velocity is based on seconds instead of milliseconds - const step = TIMESTEP_MSEC / 1000; - - // This is using RK4. A good blog post to understand how it works: - // http://gafferongames.com/game-physics/integration-basics/ - const aVelocity = velocity; - const aAcceleration = - this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; - tempPosition = position + aVelocity * step / 2; - tempVelocity = velocity + aAcceleration * step / 2; - - const bVelocity = tempVelocity; - const bAcceleration = - this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; - tempPosition = position + bVelocity * step / 2; - tempVelocity = velocity + bAcceleration * step / 2; - - const cVelocity = tempVelocity; - const cAcceleration = - this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; - tempPosition = position + cVelocity * step / 2; - tempVelocity = velocity + cAcceleration * step / 2; - - const dVelocity = tempVelocity; - const dAcceleration = - this._tension * (this._toValue - tempPosition) - this._friction * tempVelocity; - tempPosition = position + cVelocity * step / 2; - tempVelocity = velocity + cAcceleration * step / 2; - - const dxdt = (aVelocity + 2 * (bVelocity + cVelocity) + dVelocity) / 6; - const dvdt = (aAcceleration + 2 * (bAcceleration + cAcceleration) + dAcceleration) / 6; - - position += dxdt * step; - velocity += dvdt * step; - } - - this._lastTime = now; - this._lastPosition = position; - this._lastVelocity = velocity; - - this._onUpdate(position); - if (!this.__active) { // a listener might have stopped us in _onUpdate - return; - } - - // Conditions for stopping the spring animation - let isOvershooting = false; - if (this._overshootClamping && this._tension !== 0) { - if (this._startPosition < this._toValue) { - isOvershooting = position > this._toValue; - } else { - isOvershooting = position < this._toValue; - } - } - - const isVelocity = Math.abs(velocity) <= this._restSpeedThreshold; - let isDisplacement = true; - if (this._tension !== 0) { - isDisplacement = Math.abs(this._toValue - position) <= this._restDisplacementThreshold; - } - - if (isOvershooting || (isVelocity && isDisplacement)) { - if (this._tension !== 0) { - // Ensure that we end up with a round value - this._onUpdate(this._toValue); - } - - this.__debouncedOnEnd({ finished: true }); - return; - } - this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); - } - - stop() { - this.__active = false; - window.cancelAnimationFrame(this._animationFrame); - this.__debouncedOnEnd({ finished: false }); - } -} - -let _uniqueId = 1; - -class AnimatedInterpolation extends AnimatedWithChildren { - constructor(parent, interpolation) { - super(); - this._parent = parent; - this._interpolation = interpolation; - } - - __getValue() { - const parentValue = this._parent.__getValue(); - invariant( - typeof parentValue === 'number', - 'Cannot interpolate an input which is not a number.' - ); - return this._interpolation(parentValue); - } - - interpolate(config) { - return new AnimatedInterpolation(this, Interpolation.create(config)); - } - - __attach() { - this._parent.__addChild(this); - } - - __detach() { - this._parent.__removeChild(this); - } -} - - -class AnimatedValue extends AnimatedWithChildren { - constructor(value) { - super(); - this._value = value; - this._offset = 0; - this._animation = null; - this._listeners = {}; - } - - __detach() { - this.stopAnimation(); - } - - __getValue() { - return this._value + this._offset; - } - - /** - * Directly set the value. This will stop any animations running on the value - * and update all the bound properties. - */ - setValue(value) { - if (this._animation) { - this._animation.stop(); - this._animation = null; - } - this._updateValue(value); - } - - /** - * Sets an offset that is applied on top of whatever value is set, whether via - * `setValue`, an animation, or `Animated.event`. Useful for compensating - * things like the start of a pan gesture. - */ - setOffset(offset) { - this._offset = offset; - } - - /** - * Merges the offset value into the base value and resets the offset to zero. - * The final output of the value is unchanged. - */ - flattenOffset() { - this._value += this._offset; - this._offset = 0; - } - - /** - * Adds an asynchronous listener to the value so you can observe updates from - * animations. This is useful because there is no way to - * synchronously read the value because it might be driven natively. - */ - addListener(callback) { - const id = String(_uniqueId++); - this._listeners[id] = callback; - return id; - } - - removeListener(id) { - delete this._listeners[id]; - } - - removeAllListeners() { - this._listeners = {}; - } - - /** - * Stops any running animation or tracking. `callback` is invoked with the - * final value after stopping the animation, which is useful for updating - * state to match the animation position with layout. - */ - stopAnimation(callback) { - this.stopTracking(); - if (this._animation) { - this._animation.stop(); - } - this._animation = null; - if (callback) { - callback(this.__getValue()); - } - } - - /** - * Interpolates the value before updating the property, e.g. mapping 0-1 to - * 0-10. - */ - interpolate(config) { - return new AnimatedInterpolation(this, Interpolation.create(config)); - } - - /** - * Typically only used internally, but could be used by a custom Animation - * class. - */ - animate(animation, callback) { - let handle = null; - if (animation.__isInteraction) { - handle = InteractionManager.createInteractionHandle(); - } - const previousAnimation = this._animation; - if (this._animation) { - this._animation.stop(); - } - this._animation = animation; - animation.start( - this._value, - (value) => { - this._updateValue(value); - }, - (result) => { - this._animation = null; - if (handle !== null) { - InteractionManager.clearInteractionHandle(handle); - } - if (callback) { - callback(result); - } - }, - previousAnimation - ); - } - - /** - * Typically only used internally. - */ - stopTracking() { - if (this._tracking) { - this._tracking.__detach(); - } - this._tracking = null; - } - - /** - * Typically only used internally. - */ - track(tracking) { - this.stopTracking(); - this._tracking = tracking; - } - - _updateValue(value) { - this._value = value; - _flush(this); - for (const key in this._listeners) { - this._listeners[key]({ value: this.__getValue() }); - } - } -} - - -class AnimatedValueXY extends AnimatedWithChildren { - constructor(valueIn) { - super(); - const value = valueIn || { x: 0, y: 0 }; // @flowfixme: shouldn't need `: any` - if (typeof value.x === 'number' && typeof value.y === 'number') { - this.x = new AnimatedValue(value.x); - this.y = new AnimatedValue(value.y); - } else { - invariant( - value.x instanceof AnimatedValue && - value.y instanceof AnimatedValue, - 'AnimatedValueXY must be initalized with an object of numbers or ' + - 'AnimatedValues.' - ); - this.x = value.x; - this.y = value.y; - } - this._listeners = {}; - } - - setValue(value) { - this.x.setValue(value.x); - this.y.setValue(value.y); - } - - setOffset(offset) { - this.x.setOffset(offset.x); - this.y.setOffset(offset.y); - } - - flattenOffset() { - this.x.flattenOffset(); - this.y.flattenOffset(); - } - - __getValue() { - return { - x: this.x.__getValue(), - y: this.y.__getValue(), - }; - } - - stopAnimation(callback) { - this.x.stopAnimation(); - this.y.stopAnimation(); - if (callback) { - callback(this.__getValue()); - } - } - - addListener(callback) { - const id = String(_uniqueId++); - const jointCallback = ({ value }) => { - callback(this.__getValue()); - }; - this._listeners[id] = { - x: this.x.addListener(jointCallback), - y: this.y.addListener(jointCallback), - }; - return id; - } - - removeListener(id) { - this.x.removeListener(this._listeners[id].x); - this.y.removeListener(this._listeners[id].y); - delete this._listeners[id]; - } - - /** - * Converts `{x, y}` into `{left, top}` for use in style, e.g. - * - *```javascript - * style={this.state.anim.getLayout()} - *``` - */ - getLayout() { - return { - left: this.x, - top: this.y, - }; - } - - /** - * Converts `{x, y}` into a useable translation transform, e.g. - * - *```javascript - * style={{ - * transform: this.state.anim.getTranslateTransform() - * }} - *``` - */ - getTranslateTransform() { - return [ - { translateX: this.x }, - { translateY: this.y } - ]; - } -} - - -class AnimatedAddition extends AnimatedWithChildren { - constructor(a, b) { - super(); - this._a = a; - this._b = b; - } - - __getValue() { - return this._a.__getValue() + this._b.__getValue(); - } - - interpolate(config) { - return new AnimatedInterpolation(this, Interpolation.create(config)); - } - - __attach() { - this._a.__addChild(this); - this._b.__addChild(this); - } - - __detach() { - this._a.__removeChild(this); - this._b.__removeChild(this); - } -} - -class AnimatedMultiplication extends AnimatedWithChildren { - constructor(a, b) { - super(); - this._a = a; - this._b = b; - } - - __getValue() { - return this._a.__getValue() * this._b.__getValue(); - } - - interpolate(config) { - return new AnimatedInterpolation(this, Interpolation.create(config)); - } - - __attach() { - this._a.__addChild(this); - this._b.__addChild(this); - } - - __detach() { - this._a.__removeChild(this); - this._b.__removeChild(this); - } -} - -class AnimatedTransform extends AnimatedWithChildren { - constructor(transforms) { - super(); - this._transforms = transforms; - } - - __getValue() { - return this._transforms.map(transform => { - const result = {}; - for (const key in transform) { - const value = transform[key]; - if (value instanceof Animated) { - result[key] = value.__getValue(); - } else { - result[key] = value; - } - } - return result; - }); - } - - __getAnimatedValue() { - return this._transforms.map(transform => { - const result = {}; - for (const key in transform) { - const value = transform[key]; - if (value instanceof Animated) { - result[key] = value.__getAnimatedValue(); - } else { - // All transform components needed to recompose matrix - result[key] = value; - } - } - return result; - }); - } - - __attach() { - this._transforms.forEach(transform => { - for (const key in transform) { - const value = transform[key]; - if (value instanceof Animated) { - value.__addChild(this); - } - } - }); - } - - __detach() { - this._transforms.forEach(transform => { - for (const key in transform) { - const value = transform[key]; - if (value instanceof Animated) { - value.__removeChild(this); - } - } - }); - } -} - -class AnimatedStyle extends AnimatedWithChildren { - constructor(style) { - super(); - let newStyle; - newStyle = flattenStyle(style) || {}; - if (newStyle.transform) { - newStyle = { - ...newStyle, - transform: new AnimatedTransform(newStyle.transform), - }; - } - this._style = newStyle; - } - - __getValue() { - const style = {}; - for (const key in this._style) { - const value = this._style[key]; - if (value instanceof Animated) { - style[key] = value.__getValue(); - } else { - style[key] = value; - } - } - return style; - } - - __getAnimatedValue() { - const style = {}; - for (const key in this._style) { - const value = this._style[key]; - if (value instanceof Animated) { - style[key] = value.__getAnimatedValue(); - } - } - return style; - } - - __attach() { - for (const key in this._style) { - const value = this._style[key]; - if (value instanceof Animated) { - value.__addChild(this); - } - } - } - - __detach() { - for (const key in this._style) { - const value = this._style[key]; - if (value instanceof Animated) { - value.__removeChild(this); - } - } - } -} - -class AnimatedProps extends Animated { - constructor(props, callback) { - super(); - this._props = props; - if (this._props.style) { - this._props = { - ...this._props, - style: new AnimatedStyle(this._props.style), - }; - } - this._callback = callback; - this.__attach(); - } - - __getValue() { - const props = {}; - for (const key in this._props) { - const value = this._props[key]; - if (value instanceof Animated) { - props[key] = value.__getValue(); - } else { - props[key] = value; - } - } - return props; - } - - __getAnimatedValue() { - const props = {}; - for (const key in this._props) { - const value = this._props[key]; - if (value instanceof Animated) { - props[key] = value.__getAnimatedValue(); - } - } - return props; - } - - __attach() { - for (const key in this._props) { - const value = this._props[key]; - if (value instanceof Animated) { - value.__addChild(this); - } - } - } - - __detach() { - for (const key in this._props) { - const value = this._props[key]; - if (value instanceof Animated) { - value.__removeChild(this); - } - } - } - - update() { - this._callback(); - } -} - -class AnimatedTracking extends Animated { - constructor(value, parent, animationClass, animationConfig, callback) { - super(); - this._value = value; - this._parent = parent; - this._animationClass = animationClass; - this._animationConfig = animationConfig; - this._callback = callback; - this.__attach(); - } - - __getValue() { - return this._parent.__getValue(); - } - - __attach() { - this._parent.__addChild(this); - } - - __detach() { - this._parent.__removeChild(this); - } - - update() { - this._value.animate(new this._animationClass({ - ...this._animationConfig, - toValue: this._animationConfig.toValue.__getValue(), - }), this._callback); - } -} - -function add(a, b) { - return new AnimatedAddition(a, b); -} - -function multiply(a, b) { - return new AnimatedMultiplication(a, b); -} - -function parallel(animations, config) { - let doneCount = 0; - // Make sure we only call stop() at most once for each animation - const hasEnded = {}; - const stopTogether = !(config && config.stopTogether === false); - - const result = { - start(callback) { - if (doneCount === animations.length) { - if (callback) { - callback({ finished: true }); - } - return; - } - - animations.forEach((animation, idx) => { - const cb = function (endResult) { - hasEnded[idx] = true; - doneCount++; - if (doneCount === animations.length) { - doneCount = 0; - if (callback) { - callback(endResult); - } - return; - } - - if (!endResult.finished && stopTogether) { - result.stop(); - } - }; - - if (!animation) { - cb({ finished: true }); - } else { - animation.start(cb); - } - }); - }, - - stop() { - animations.forEach((animation, idx) => { - if (!hasEnded[idx]) { - animation.stop(); - } - hasEnded[idx] = true; - }); - } - }; - - return result; -} - -function maybeVectorAnim(value, config, anim) { - if (value instanceof AnimatedValueXY) { - const configX = { ...config }; - const configY = { ...config }; - for (const key in config) { - const { x, y } = config[key]; - if (x !== undefined && y !== undefined) { - configX[key] = x; - configY[key] = y; - } - } - const aX = anim(value.x, configX); - const aY = anim(value.y, configY); - // We use `stopTogether: false` here because otherwise tracking will break - // because the second animation will get stopped before it can update. - return parallel([aX, aY], { stopTogether: false }); - } - return null; -} - -function spring(value, config) { - return maybeVectorAnim(value, config, spring) || { - start(callback) { - const singleValue = value; - const singleConfig = config; - singleValue.stopTracking(); - if (config.toValue instanceof Animated) { - singleValue.track(new AnimatedTracking( - singleValue, - config.toValue, - SpringAnimation, - singleConfig, - callback - )); - } else { - singleValue.animate(new SpringAnimation(singleConfig), callback); - } - }, - - stop() { - value.stopAnimation(); - }, - }; -} - -function timing(value, config) { - return maybeVectorAnim(value, config, timing) || { - start(callback) { - const singleValue = value; - const singleConfig = config; - singleValue.stopTracking(); - if (config.toValue instanceof Animated) { - singleValue.track(new AnimatedTracking( - singleValue, - config.toValue, - TimingAnimation, - singleConfig, - callback - )); - } else { - singleValue.animate(new TimingAnimation(singleConfig), callback); - } - }, - - stop() { - value.stopAnimation(); - }, - }; -} - -function decay(value, config) { - return maybeVectorAnim(value, config, decay) || { - start(callback) { - const singleValue = value; - const singleConfig = config; - singleValue.stopTracking(); - singleValue.animate(new DecayAnimation(singleConfig), callback); - }, - - stop() { - value.stopAnimation(); - }, - }; -} - -function sequence(animations) { - let current = 0; - return { - start(callback) { - const onComplete = function (result) { - if (!result.finished) { - if (callback) { - callback(result); - } - return; - } - - current++; - - if (current === animations.length) { - if (callback) { - callback(result); - } - return; - } - - animations[current].start(onComplete); - }; - - if (animations.length === 0) { - if (callback) { - callback({ finished: true }); - } - } else { - animations[current].start(onComplete); - } - }, - - stop() { - if (current < animations.length) { - animations[current].stop(); - } - } - }; -} - -function delay(time) { - // Would be nice to make a specialized implementation - return timing(new AnimatedValue(0), { toValue: 0, delay: time, duration: 0 }); -} - -function stagger(time, animations) { - return parallel(animations.map(function (animation, i) { - return sequence([ - delay(time * i), - animation, - ]); - })); -} - -function event(argMapping, config) { - return function (...args) { - const traverse = function (recMapping, recEvt, key) { - if (typeof recEvt === 'number') { - invariant( - recMapping instanceof AnimatedValue, - 'Bad mapping of type ' + typeof recMapping + ' for key ' + key + - ', event value must map to AnimatedValue' - ); - recMapping.setValue(recEvt); - return; - } - invariant( - typeof recMapping === 'object', - 'Bad mapping of type ' + typeof recMapping + ' for key ' + key - ); - invariant( - typeof recEvt === 'object', - 'Bad event of type ' + typeof recEvt + ' for key ' + key - ); - for (const i in recMapping) { - traverse(recMapping[i], recEvt[i], i); - } - }; - argMapping.forEach((mapping, idx) => { - traverse(mapping, args[idx], 'arg' + idx); - }); - if (config && config.listener) { - config.listener.apply(null, args); - } - }; -} - -const AnimatedImplementation = { - Value: AnimatedValue, - ValueXY: AnimatedValueXY, - decay, - timing, - spring, - add, - multiply, - sequence, - parallel, - stagger, - event, - - __PropsOnlyForTests: AnimatedProps, - __Animated: Animated, - __Animation: Animation, - __AnimatedWithChildren: AnimatedWithChildren, - __AnimatedStyle: AnimatedStyle, -}; - -module.exports = AnimatedImplementation; diff --git a/src/api/Animated/Easing.js b/src/api/Animated/Easing.js deleted file mode 100644 index 6183793..0000000 --- a/src/api/Animated/Easing.js +++ /dev/null @@ -1,122 +0,0 @@ -import _bezier from 'cubic-bezier'; - -let _ease = () => {}; - -const EPSILON = (1000 / 60 / 500) / 4; - -/** - * This class implements common easing functions. The math is pretty obscure, - * but this cool website has nice visual illustrations of what they represent: - * http://xaedes.de/dev/transitions/ - */ -class Easing { - static step0(n) { - return n > 0 ? 1 : 0; - } - - static step1(n) { - return n >= 1 ? 1 : 0; - } - - static linear(t) { - return t; - } - - static ease(t) { - return _ease(t); - } - - static quad(t) { - return t * t; - } - - static cubic(t) { - return t * t * t; - } - - static poly(n) { - return (t) => Math.pow(t, n); - } - - static sin(t) { - return 1 - Math.cos(t * Math.PI / 2); - } - - static circle(t) { - return 1 - Math.sqrt(1 - t * t); - } - - static exp(t) { - return Math.pow(2, 10 * (t - 1)); - } - - /** - * A simple elastic interaction, similar to a spring. Default bounciness - * is 1, which overshoots a little bit once. 0 bounciness doesn't overshoot - * at all, and bounciness of N > 1 will overshoot about N times. - * - * Wolfram Plots: - * - * http://tiny.cc/elastic_b_1 (default bounciness = 1) - * http://tiny.cc/elastic_b_3 (bounciness = 3) - */ - static elastic(bounciness = 1) { - const p = bounciness * Math.PI; - return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p); - } - - static back(s = 1.70158) { - return (t) => t * t * ((s + 1) * t - s); - } - - static bounce(argT) { - let t = argT; - if (t < 1 / 2.75) { - return 7.5625 * t * t; - } - - if (t < 2 / 2.75) { - t -= 1.5 / 2.75; - return 7.5625 * t * t + 0.75; - } - - if (t < 2.5 / 2.75) { - t -= 2.25 / 2.75; - return 7.5625 * t * t + 0.9375; - } - - t -= 2.625 / 2.75; - return 7.5625 * t * t + 0.984375; - } - - static bezier(x1, y1, x2, y2, epsilon = EPSILON) { - return _bezier(x1, y1, x2, y2, epsilon); - } - - static in(easing) { - return easing; - } - - /** - * Runs an easing function backwards. - */ - static out(easing) { - return (t) => 1 - easing(1 - t); - } - - /** - * Makes any easing function symmetrical. - */ - static inOut(easing) { - return (t) => { - if (t < 0.5) { - return easing(t * 2) / 2; - } - return 1 - easing((1 - t) * 2) / 2; - }; - } -} - -_ease = Easing.bezier(0.42, 0, 1, 1); - -module.exports = Easing; diff --git a/src/api/Animated/Interpolation.js b/src/api/Animated/Interpolation.js deleted file mode 100644 index aa38a49..0000000 --- a/src/api/Animated/Interpolation.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Animated/src/Interpolation.js - */ -class Interpolation { - static create(config) { - // TODO(lmr): - } -} - -module.exports = Interpolation; diff --git a/src/api/Animated/SpringConfig.js b/src/api/Animated/SpringConfig.js deleted file mode 100644 index 0488523..0000000 --- a/src/api/Animated/SpringConfig.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Animated/src/SpringConfig.js - */ - -function tensionFromOrigamiValue(oValue) { - return (oValue - 30) * 3.62 + 194; -} - -function frictionFromOrigamiValue(oValue) { - return (oValue - 8) * 3 + 25; -} - -function fromOrigamiTensionAndFriction(tension, friction) { - return { - tension: tensionFromOrigamiValue(tension), - friction: frictionFromOrigamiValue(friction) - }; -} - -function fromBouncinessAndSpeed(bounciness, speed) { - function normalize(value, startValue, endValue) { - return (value - startValue) / (endValue - startValue); - } - - function projectNormal(n, start, end) { - return start + (n * (end - start)); - } - - function linearInterpolation(t, start, end) { - return t * end + (1 - t) * start; - } - - function quadraticOutInterpolation(t, start, end) { - return linearInterpolation(2 * t - t * t, start, end); - } - - function b3Friction1(x) { - return (0.0007 * Math.pow(x, 3)) - - (0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28; - } - - function b3Friction2(x) { - return (0.000044 * Math.pow(x, 3)) - - (0.006 * Math.pow(x, 2)) + 0.36 * x + 2; - } - - function b3Friction3(x) { - return (0.00000045 * Math.pow(x, 3)) - - (0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84; - } - - function b3Nobounce(tension) { - if (tension <= 18) { - return b3Friction1(tension); - } else if (tension > 18 && tension <= 44) { - return b3Friction2(tension); - } - return b3Friction3(tension); - } - - let b = normalize(bounciness / 1.7, 0, 20); - b = projectNormal(b, 0, 0.8); - const s = normalize(speed / 1.7, 0, 20); - const bouncyTension = projectNormal(s, 0.5, 200); - const bouncyFriction = quadraticOutInterpolation( - b, - b3Nobounce(bouncyTension), - 0.01 - ); - - return { - tension: tensionFromOrigamiValue(bouncyTension), - friction: frictionFromOrigamiValue(bouncyFriction) - }; -} - -module.exports = { - fromOrigamiTensionAndFriction, - fromBouncinessAndSpeed, -}; diff --git a/src/api/Animated/createAnimatedComponent.js b/src/api/Animated/createAnimatedComponent.js deleted file mode 100644 index 6e9cba5..0000000 --- a/src/api/Animated/createAnimatedComponent.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -function createAnimatedComponent(Component) { - const refName = 'node'; - - class AnimatedComponent extends React.Component { - render() { - return ( - - ); - } - } - - return AnimatedComponent; -} - -module.exports = createAnimatedComponent; diff --git a/src/api/Animated/index.js b/src/api/Animated/index.js deleted file mode 100644 index 914f6ca..0000000 --- a/src/api/Animated/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import View from '../../components/View'; -import Text from '../../components/Text'; -import Image from '../../components/Image'; -import createAnimatedComponent from './createAnimatedComponent'; -import AnimatedImplementation from './AnimatedImplementation'; - -module.exports = { - ...AnimatedImplementation, - createAnimatedComponent, - View: createAnimatedComponent(View), - Text: createAnimatedComponent(Text), - Image: createAnimatedComponent(Image), -}; diff --git a/src/api/AppRegistry.js b/src/api/AppRegistry.js deleted file mode 100644 index df50345..0000000 --- a/src/api/AppRegistry.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/AppRegistry/AppRegistry.js - */ -const runnables = {}; - -const AppRegistry = { - registerConfig(configs) { - - }, - - registerComponent(appKey, getComponentFunc) { - return appKey; - }, - - registerRunnable(appKey, func) { - runnables[appKey] = { run: func }; - return appKey; - }, - - getAppKeys() { - return Object.keys(runnables); - }, - - runApplication(appKey, appParameters) { - - }, - - unmountApplicationComponentAtRootTag(rootTag) { - - }, -}; - -module.exports = AppRegistry; diff --git a/src/api/AppState.js b/src/api/AppState.js deleted file mode 100644 index 8e3b75a..0000000 --- a/src/api/AppState.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/AppState/AppState.js - */ -import invariant from 'invariant'; -import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; - -const _eventHandlers = { - change: new Map(), - memoryWarning: new Map(), -}; - -const AppState = { - addEventListener(type, handler) { - invariant( - ['change', 'memoryWarning'].indexOf(type) !== -1, - 'Trying to subscribe to unknown event: "%s"', type - ); - if (type === 'change') { - _eventHandlers[type].set(handler, DeviceEventEmitter.addListener( - 'appStateDidChange', - (appStateData) => handler(appStateData.appState) - )); - } else if (type === 'memoryWarning') { - _eventHandlers[type].set(handler, DeviceEventEmitter.addListener( - 'memoryWarning', - handler - )); - } - }, - - removeEventListener(type, handler) { - invariant( - ['change', 'memoryWarning'].indexOf(type) !== -1, - 'Trying to remove listener for unknown event: "%s"', type - ); - if (!_eventHandlers[type].has(handler)) { - return; - } - _eventHandlers[type].get(handler).remove(); - _eventHandlers[type].delete(handler); - }, - - currentState: 'active', - - __setAppState(appState) { - DeviceEventEmitter.emit('appStateDidChange', { appState }); - }, -}; - -DeviceEventEmitter.addListener( - 'appStateDidChange', - (appStateData) => { AppState.currentState = appStateData.appState; } -); - -module.exports = AppState; diff --git a/src/api/AppStateIOS.js b/src/api/AppStateIOS.js deleted file mode 100644 index 5066681..0000000 --- a/src/api/AppStateIOS.js +++ /dev/null @@ -1,126 +0,0 @@ -import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; -import AppState from '../NativeModules/AppState'; -import invariant from 'invariant'; - -const logError = (error) => console.error(error); - -const _eventHandlers = { - change: new Map(), - memoryWarning: new Map(), -}; - -/** - * `AppStateIOS` can tell you if the app is in the foreground or background, - * and notify you when the state changes. - * - * AppStateIOS is frequently used to determine the intent and proper behavior when - * handling push notifications. - * - * ### iOS App States - * - * - `active` - The app is running in the foreground - * - `background` - The app is running in the background. The user is either - * in another app or on the home screen - * - `inactive` - This is a transition state that currently never happens for - * typical React Native apps. - * - * For more information, see - * [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html) - * - * ### Basic Usage - * - * To see the current state, you can check `AppStateIOS.currentState`, which - * will be kept up-to-date. However, `currentState` will be null at launch - * while `AppStateIOS` retrieves it over the bridge. - * - * ``` - * getInitialState: function() { - * return { - * currentAppState: AppStateIOS.currentState, - * }; - * }, - * componentDidMount: function() { - * AppStateIOS.addEventListener('change', this._handleAppStateChange); - * }, - * componentWillUnmount: function() { - * AppStateIOS.removeEventListener('change', this._handleAppStateChange); - * }, - * _handleAppStateChange: function(currentAppState) { - * this.setState({ currentAppState, }); - * }, - * render: function() { - * return ( - * Current state is: {this.state.currentAppState} - * ); - * }, - * ``` - * - * This example will only ever appear to say "Current state is: active" because - * the app is only visible to the user when in the `active` state, and the null - * state will happen only momentarily. - */ - -const AppStateIOS = { - - /** - * Add a handler to AppState changes by listening to the `change` event type - * and providing the handler - */ - addEventListener(type, handler) { - invariant( - ['change', 'memoryWarning'].indexOf(type) !== -1, - 'Trying to subscribe to unknown event: "%s"', type - ); - if (type === 'change') { - _eventHandlers[type].set(handler, DeviceEventEmitter.addListener( - 'appStateDidChange', - (appStateData) => { - handler(appStateData.app_state); - } - )); - } else if (type === 'memoryWarning') { - _eventHandlers[type].set(handler, DeviceEventEmitter.addListener( - 'memoryWarning', - handler - )); - } - }, - - /** - * Remove a handler by passing the `change` event type and the handler - */ - removeEventListener(type, handler) { - invariant( - ['change', 'memoryWarning'].indexOf(type) !== -1, - 'Trying to remove listener for unknown event: "%s"', type - ); - if (!_eventHandlers[type].has(handler)) { - return; - } - _eventHandlers[type].get(handler).remove(); - _eventHandlers[type].delete(handler); - }, - - // TODO: getCurrentAppState callback seems to be called at a really late stage - // after app launch. Trying to get currentState when mounting App component - // will likely to have the initial value here. - // Initialize to 'active' instead of null. - currentState: 'active', - -}; - -DeviceEventEmitter.addListener( - 'appStateDidChange', - (appStateData) => { - AppStateIOS.currentState = appStateData.app_state; - } -); - -AppState.getCurrentAppState( - (appStateData) => { - AppStateIOS.currentState = appStateData.app_state; - }, - logError -); - -module.exports = AppStateIOS; diff --git a/src/api/AsyncStorage.js b/src/api/AsyncStorage.js deleted file mode 100644 index 94655e4..0000000 --- a/src/api/AsyncStorage.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Storage/AsyncStorage.js - */ - -function wrap(value, callback) { - return Promise.resolve(value).then( - obj => { - if (callback) { - callback(null, obj); - } - return obj; - }, - err => { - if (callback) { - callback(err); - } - throw err; - } - ); -} - -let db = {}; - -const AsyncStorage = { - getItem(key, callback) { - return wrap(db[key] || null, callback); - }, - - setItem(key, value, callback) { - db[key] = value; - return wrap(null, callback); - }, - - removeItem(key, callback) { - delete db[key]; - return wrap(null, callback); - }, - - mergeItem(key, value, callback) { - db[key] = Object.assign({}, db[key] || {}, value); - return wrap(null, callback); - }, - - clear(callback) { - db = {}; - return wrap(null, callback); - }, - - getAllKeys(callback) { - return wrap(Object.keys(db), callback); - }, - - flushGetRequests() { - - }, - - multiGet(keys, callback) { - return wrap(keys.map(k => [k, db[k] || null]), callback); - }, - - multiSet(keyValuePairs, callback) { - keyValuePairs.forEach(([key, value]) => { - db[key] = value; - }); - return wrap(null, callback); - }, - - multiRemove(keys, callback) { - keys.forEach(key => delete db[key]); - return wrap(null, callback); - }, - - multiMerge(keyValuePairs, callback) { - keyValuePairs.forEach(([key, value]) => { - db[key] = Object.asign({}, db[key] || {}, value); - }); - return wrap(null, callback); - }, -}; - -module.exports = AsyncStorage; diff --git a/src/api/BackAndroid.js b/src/api/BackAndroid.js deleted file mode 100644 index 2214fdf..0000000 --- a/src/api/BackAndroid.js +++ /dev/null @@ -1,55 +0,0 @@ -import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; -import DeviceEventManager from '../NativeModules/DeviceEventManager'; - -const DEVICE_BACK_EVENT = 'hardwareBackPress'; - -const _backPressSubscriptions = new Set(); - -/** - * Detect hardware back button presses, and programmatically invoke the default back button - * functionality to exit the app if there are no listeners or if none of the listeners return true. - * - * Example: - * - * ```js - * BackAndroid.addEventListener('hardwareBackPress', function() { - * if (!this.onMainScreen()) { - * this.goBack(); - * return true; - * } - * return false; - * }); - * ``` - */ -const BackAndroid = { - - exitApp() { - DeviceEventManager.invokeDefaultBackPressHandler(); - }, - - addEventListener(eventName, handler) { - _backPressSubscriptions.add(handler); - return { - remove: () => BackAndroid.removeEventListener(eventName, handler), - }; - }, - - removeEventListener(eventName, handler) { - _backPressSubscriptions.delete(handler); - }, - -}; - -DeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function () { - let invokeDefault = true; - _backPressSubscriptions.forEach((subscription) => { - if (subscription()) { - invokeDefault = false; - } - }); - if (invokeDefault) { - BackAndroid.exitApp(); - } -}); - -module.exports = BackAndroid; diff --git a/src/api/CameraRoll.js b/src/api/CameraRoll.js deleted file mode 100644 index 02ab154..0000000 --- a/src/api/CameraRoll.js +++ /dev/null @@ -1,107 +0,0 @@ -import invariant from 'invariant'; -import React from 'react'; -import CameraRollManager from '../NativeModules/CameraRollManager'; - -const { PropTypes } = React; - -const GROUP_TYPES_OPTIONS = [ - 'Album', - 'All', - 'Event', - 'Faces', - 'Library', - 'PhotoStream', - 'SavedPhotos', // default -]; - -const ASSET_TYPE_OPTIONS = [ - 'All', - 'Videos', - 'Photos', // default -]; - -/** - * Shape of the param arg for the `getPhotos` function. - */ -const getPhotosParamChecker = PropTypes.shape({ - /** - * The number of photos wanted in reverse order of the photo application - * (i.e. most recent first for SavedPhotos). - */ - first: PropTypes.number.isRequired, - - /** - * A cursor that matches `page_info { end_cursor }` returned from a previous - * call to `getPhotos` - */ - after: PropTypes.string, - - /** - * Specifies which group types to filter the results to. - */ - groupTypes: PropTypes.oneOf(GROUP_TYPES_OPTIONS), - - /** - * Specifies filter on group names, like 'Recent Photos' or custom album - * titles. - */ - groupName: PropTypes.string, - - /** - * Specifies filter on asset type - */ - assetType: PropTypes.oneOf(ASSET_TYPE_OPTIONS), - - /** - * Filter by mimetype (e.g. image/jpeg). - */ - mimeTypes: PropTypes.arrayOf(PropTypes.string), -}); - -class CameraRoll { - - /** - * Saves the image to the camera roll / gallery. - * - * On Android, the tag is a local URI, such as `"file:///sdcard/img.png"`. - * - * On iOS, the tag can be one of the following: - * - * - local URI - * - assets-library tag - * - a tag not matching any of the above, which means the image data will - * be stored in memory (and consume memory as long as the process is alive) - * - * Returns a Promise which when resolved will be passed the new URI. - */ - static saveImageWithTag(tag) { - invariant( - typeof tag === 'string', - 'CameraRoll.saveImageWithTag tag must be a valid string.' - ); - // TODO(lmr): - return CameraRollManager.saveImageWithTag(tag); - } - - /** - * Returns a Promise with photo identifier objects from the local camera - * roll of the device matching shape defined by `getPhotosReturnChecker`. - * - * @param {object} params See `getPhotosParamChecker`. - * - * Returns a Promise which when resolved will be of shape `getPhotosReturnChecker`. - */ - static getPhotos(params) { - if (process.env.NODE_ENV === 'development') { - getPhotosParamChecker({ params }, 'params', 'CameraRoll.getPhotos'); - } - // TODO(lmr): - // TODO: Add the __DEV__ check back in to verify the Promise result - return CameraRollManager.getPhotos(params); - } -} - -CameraRoll.GroupTypesOptions = GROUP_TYPES_OPTIONS; -CameraRoll.AssetTypeOptions = ASSET_TYPE_OPTIONS; - -module.exports = CameraRoll; diff --git a/src/api/DatePickerAndroid.js b/src/api/DatePickerAndroid.js deleted file mode 100644 index f7d403a..0000000 --- a/src/api/DatePickerAndroid.js +++ /dev/null @@ -1,71 +0,0 @@ -import DatePickerModule from '../NativeModules/DatePickerAndroid'; - -/** - * Convert a Date to a timestamp. - */ -function _toMillis(dateVal) { - // Is it a Date object? - if (typeof dateVal === 'object' && typeof dateVal.getMonth === 'function') { - return dateVal.getTime(); - } - return null; -} - -/** - * Opens the standard Android date picker dialog. - * - * ### Example - * - * ``` - * try { - * const {action, year, month, day} = await DatePickerAndroid.open({ - * // Use `new Date()` for current date. - * // May 25 2020. Month 0 is January. - * date: new Date(2020, 4, 25) - * }); - * if (action !== DatePickerAndroid.dismissedAction) { - * // Selected year, month (0-11), day - * } - * } catch ({code, message}) { - * console.warn('Cannot open date picker', message); - * } - * ``` - */ -class DatePickerAndroid { - /** - * Opens the standard Android date picker dialog. - * - * The available keys for the `options` object are: - * * `date` (`Date` object or timestamp in milliseconds) - date to show by default - * * `minDate` (`Date` or timestamp in milliseconds) - minimum date that can be selected - * * `maxDate` (`Date` object or timestamp in milliseconds) - minimum date that can be selected - * - * Returns a Promise which will be invoked an object containing `action`, `year`, `month` (0-11), - * `day` if the user picked a date. If the user dismissed the dialog, the Promise will - * still be resolved with action being `DatePickerAndroid.dismissedAction` and all the other keys - * being undefined. **Always** check whether the `action` before reading the values. - * - * Note the native date picker dialog has some UI glitches on Android 4 and lower - * when using the `minDate` and `maxDate` options. - */ - static open(options) { - const optionsMs = options; - if (optionsMs) { - optionsMs.date = _toMillis(options.date); - optionsMs.minDate = _toMillis(options.minDate); - optionsMs.maxDate = _toMillis(options.maxDate); - } - return DatePickerModule.open(optionsMs); - } - - /** - * A date has been selected. - */ - static get dateSetAction() { return 'dateSetAction'; } - /** - * The dialog has been dismissed. - */ - static get dismissedAction() { return 'dismissedAction'; } -} - -module.exports = DatePickerAndroid; diff --git a/src/api/Dimensions.js b/src/api/Dimensions.js deleted file mode 100644 index 8459193..0000000 --- a/src/api/Dimensions.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Utilities/Dimensions.js - */ -const dimensions = { - // TODO(lmr): find the other dimensions to put in here... - window: { - width: 320, - height: 768, - scale: 2, - fontScale: 2, - }, -}; - -const Dimensions = { - set(dims) { - Object.assign(dimensions, dims); - return true; - }, - get(dim) { - return dimensions[dim]; - }, -}; - -module.exports = Dimensions; diff --git a/src/api/ImagePickerIOS.js b/src/api/ImagePickerIOS.js deleted file mode 100644 index 4cb5001..0000000 --- a/src/api/ImagePickerIOS.js +++ /dev/null @@ -1,28 +0,0 @@ - -import ImagePicker from '../NativeModules/ImagePickerIOS'; - -const ImagePickerIOS = { - canRecordVideos(callback) { - return ImagePicker.canRecordVideos(callback); - }, - canUseCamera(callback) { - return ImagePicker.canUseCamera(callback); - }, - openCameraDialog(config, successCallback, cancelCallback) { - const newConfig = { - videoMode: false, - ...config, - }; - return ImagePicker.openCameraDialog(newConfig, successCallback, cancelCallback); - }, - openSelectDialog(config, successCallback, cancelCallback) { - const newConfig = { - showImages: true, - showVideos: false, - ...config, - }; - return ImagePicker.openSelectDialog(newConfig, successCallback, cancelCallback); - }, -}; - -module.exports = ImagePickerIOS; diff --git a/src/api/IntentAndroid.js b/src/api/IntentAndroid.js deleted file mode 100644 index 23fa450..0000000 --- a/src/api/IntentAndroid.js +++ /dev/null @@ -1,66 +0,0 @@ -import invariant from 'invariant'; -import Linking from './Linking'; - -class IntentAndroid { - - /** - * Starts a corresponding external activity for the given URL. - * - * For example, if the URL is "https://www.facebook.com", the system browser will be opened, - * or the "choose application" dialog will be shown. - * - * You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact, - * or any other URL that can be opened with {@code Intent.ACTION_VIEW}. - * - * NOTE: This method will fail if the system doesn't know how to open the specified URL. - * If you're passing in a non-http(s) URL, it's best to check {@code canOpenURL} first. - * - * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly! - * - * @deprecated - */ - static openURL(url) { - console.warn( - '"IntentAndroid" is deprecated. Use the promise based "Linking" instead.' - ); - Linking.openURL(url); - } - - /** - * Determine whether or not an installed app can handle a given URL. - * - * You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact, - * or any other URL that can be opened with {@code Intent.ACTION_VIEW}. - * - * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly! - * - * @param URL the URL to open - * - * @deprecated - */ - static canOpenURL(url, callback) { - invariant( - typeof callback === 'function', - 'A valid callback function is required' - ); - Linking.canOpenURL(url).then(callback); - } - - /** - * If the app launch was triggered by an app link with {@code Intent.ACTION_VIEW}, - * it will give the link url, otherwise it will give `null` - * - * Refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents - * - * @deprecated - */ - static getInitialURL(callback) { - invariant( - typeof callback === 'function', - 'A valid callback function is required' - ); - Linking.getInitialURL().then(callback); - } -} - -module.exports = IntentAndroid; diff --git a/src/api/InteractionManager.js b/src/api/InteractionManager.js deleted file mode 100644 index 21c2371..0000000 --- a/src/api/InteractionManager.js +++ /dev/null @@ -1,69 +0,0 @@ -import keyMirror from 'keymirror'; -import invariant from 'invariant'; - -const { EventEmitter } = require('events'); - -const _emitter = new EventEmitter(); - -let _inc = 0; -let _deadline = -1; // eslint-disable-line no-unused-vars - -const InteractionManager = { - Events: keyMirror({ - interactionStart: true, - interactionComplete: true, - }), - - /** - * Schedule a function to run after all interactions have completed. - */ - runAfterInteractions(task) { - return new Promise(resolve => { - // TODO(lmr): - // _scheduleUpdate(); - // if (task) { - // _taskQueue.enqueue(task); - // } - // const name = task && task.name || '?'; - // _taskQueue.enqueue({ run: resolve, name: 'resolve ' + name }); - }); - }, - - /** - * Notify manager that an interaction has started. - */ - createInteractionHandle() { - // TODO(lmr): - // _scheduleUpdate(); - const handle = ++_inc; - // _addInteractionSet.add(handle); - return handle; - }, - - /** - * Notify manager that an interaction has completed. - */ - clearInteractionHandle(handle) { - invariant( - !!handle, - 'Must provide a handle to clear.' - ); - // TODO(lmr): - // _scheduleUpdate(); - // _addInteractionSet.delete(handle); - // _deleteInteractionSet.add(handle); - }, - - addListener: _emitter.addListener.bind(_emitter), - - /** - * A positive number will use setTimeout to schedule any tasks after the - * eventLoopRunningTime hits the deadline value, otherwise all tasks will be - * executed in one setImmediate batch (default). - */ - setDeadline(deadline) { - _deadline = deadline; - }, -}; - -module.exports = InteractionManager; diff --git a/src/api/Keyboard.js b/src/api/Keyboard.js deleted file mode 100644 index af21cf5..0000000 --- a/src/api/Keyboard.js +++ /dev/null @@ -1,12 +0,0 @@ -const Keyboard = { - addListener(eventname, handler) { - return { - remove: () => {} - }; - }, - removeListener: () => {}, - removeAllListeners: () => {}, - dismiss: () => {} -}; - -module.exports = Keyboard; diff --git a/src/api/LayoutAnimation.js b/src/api/LayoutAnimation.js deleted file mode 100644 index d5715ed..0000000 --- a/src/api/LayoutAnimation.js +++ /dev/null @@ -1,124 +0,0 @@ -import React from 'react'; -import UIManager from '../NativeModules/UIManager'; -import keyMirror from 'keymirror'; - -const { PropTypes } = React; - -const TypesEnum = { - spring: true, - linear: true, - easeInEaseOut: true, - easeIn: true, - easeOut: true, - keyboard: true, -}; - -const Types = keyMirror(TypesEnum); - -const PropertiesEnum = { - opacity: true, - scaleXY: true, -}; - -const Properties = keyMirror(PropertiesEnum); - -const animChecker = PropTypes.shape({ - duration: PropTypes.number, - delay: PropTypes.number, - springDamping: PropTypes.number, - initialVelocity: PropTypes.number, - type: PropTypes.oneOf( - Object.keys(Types) - ), - property: PropTypes.oneOf( // Only applies to create/delete - Object.keys(Properties) - ), -}); - -const configChecker = PropTypes.shape({ - duration: PropTypes.number.isRequired, - create: animChecker, - update: animChecker, - delete: animChecker, -}); - -const nop = () => {}; - -function configureNext(config, onAnimationDidEnd) { - configChecker({ config }, 'config', 'LayoutAnimation.configureNext'); - UIManager.configureNextLayoutAnimation( - config, - onAnimationDidEnd || nop, - nop - ); -} - -function create(duration, type, creationProp) { - return { - duration, - create: { - type, - property: creationProp, - }, - update: { - type, - }, - }; -} - -const Presets = { - easeInEaseOut: create( - 300, Types.easeInEaseOut, Properties.opacity - ), - linear: create( - 500, Types.linear, Properties.opacity - ), - spring: { - duration: 700, - create: { - type: Types.linear, - property: Properties.opacity, - }, - update: { - type: Types.spring, - springDamping: 0.4, - }, - }, -}; - -const LayoutAnimation = { - /** - * Schedules an animation to happen on the next layout. - * - * @param config Specifies animation properties: - * - * - `duration` in milliseconds - * - `create`, config for animating in new views (see `Anim` type) - * - `update`, config for animating views that have been updated - * (see `Anim` type) - * - * @param onAnimationDidEnd Called when the animation finished. - * Only supported on iOS. - * @param onError Called on error. Only supported on iOS. - */ - configureNext, - /** - * Helper for creating a config for `configureNext`. - */ - create, - Types, - Properties, - configChecker, - Presets, - easeInEaseOut: configureNext.bind( - null, Presets.easeInEaseOut - ), - linear: configureNext.bind( - null, Presets.linear - ), - spring: configureNext.bind( - null, Presets.spring - ), -}; - -module.exports = LayoutAnimation; diff --git a/src/api/Linking.js b/src/api/Linking.js deleted file mode 100644 index d01f726..0000000 --- a/src/api/Linking.js +++ /dev/null @@ -1,121 +0,0 @@ -import invariant from 'invariant'; -import Platform from '../plugins/Platform'; -import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; -import LinkingManager from '../NativeModules/LinkingManager'; -import IntentAndroid from './IntentAndroid'; -import LinkingManagerIOS from './LinkingIOS'; - -const _notifHandlers = new Map(); - -const DEVICE_NOTIF_EVENT = 'openURL'; - -// TODO(lmr): -class Linking { - /** - * Add a handler to Linking changes by listening to the `url` event type - * and providing the handler - * - * @platform ios - */ - static addEventListener(type, handler) { - if (Platform.OS === 'android') { - console.warn( - 'Linking.addEventListener is not supported on Android' - ); - } else { - invariant( - type === 'url', - 'Linking only supports `url` events' - ); - const listener = DeviceEventEmitter.addListener( - DEVICE_NOTIF_EVENT, - handler - ); - _notifHandlers.set(handler, listener); - } - } - - /** - * Remove a handler by passing the `url` event type and the handler - * - * @platform ios - */ - static removeEventListener(type, handler) { - if (Platform.OS === 'android') { - console.warn( - 'Linking.removeEventListener is not supported on Android' - ); - } else { - invariant( - type === 'url', - 'Linking only supports `url` events' - ); - const listener = _notifHandlers.get(handler); - if (!listener) { - return; - } - listener.removeListener( - DEVICE_NOTIF_EVENT, - handler - ); - _notifHandlers.delete(handler); - } - } - - /** - * Try to open the given `url` with any of the installed apps. - * - * You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact, - * or any other URL that can be opened with the installed apps. - * - * NOTE: This method will fail if the system doesn't know how to open the specified URL. - * If you're passing in a non-http(s) URL, it's best to check {@code canOpenURL} first. - * - * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly! - */ - static openURL(url) { - this._validateURL(url); - return LinkingManager.openURL(url); - } - - /** - * Determine whether or not an installed app can handle a given URL. - * - * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly! - * - * NOTE: As of iOS 9, your app needs to provide the `LSApplicationQueriesSchemes` key - * inside `Info.plist`. - * - * @param URL the URL to open - */ - static canOpenURL(url) { - this._validateURL(url); - return LinkingManager.canOpenURL(url); - } - - /** - * If the app launch was triggered by an app link with, - * it will give the link url, otherwise it will give `null` - * - * NOTE: To support deep linking on Android, refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents - */ - static getInitialURL() { - if (Platform.OS === 'android') { - return IntentAndroid.getInitialURL(); - } - return Promise.resolve(LinkingManagerIOS.initialURL); - } - - static _validateURL(url) { - invariant( - typeof url === 'string', - `Invalid URL: should be a string. Was: ${url}` - ); - invariant( - url, - 'Invalid URL: cannot be empty' - ); - } -} - -module.exports = Linking; diff --git a/src/api/LinkingIOS.js b/src/api/LinkingIOS.js deleted file mode 100644 index 49fae0b..0000000 --- a/src/api/LinkingIOS.js +++ /dev/null @@ -1,78 +0,0 @@ -import LinkingManager from '../NativeModules/LinkingManager'; -import Linking from './Linking'; -import invariant from 'invariant'; - -let _initialURL = LinkingManager && LinkingManager.initialURL; - -class LinkingIOS { - /** - * Add a handler to LinkingIOS changes by listening to the `url` event type - * and providing the handler - * - * @deprecated - */ - static addEventListener(type, handler) { - console.warn( - '"LinkingIOS.addEventListener" is deprecated. Use "Linking.addEventListener" instead.' - ); - Linking.addEventListener(type, handler); - } - - /** - * Remove a handler by passing the `url` event type and the handler - * - * @deprecated - */ - static removeEventListener(type, handler) { - console.warn( - '"LinkingIOS.removeEventListener" is deprecated. Use "Linking.removeEventListener" instead.' - ); - Linking.removeEventListener(type, handler); - } - - /** - * Try to open the given `url` with any of the installed apps. - * - * @deprecated - */ - static openURL(url) { - console.warn( - '"LinkingIOS.openURL" is deprecated. Use the promise based "Linking.openURL" instead.' - ); - Linking.openURL(url); - } - - /** - * Determine whether or not an installed app can handle a given URL. - * The callback function will be called with `bool supported` as the only argument - * - * NOTE: As of iOS 9, your app needs to provide the `LSApplicationQueriesSchemes` key - * inside `Info.plist`. - * - * @deprecated - */ - static canOpenURL(url, callback) { - console.warn( - '"LinkingIOS.canOpenURL" is deprecated. Use the promise based "Linking.canOpenURL" instead.' - ); - invariant( - typeof callback === 'function', - 'A valid callback function is required' - ); - Linking.canOpenURL(url).then(callback); - } - - /** - * If the app launch was triggered by an app link, it will pop the link url, - * otherwise it will return `null` - * - * @deprecated - */ - static popInitialURL() { - const initialURL = _initialURL; - _initialURL = null; - return initialURL; - } -} - -module.exports = LinkingIOS; diff --git a/src/api/ListViewDataSource.js b/src/api/ListViewDataSource.js deleted file mode 100644 index 46d28e0..0000000 --- a/src/api/ListViewDataSource.js +++ /dev/null @@ -1,27 +0,0 @@ - - -class ListViewDataSource { - constructor() { - this._dataBlob = null; - } - - getRowCount() { - - } - - cloneWithRows(data) { - const newSource = new ListViewDataSource(); - newSource._dataBlob = data; - - return newSource; - } - - cloneWithRowsAndSections(data) { - const newSource = new ListViewDataSource(); - newSource._dataBlob = data; - - return newSource; - } -} - -module.exports = ListViewDataSource; diff --git a/src/api/NetInfo.js b/src/api/NetInfo.js deleted file mode 100644 index 393847f..0000000 --- a/src/api/NetInfo.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Network/NetInfo.js - */ -import Platform from '../plugins/Platform'; -import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; - -const DEVICE_CONNECTIVITY_EVENT = 'networkStatusDidChange'; -const _subscriptions = new Map(); - -let isExpensive = false; -let networkInfo = { - connected: true -}; - - -const NetInfo = { - addEventListener(eventname, handler) { - const listener = DeviceEventEmitter.addListener( - DEVICE_CONNECTIVITY_EVENT, - ({ network_info }) => handler(network_info) - ); - _subscriptions.set(handler, listener); - }, - - removeEventListener(eventName, handler) { - const listener = _subscriptions.get(handler); - if (!listener) { - return; - } - listener.remove(); - _subscriptions.delete(handler); - }, - - fetch() { - return Promise.resolve(networkInfo); - }, - - isConnected: { - addEventListener(eventname, handler) { - - }, - - removeEventListener(eventName, handler) { - - }, - fetch() { - return NetInfo.fetch().then(info => info.connected); - }, - }, - - isConnectionExpensive(callback) { - if (Platform.OS === 'android') { - callback(isExpensive); - } else { - callback(null, 'Unsupported'); - } - }, - - // TODO(lmr): figure out a good way to expose setters here. - __setNetworkInfo(info) { - networkInfo = info; - }, - __setIsConnectionExpensive(expensive) { - isExpensive = expensive; - }, - __setIsConnected(connected) { - networkInfo = Object.assign({}, networkInfo, { connected }); - }, -}; - -module.exports = NetInfo; diff --git a/src/api/PanResponder.js b/src/api/PanResponder.js deleted file mode 100644 index d344162..0000000 --- a/src/api/PanResponder.js +++ /dev/null @@ -1,386 +0,0 @@ -import TouchHistoryMath from './TouchHistoryMath'; - -const currentCentroidXOfTouchesChangedAfter = - TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; -const currentCentroidYOfTouchesChangedAfter = - TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; -const previousCentroidXOfTouchesChangedAfter = - TouchHistoryMath.previousCentroidXOfTouchesChangedAfter; -const previousCentroidYOfTouchesChangedAfter = - TouchHistoryMath.previousCentroidYOfTouchesChangedAfter; -const currentCentroidX = TouchHistoryMath.currentCentroidX; -const currentCentroidY = TouchHistoryMath.currentCentroidY; - -/** - * `PanResponder` reconciles several touches into a single gesture. It makes - * single-touch gestures resilient to extra touches, and can be used to - * recognize simple multi-touch gestures. - * - * It provides a predictable wrapper of the responder handlers provided by the - * [gesture responder system](/react-native/docs/gesture-responder-system.html). - * For each handler, it provides a new `gestureState` object alongside the - * native event object: - * - * ``` - * onPanResponderMove: (event, gestureState) => {} - * ``` - * - * A native event is a synthetic touch event with the following form: - * - * - `nativeEvent` - * + `changedTouches` - Array of all touch events that have changed since the last event - * + `identifier` - The ID of the touch - * + `locationX` - The X position of the touch, relative to the element - * + `locationY` - The Y position of the touch, relative to the element - * + `pageX` - The X position of the touch, relative to the root element - * + `pageY` - The Y position of the touch, relative to the root element - * + `target` - The node id of the element receiving the touch event - * + `timestamp` - A time identifier for the touch, useful for velocity calculation - * + `touches` - Array of all current touches on the screen - * - * A `gestureState` object has the following: - * - * - `stateID` - ID of the gestureState- persisted as long as there at least - * one touch on screen - * - `moveX` - the latest screen coordinates of the recently-moved touch - * - `moveY` - the latest screen coordinates of the recently-moved touch - * - `x0` - the screen coordinates of the responder grant - * - `y0` - the screen coordinates of the responder grant - * - `dx` - accumulated distance of the gesture since the touch started - * - `dy` - accumulated distance of the gesture since the touch started - * - `vx` - current velocity of the gesture - * - `vy` - current velocity of the gesture - * - `numberActiveTouches` - Number of touches currently on screen - * - * ### Basic Usage - * - * ``` - * componentWillMount: function() { - * this._panResponder = PanResponder.create({ - * // Ask to be the responder: - * onStartShouldSetPanResponder: (evt, gestureState) => true, - * onStartShouldSetPanResponderCapture: (evt, gestureState) => true, - * onMoveShouldSetPanResponder: (evt, gestureState) => true, - * onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, - * - * onPanResponderGrant: (evt, gestureState) => { - * // The guesture has started. Show visual feedback so the user knows - * // what is happening! - * - * // gestureState.{x,y}0 will be set to zero now - * }, - * onPanResponderMove: (evt, gestureState) => { - * // The most recent move distance is gestureState.move{X,Y} - * - * // The accumulated gesture distance since becoming responder is - * // gestureState.d{x,y} - * }, - * onPanResponderTerminationRequest: (evt, gestureState) => true, - * onPanResponderRelease: (evt, gestureState) => { - * // The user has released all touches while this view is the - * // responder. This typically means a gesture has succeeded - * }, - * onPanResponderTerminate: (evt, gestureState) => { - * // Another component has become the responder, so this gesture - * // should be cancelled - * }, - * onShouldBlockNativeResponder: (evt, gestureState) => { - * // Returns whether this component should block native components from becoming the JS - * // responder. Returns true by default. Is currently only supported on android. - * return true; - * }, - * }); - * }, - * - * render: function() { - * return ( - * - * ); - * }, - * - * ``` - * - * ### Working Example - * - * To see it in action, try the - * [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js) - */ - -const PanResponder = { - - /** - * - * A graphical explanation of the touch data flow: - * - * +----------------------------+ +--------------------------------+ - * | ResponderTouchHistoryStore | |TouchHistoryMath | - * +----------------------------+ +----------+---------------------+ - * |Global store of touchHistory| |Allocation-less math util | - * |including activeness, start | |on touch history (centroids | - * |position, prev/cur position.| |and multitouch movement etc) | - * | | | | - * +----^-----------------------+ +----^---------------------------+ - * | | - * | (records relevant history | - * | of touches relevant for | - * | implementing higher level | - * | gestures) | - * | | - * +----+-----------------------+ +----|---------------------------+ - * | ResponderEventPlugin | | | Your App/Component | - * +----------------------------+ +----|---------------------------+ - * |Negotiates which view gets | Low level | | High level | - * |onResponderMove events. | events w/ | +-+-------+ events w/ | - * |Also records history into | touchHistory| | Pan | multitouch + | - * |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative| - * +----------------------------+ attached to | | | distance and | - * each event | +---------+ velocity. | - * | | - * | | - * +--------------------------------+ - * - * - * - * Gesture that calculates cumulative movement over time in a way that just - * "does the right thing" for multiple touches. The "right thing" is very - * nuanced. When moving two touches in opposite directions, the cumulative - * distance is zero in each dimension. When two touches move in parallel five - * pixels in the same direction, the cumulative distance is five, not ten. If - * two touches start, one moves five in a direction, then stops and the other - * touch moves fives in the same direction, the cumulative distance is ten. - * - * This logic requires a kind of processing of time "clusters" of touch events - * so that two touch moves that essentially occur in parallel but move every - * other frame respectively, are considered part of the same movement. - * - * Explanation of some of the non-obvious fields: - * - * - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is - * invalid. If a move event has been observed, `(moveX, moveY)` is the - * centroid of the most recently moved "cluster" of active touches. - * (Currently all move have the same timeStamp, but later we should add some - * threshold for what is considered to be "moving"). If a palm is - * accidentally counted as a touch, but a finger is moving greatly, the palm - * will move slightly, but we only want to count the single moving touch. - * - x0/y0: Centroid location (non-cumulative) at the time of becoming - * responder. - * - dx/dy: Cumulative touch distance - not the same thing as sum of each touch - * distance. Accounts for touch moves that are clustered together in time, - * moving the same direction. Only valid when currently responder (otherwise, - * it only represents the drag distance below the threshold). - * - vx/vy: Velocity. - */ - - _initializeGestureState(gestureState) { - const newGestureState = gestureState; - newGestureState.moveX = 0; - newGestureState.moveY = 0; - newGestureState.x0 = 0; - newGestureState.y0 = 0; - newGestureState.dx = 0; - newGestureState.dy = 0; - newGestureState.vx = 0; - newGestureState.vy = 0; - newGestureState.numberActiveTouches = 0; - newGestureState._accountsForMovesUpTo = 0; - }, - - /** - * This is nuanced and is necessary. It is incorrect to continuously take all - * active *and* recently moved touches, find the centroid, and track how that - * result changes over time. Instead, we must take all recently moved - * touches, and calculate how the centroid has changed just for those - * recently moved touches, and append that change to an accumulator. This is - * to (at least) handle the case where the user is moving three fingers, and - * then one of the fingers stops but the other two continue. - * - * This is very different than taking all of the recently moved touches and - * storing their centroid as `dx/dy`. For correctness, we must *accumulate - * changes* in the centroid of recently moved touches. - * - * There is also some nuance with how we handle multiple moved touches in a - * single event. With the way `ReactNativeEventEmitter` dispatches touches as - * individual events, multiple touches generate two 'move' events, each of - * them triggering `onResponderMove`. But with the way `PanResponder` works, - * all of the gesture inference is performed on the first dispatch, since it - * looks at all of the touches (even the ones for which there hasn't been a - * native dispatch yet). Therefore, `PanResponder` does not call - * `onResponderMove` passed the first dispatch. This diverges from the - * typical responder callback pattern (without using `PanResponder`), but - * avoids more dispatches than necessary. - */ - _updateGestureStateOnMove(gestureState, touchHistory) { - const newGestureState = gestureState; - newGestureState.numberActiveTouches = touchHistory.numberActiveTouches; - newGestureState.moveX = currentCentroidXOfTouchesChangedAfter( - touchHistory, - newGestureState._accountsForMovesUpTo - ); - newGestureState.moveY = currentCentroidYOfTouchesChangedAfter( - touchHistory, - newGestureState._accountsForMovesUpTo - ); - const movedAfter = newGestureState._accountsForMovesUpTo; - const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); - const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); - const prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); - const y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); - const nextDX = newGestureState.dx + (x - prevX); - const nextDY = newGestureState.dy + (y - prevY); - - // TODO: This must be filtered intelligently. - const dt = - (touchHistory.mostRecentTimeStamp - newGestureState._accountsForMovesUpTo); - newGestureState.vx = (nextDX - newGestureState.dx) / dt; - newGestureState.vy = (nextDY - newGestureState.dy) / dt; - - newGestureState.dx = nextDX; - newGestureState.dy = nextDY; - newGestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp; - }, - - /** - * @param {object} config Enhanced versions of all of the responder callbacks - * that provide not only the typical `ResponderSyntheticEvent`, but also the - * `PanResponder` gesture state. Simply replace the word `Responder` with - * `PanResponder` in each of the typical `onResponder*` callbacks. For - * example, the `config` object would look like: - * - * - `onMoveShouldSetPanResponder: (e, gestureState) => {...}` - * - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}` - * - `onStartShouldSetPanResponder: (e, gestureState) => {...}` - * - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}` - * - `onPanResponderReject: (e, gestureState) => {...}` - * - `onPanResponderGrant: (e, gestureState) => {...}` - * - `onPanResponderStart: (e, gestureState) => {...}` - * - `onPanResponderEnd: (e, gestureState) => {...}` - * - `onPanResponderRelease: (e, gestureState) => {...}` - * - `onPanResponderMove: (e, gestureState) => {...}` - * - `onPanResponderTerminate: (e, gestureState) => {...}` - * - `onPanResponderTerminationRequest: (e, gestureState) => {...}` - * - `onShouldBlockNativeResponder: (e, gestureState) => {...}` - * - * In general, for events that have capture equivalents, we update the - * gestureState once in the capture phase and can use it in the bubble phase - * as well. - * - * Be careful with onStartShould* callbacks. They only reflect updated - * `gestureState` for start/end events that bubble/capture to the Node. - * Once the node is the responder, you can rely on every start/end event - * being processed by the gesture and `gestureState` being updated - * accordingly. (numberActiveTouches) may not be totally accurate unless you - * are the responder. - */ - create(config) { - const gestureState = { - // Useful for debugging - stateID: Math.random(), - }; - PanResponder._initializeGestureState(gestureState); - const panHandlers = { - onStartShouldSetResponder(e) { - return config.onStartShouldSetPanResponder === undefined ? false : - config.onStartShouldSetPanResponder(e, gestureState); - }, - onMoveShouldSetResponder(e) { - return config.onMoveShouldSetPanResponder === undefined ? false : - config.onMoveShouldSetPanResponder(e, gestureState); - }, - onStartShouldSetResponderCapture(e) { - // TODO: Actually, we should reinitialize the state any time - // touches.length increases from 0 active to > 0 active. - if (e.nativeEvent.touches.length === 1) { - PanResponder._initializeGestureState(gestureState); - } - gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches; - return config.onStartShouldSetPanResponderCapture !== undefined ? - config.onStartShouldSetPanResponderCapture(e, gestureState) : false; - }, - - onMoveShouldSetResponderCapture(e) { - const touchHistory = e.touchHistory; - // Responder system incorrectly dispatches should* to current responder - // Filter out any touch moves past the first one - we would have - // already processed multi-touch geometry during the first event. - if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { - return false; - } - PanResponder._updateGestureStateOnMove(gestureState, touchHistory); - return config.onMoveShouldSetPanResponderCapture ? - config.onMoveShouldSetPanResponderCapture(e, gestureState) : false; - }, - - onResponderGrant(e) { - gestureState.x0 = currentCentroidX(e.touchHistory); - gestureState.y0 = currentCentroidY(e.touchHistory); - gestureState.dx = 0; - gestureState.dy = 0; - if (config.onPanResponderGrant) { - config.onPanResponderGrant(e, gestureState); - } - // TODO: t7467124 investigate if this can be removed - return config.onShouldBlockNativeResponder === undefined ? true : - config.onShouldBlockNativeResponder(); - }, - - onResponderReject(e) { - if (config.onPanResponderReject) { - config.onPanResponderReject(e, gestureState); - } - }, - - onResponderRelease(e) { - if (config.onPanResponderRelease) { - config.onPanResponderRelease(e, gestureState); - } - PanResponder._initializeGestureState(gestureState); - }, - - onResponderStart(e) { - const touchHistory = e.touchHistory; - gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - if (config.onPanResponderStart) { - config.onPanResponderStart(e, gestureState); - } - }, - - onResponderMove(e) { - const touchHistory = e.touchHistory; - // Guard against the dispatch of two touch moves when there are two - // simultaneously changed touches. - if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { - return; - } - // Filter out any touch moves past the first one - we would have - // already processed multi-touch geometry during the first event. - PanResponder._updateGestureStateOnMove(gestureState, touchHistory); - if (config.onPanResponderMove) { - config.onPanResponderMove(e, gestureState); - } - }, - - onResponderEnd(e) { - const touchHistory = e.touchHistory; - gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - if (config.onPanResponderEnd) { - config.onPanResponderEnd(e, gestureState); - } - }, - - onResponderTerminate(e) { - if (config.onPanResponderTerminate) { - config.onPanResponderTerminate(e, gestureState); - } - PanResponder._initializeGestureState(gestureState); - }, - - onResponderTerminationRequest(e) { - return config.onPanResponderTerminationRequest === undefined ? true : - config.onPanResponderTerminationRequest(e, gestureState); - }, - }; - return { panHandlers }; - }, -}; - -module.exports = PanResponder; diff --git a/src/api/PixelRatio.js b/src/api/PixelRatio.js deleted file mode 100644 index 3978a31..0000000 --- a/src/api/PixelRatio.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Utilities/PixelRatio.js - */ -const PixelRatio = { - get() { - return 2; - }, - getFontScale() { - return 2; - }, - getPixelSizeForLayoutSize(layoutSize) { - return Math.round(layoutSize * PixelRatio.get()); - }, - roundToNearestPixel(layoutSize) { - const ratio = PixelRatio.get(); - return Math.round(layoutSize * ratio) / ratio; - }, - startDetecting() { - - }, -}; - -module.exports = PixelRatio; diff --git a/src/api/PushNotificationIOS.js b/src/api/PushNotificationIOS.js deleted file mode 100644 index 9884e12..0000000 --- a/src/api/PushNotificationIOS.js +++ /dev/null @@ -1,231 +0,0 @@ -import invariant from 'invariant'; -import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; - -const _notifHandlers = new Map(); -let _initialNotification = null; - -const DEVICE_NOTIF_EVENT = 'remoteNotificationReceived'; -const NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered'; - -class PushNotificationIOS { - /** - * Schedules the localNotification for immediate presentation. - * - * details is an object containing: - * - * - `alertBody` : The message displayed in the notification alert. - * - `soundName` : The sound played when the notification is fired (optional). - * - */ - static presentLocalNotification(details) { - - } - - /** - * Schedules the localNotification for future presentation. - * - * details is an object containing: - * - * - `fireDate` : The date and time when the system should deliver the notification. - * - `alertBody` : The message displayed in the notification alert. - * - `soundName` : The sound played when the notification is fired (optional). - * - */ - static scheduleLocalNotification(details) { - - } - - /** - * Cancels all scheduled localNotifications - */ - static cancelAllLocalNotifications() { - - } - - /** - * Sets the badge number for the app icon on the home screen - */ - static setApplicationIconBadgeNumber(number) { - - } - - /** - * Gets the current badge number for the app icon on the home screen - */ - static getApplicationIconBadgeNumber(callback) { - - } - - /** - * Attaches a listener to remote notification events while the app is running - * in the foreground or the background. - * - * Valid events are: - * - * - `notification` : Fired when a remote notification is received. The - * handler will be invoked with an instance of `PushNotificationIOS`. - * - `register`: Fired when the user registers for remote notifications. The - * handler will be invoked with a hex string representing the deviceToken. - */ - static addEventListener(type, handler) { - invariant( - type === 'notification' || type === 'register', - 'PushNotificationIOS only supports `notification` and `register` events' - ); - let listener; - if (type === 'notification') { - listener = DeviceEventEmitter.addListener( - DEVICE_NOTIF_EVENT, - (notifData) => { - handler(new PushNotificationIOS(notifData)); - } - ); - } else if (type === 'register') { - listener = DeviceEventEmitter.addListener( - NOTIF_REGISTER_EVENT, - (registrationInfo) => { - handler(registrationInfo.deviceToken); - } - ); - } - _notifHandlers.set(handler, listener); - } - - /** - * Requests notification permissions from iOS, prompting the user's - * dialog box. By default, it will request all notification permissions, but - * a subset of these can be requested by passing a map of requested - * permissions. - * The following permissions are supported: - * - * - `alert` - * - `badge` - * - `sound` - * - * If a map is provided to the method, only the permissions with truthy values - * will be requested. - */ - static requestPermissions(permissions) { - - } - - /** - * Unregister for all remote notifications received via Apple Push Notification service. - * - * You should call this method in rare circumstances only, such as when a new version of - * the app removes support for all types of remote notifications. Users can temporarily - * prevent apps from receiving remote notifications through the Notifications section of - * the Settings app. Apps unregistered through this method can always re-register. - */ - static abandonPermissions() { - - } - - /** - * See what push permissions are currently enabled. `callback` will be - * invoked with a `permissions` object: - * - * - `alert` :boolean - * - `badge` :boolean - * - `sound` :boolean - */ - static checkPermissions(callback) { - invariant( - typeof callback === 'function', - 'Must provide a valid callback' - ); - } - - /** - * Removes the event listener. Do this in `componentWillUnmount` to prevent - * memory leaks - */ - static removeEventListener(type, handler) { - invariant( - type === 'notification' || type === 'register', - 'PushNotificationIOS only supports `notification` and `register` events' - ); - const listener = _notifHandlers.get(handler); - if (!listener) { - return; - } - listener.remove(); - _notifHandlers.delete(handler); - } - - /** - * An initial notification will be available if the app was cold-launched - * from a notification. - * - * The first caller of `popInitialNotification` will get the initial - * notification object, or `null`. Subsequent invocations will return null. - */ - static popInitialNotification() { - const initialNotification = _initialNotification && - new PushNotificationIOS(_initialNotification); - _initialNotification = null; - return initialNotification; - } - - /** - * You will never need to instantiate `PushNotificationIOS` yourself. - * Listening to the `notification` event and invoking - * `popInitialNotification` is sufficient - */ - constructor(nativeNotif) { - this._data = {}; - - // Extract data from Apple's `aps` dict as defined: - - // https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html - - Object.keys(nativeNotif).forEach((notifKey) => { - const notifVal = nativeNotif[notifKey]; - if (notifKey === 'aps') { - this._alert = notifVal.alert; - this._sound = notifVal.sound; - this._badgeCount = notifVal.badge; - } else { - this._data[notifKey] = notifVal; - } - }); - } - - /** - * An alias for `getAlert` to get the notification's main message string - */ - getMessage() { - // alias because "alert" is an ambiguous name - return this._alert; - } - - /** - * Gets the sound string from the `aps` object - */ - getSound() { - return this._sound; - } - - /** - * Gets the notification's main message from the `aps` object - */ - getAlert() { - return this._alert; - } - - /** - * Gets the badge count number from the `aps` object - */ - getBadgeCount() { - return this._badgeCount; - } - - /** - * Gets the data object on the notif - */ - getData() { - return this._data; - } -} - -module.exports = PushNotificationIOS; diff --git a/src/api/Settings.js b/src/api/Settings.js deleted file mode 100644 index 02e924a..0000000 --- a/src/api/Settings.js +++ /dev/null @@ -1,68 +0,0 @@ -import invariant from 'invariant'; -import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; - -const subscriptions = []; - -const Settings = { - _settings: {}, - - get(key) { - return this._settings[key]; - }, - - set(settings) { - this._settings = Object.assign(this._settings, settings); - }, - - watchKeys(keys, callback) { - let newKeys = keys; - if (typeof keys === 'string') { - newKeys = [keys]; - } - - invariant( - Array.isArray(newKeys), - 'keys should be a string or array of strings' - ); - - const sid = subscriptions.length; - subscriptions.push({ keys: newKeys, callback }); - return sid; - }, - - clearWatch(watchId) { - if (watchId < subscriptions.length) { - subscriptions[watchId] = { - keys: [], - callback: null, - }; - } - }, - - _sendObservations(body) { - Object.keys(body).forEach((key) => { - const newValue = body[key]; - const didChange = this._settings[key] !== newValue; - this._settings[key] = newValue; - - if (didChange) { - subscriptions.forEach((sub) => { - if (sub.keys.indexOf(key) !== -1 && sub.callback) { - sub.callback(); - } - }); - } - }); - }, - - __emulateDeviceSettingsChange(settings) { - DeviceEventEmitter.emit('settingsUpdated', settings); - }, -}; - -DeviceEventEmitter.addListener( - 'settingsUpdated', - Settings._sendObservations.bind(Settings) -); - -module.exports = Settings; diff --git a/src/api/Share.js b/src/api/Share.js deleted file mode 100644 index d5ad7db..0000000 --- a/src/api/Share.js +++ /dev/null @@ -1,17 +0,0 @@ - -class Share { - - static share(content, options) { - return Promise.resolve('sharedAction'); - } - - static get sharedAction() { - return 'sharedAction'; - } - - static get dismissedAction() { - return 'dismissedAction'; - } -} - -module.exports = Share; diff --git a/src/api/StatusBarIOS.js b/src/api/StatusBarIOS.js deleted file mode 100644 index 7410329..0000000 --- a/src/api/StatusBarIOS.js +++ /dev/null @@ -1,32 +0,0 @@ -let _style = {}; -let _hidden = false; -let _networkActivityIndicatorVisible = true; - -const StatusBarIOS = { - - setStyle(style, animated) { - _style = style; - }, - - setHidden(hidden, animation) { - _hidden = hidden; - }, - - setNetworkActivityIndicatorVisible(visible) { - _networkActivityIndicatorVisible = visible; - }, - - __getStyle() { - return _style; - }, - - __getHidden() { - return _hidden; - }, - - __getNetworkActivityIndicatorVisible() { - return _networkActivityIndicatorVisible; - }, -}; - -module.exports = StatusBarIOS; diff --git a/src/api/StyleSheet.js b/src/api/StyleSheet.js deleted file mode 100644 index aef5363..0000000 --- a/src/api/StyleSheet.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/StyleSheet.js - */ -const StyleSheet = { - create(styles) { - return styles; - }, - flatten(styles) { - if (Array.isArray(styles)) { - return Object.assign({}, ...styles.map(StyleSheet.flatten)); - } - - return styles; - } -}; - -module.exports = StyleSheet; diff --git a/src/api/TextInputState.js b/src/api/TextInputState.js deleted file mode 100644 index fd9a4de..0000000 --- a/src/api/TextInputState.js +++ /dev/null @@ -1,39 +0,0 @@ - -const TextInputState = { - /** - * Internal state - */ - _currentlyFocusedID: null, - - /** - * Returns the ID of the currently focused text field, if one exists - * If no text field is focused it returns null - */ - currentlyFocusedField() { - return this._currentlyFocusedID; - }, - - /** - * @param {number} TextInputID id of the text field to focus - * Focuses the specified text field - * noop if the text field was already focused - */ - focusTextInput(textFieldID) { - if (this._currentlyFocusedID !== textFieldID && textFieldID !== null) { - this._currentlyFocusedID = textFieldID; - } - }, - - /** - * @param {number} textFieldID id of the text field to focus - * Unfocuses the specified text field - * noop if it wasn't focused - */ - blurTextInput(textFieldID) { - if (this._currentlyFocusedID === textFieldID && textFieldID !== null) { - this._currentlyFocusedID = null; - } - } -}; - -module.exports = TextInputState; diff --git a/src/api/TimePickerAndroid.js b/src/api/TimePickerAndroid.js deleted file mode 100644 index cfe9644..0000000 --- a/src/api/TimePickerAndroid.js +++ /dev/null @@ -1,54 +0,0 @@ -import TimePickerModule from '../NativeModules/TimePickerAndroid'; - -/** - * Opens the standard Android time picker dialog. - * - * ### Example - * - * ``` - * try { - * const {action, hour, minute} = await TimePickerAndroid.open({ - * hour: 14, - * minute: 0, - * is24Hour: false, // Will display '2 PM' - * }); - * if (action !== DatePickerAndroid.dismissedAction) { - * // Selected hour (0-23), minute (0-59) - * } - * } catch ({code, message}) { - * console.warn('Cannot open time picker', message); - * } - * ``` - */ -class TimePickerAndroid { - - /** - * Opens the standard Android time picker dialog. - * - * The available keys for the `options` object are: - * * `hour` (0-23) - the hour to show, defaults to the current time - * * `minute` (0-59) - the minute to show, defaults to the current time - * * `is24Hour` (boolean) - If `true`, the picker uses the 24-hour format. If `false`, - * the picker shows an AM/PM chooser. If undefined, the default for the current locale - * is used. - * - * Returns a Promise which will be invoked an object containing `action`, `hour` (0-23), - * `minute` (0-59) if the user picked a time. If the user dismissed the dialog, the Promise will - * still be resolved with action being `TimePickerAndroid.dismissedAction` and all the other keys - * being undefined. **Always** check whether the `action` before reading the values. - */ - static open(options) { - return TimePickerModule.open(options); - } - - /** - * A time has been selected. - */ - static get timeSetAction() { return 'timeSetAction'; } - /** - * The dialog has been dismissed. - */ - static get dismissedAction() { return 'dismissedAction'; } -} - -module.exports = TimePickerAndroid; diff --git a/src/api/TouchHistoryMath.js b/src/api/TouchHistoryMath.js deleted file mode 100644 index a8f8b19..0000000 --- a/src/api/TouchHistoryMath.js +++ /dev/null @@ -1,118 +0,0 @@ -const TouchHistoryMath = { - /** - * This code is optimized and not intended to look beautiful. This allows - * computing of touch centroids that have moved after `touchesChangedAfter` - * timeStamp. You can compute the current centroid involving all touches - * moves after `touchesChangedAfter`, or you can compute the previous - * centroid of all touches that were moved after `touchesChangedAfter`. - * - * @param {TouchHistoryMath} touchHistory Standard Responder touch track - * data. - * @param {number} touchesChangedAfter timeStamp after which moved touches - * are considered "actively moving" - not just "active". - * @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension. - * @param {boolean} ofCurrent Compute current centroid for actively moving - * touches vs. previous centroid of now actively moving touches. - * @return {number} value of centroid in specified dimension. - */ - centroidDimension(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) { - const touchBank = touchHistory.touchBank; - let total = 0; - let count = 0; - - const oneTouchData = touchHistory.numberActiveTouches === 1 ? - touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null; - - if (oneTouchData !== null) { - if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) { - // FIXME: DONT USE TERNARIES!!!! - total += - ofCurrent && isXAxis ? oneTouchData.currentPageX : // eslint-disable-line - ofCurrent && !isXAxis ? oneTouchData.currentPageY : // eslint-disable-line - !ofCurrent && isXAxis ? oneTouchData.previousPageX : - oneTouchData.previousPageY; - count = 1; - } - } else { - for (let i = 0; i < touchBank.length; i++) { - const touchTrack = touchBank[i]; - if (touchTrack !== null && - touchTrack !== undefined && - touchTrack.touchActive && - touchTrack.currentTimeStamp >= touchesChangedAfter) { - let toAdd; // Yuck, program temporarily in invalid state. - if (ofCurrent && isXAxis) { - toAdd = touchTrack.currentPageX; - } else if (ofCurrent && !isXAxis) { - toAdd = touchTrack.currentPageY; - } else if (!ofCurrent && isXAxis) { - toAdd = touchTrack.previousPageX; - } else { - toAdd = touchTrack.previousPageY; - } - total += toAdd; - count++; - } - } - } - return count > 0 ? total / count : TouchHistoryMath.noCentroid; - }, - - currentCentroidXOfTouchesChangedAfter(touchHistory, touchesChangedAfter) { - return TouchHistoryMath.centroidDimension( - touchHistory, - touchesChangedAfter, - true, // isXAxis - true // ofCurrent - ); - }, - - currentCentroidYOfTouchesChangedAfter(touchHistory, touchesChangedAfter) { - return TouchHistoryMath.centroidDimension( - touchHistory, - touchesChangedAfter, - false, // isXAxis - true // ofCurrent - ); - }, - - previousCentroidXOfTouchesChangedAfter(touchHistory, touchesChangedAfter) { - return TouchHistoryMath.centroidDimension( - touchHistory, - touchesChangedAfter, - true, // isXAxis - false // ofCurrent - ); - }, - - previousCentroidYOfTouchesChangedAfter(touchHistory, touchesChangedAfter) { - return TouchHistoryMath.centroidDimension( - touchHistory, - touchesChangedAfter, - false, // isXAxis - false // ofCurrent - ); - }, - - currentCentroidX(touchHistory) { - return TouchHistoryMath.centroidDimension( - touchHistory, - 0, // touchesChangedAfter - true, // isXAxis - true // ofCurrent - ); - }, - - currentCentroidY(touchHistory) { - return TouchHistoryMath.centroidDimension( - touchHistory, - 0, // touchesChangedAfter - false, // isXAxis - true // ofCurrent - ); - }, - - noCentroid: -1, -}; - -module.exports = TouchHistoryMath; diff --git a/src/api/VibrationIOS.js b/src/api/VibrationIOS.js deleted file mode 100644 index 12165fe..0000000 --- a/src/api/VibrationIOS.js +++ /dev/null @@ -1,25 +0,0 @@ -import NativeVibration from '../NativeModules/Vibration'; -import invariant from 'invariant'; - -/** - * The Vibration API is exposed at `VibrationIOS.vibrate()`. On iOS, calling this - * function will trigger a one second vibration. The vibration is asynchronous - * so this method will return immediately. - * - * There will be no effect on devices that do not support Vibration, eg. the iOS - * simulator. - * - * Vibration patterns are currently unsupported. - */ - -const Vibration = { - vibrate() { - invariant( - arguments[0] === undefined, - 'Vibration patterns not supported.' - ); - NativeVibration.vibrate(); - } -}; - -module.exports = Vibration; diff --git a/src/babel.js b/src/babel.js new file mode 100644 index 0000000..35f3f28 --- /dev/null +++ b/src/babel.js @@ -0,0 +1,4 @@ +require('babel-core/register')({ + // Ignore everything in node_modules except node_modules/rcomponents. + ignore: /node_modules\/(?!react-native)/ +}); diff --git a/src/components/ART/Path.js b/src/components/ART/Path.js deleted file mode 100644 index 85d7d9b..0000000 --- a/src/components/ART/Path.js +++ /dev/null @@ -1,28 +0,0 @@ - -class Path { - constructor(path) { - [ - 'push', - 'reset', - 'move', - 'moveTo', - 'line', - 'lineTo', - 'curve', - 'curveTo', - 'arc', - 'arcTo', - 'counterArc', - 'counterArcTo', - 'close' - ].forEach((methodName) => { this[methodName] = () => this; }); - - this.path = path || []; - } - - toJSON() { - return JSON.stringify(this.path); - } -} - -module.exports = Path; diff --git a/src/components/ART/Transform.js b/src/components/ART/Transform.js deleted file mode 100644 index e41a615..0000000 --- a/src/components/ART/Transform.js +++ /dev/null @@ -1,28 +0,0 @@ - -class Transform { - constructor() { - this.xx = 0; - this.yx = 0; - this.xy = 0; - this.yy = 0; - this.x = 0; - this.y = 0; - } - transformTo() { - return this; - } - move() { - return this; - } - rotate() { - return this; - } - scale() { - return this; - } - transform() { - return this; - } -} - -module.exports = Transform; diff --git a/src/components/ART/index.js b/src/components/ART/index.js deleted file mode 100644 index d9f4e73..0000000 --- a/src/components/ART/index.js +++ /dev/null @@ -1,40 +0,0 @@ -import createMockComponent from '../createMockComponent'; -import Transform from './Transform'; -import Path from './Path'; - -const LINEAR_GRADIENT = 1; -const RADIAL_GRADIENT = 2; -const PATTERN = 3; - -function CSSBackgroundPattern() { - // TODO(lmr): - return {}; -} - -function Pattern(url, width, height, left, top) { - this._brush = [PATTERN, url, +left || 0, +top || 0, +width, +height]; -} - -function LinearGradient(stops, x1, y1, x2, y2) { - this._brush = [LINEAR_GRADIENT, +x1, +y1, +x2, +y2]; -} - -function RadialGradient(stops, fx, fy, rx, ry, cx, cy) { - this._brush = [RADIAL_GRADIENT, +fx, +fy, +rx * 2, +ry * 2, +cx, +cy]; -} - -const ReactART = { - LinearGradient, - RadialGradient, - Pattern, - Transform, - Path, - Surface: createMockComponent('Surface'), - Group: createMockComponent('Group'), - ClippingRectangle: createMockComponent('ClippingRectangle'), - Shape: createMockComponent('Shape'), - Text: createMockComponent('Text'), - CSSBackgroundPattern, -}; - -module.exports = ReactART; diff --git a/src/components/ActivityIndicator.js b/src/components/ActivityIndicator.js deleted file mode 100644 index 7d0f447..0000000 --- a/src/components/ActivityIndicator.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Components/ActivityIndicator/ActivityIndicator.js - */ -import React from 'react'; -import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; -import View from './View'; -import ColorPropType from '../propTypes/ColorPropType'; - -const { PropTypes } = React; - -const ActivityIndicator = React.createClass({ - propTypes: { - ...View.propTypes, - /** - * Whether to show the indicator (true, the default) or hide it (false). - */ - animating: PropTypes.bool, - /** - * The foreground color of the spinner (default is gray). - */ - color: ColorPropType, - /** - * Whether the indicator should hide when not animating (true by default). - */ - hidesWhenStopped: PropTypes.bool, - /** - * Size of the indicator. Small has a height of 20, large has a height of 36. - */ - size: PropTypes.oneOf([ - 'small', - 'large', - ]), - /** - * Invoked on mount and layout changes with - * - * {nativeEvent: { layout: {x, y, width, height}}}. - */ - onLayout: PropTypes.func, - }, - mixins: [NativeMethodsMixin], - render() { - return null; - }, -}); - -module.exports = ActivityIndicator; diff --git a/src/components/ActivityIndicatorIOS.js b/src/components/ActivityIndicatorIOS.js deleted file mode 100644 index 63a95ca..0000000 --- a/src/components/ActivityIndicatorIOS.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Components/ActivityIndicator/ActivityIndicatorIOS.ios.js - */ -import React from 'react'; -import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; -import View from './View'; - -const { PropTypes } = React; - -const ActivityIndicatorIOS = React.createClass({ - propTypes: { - ...View.propTypes, - /** - * Whether to show the indicator (true, the default) or hide it (false). - */ - animating: PropTypes.bool, - /** - * The foreground color of the spinner (default is gray). - */ - color: PropTypes.string, - /** - * Whether the indicator should hide when not animating (true by default). - */ - hidesWhenStopped: PropTypes.bool, - /** - * Size of the indicator. Small has a height of 20, large has a height of 36. - */ - size: PropTypes.oneOf([ - 'small', - 'large', - ]), - /** - * Invoked on mount and layout changes with - * - * {nativeEvent: { layout: {x, y, width, height}}}. - */ - onLayout: PropTypes.func, - }, - - mixins: [NativeMethodsMixin], - - render() { - return null; - }, -}); - -module.exports = ActivityIndicatorIOS; diff --git a/src/components/DrawerLayoutAndroid.js b/src/components/DrawerLayoutAndroid.js deleted file mode 100644 index 86e1e37..0000000 --- a/src/components/DrawerLayoutAndroid.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - *https://github.com/facebook/react-native/blob/master/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js - */ -import React from 'react'; -import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; -import View from './View'; -import UIManager from '../NativeModules/UIManager'; -import ColorPropType from '../propTypes/ColorPropType'; - -const ReactPropTypes = React.PropTypes; -const DrawerConsts = UIManager.AndroidDrawerLayout.Constants; - -const DrawerLayoutAndroid = React.createClass({ - - propTypes: { - ...View.propTypes, - /** - * Determines whether the keyboard gets dismissed in response to a drag. - * - 'none' (the default), drags do not dismiss the keyboard. - * - 'on-drag', the keyboard is dismissed when a drag begins. - */ - keyboardDismissMode: ReactPropTypes.oneOf([ - 'none', // default - 'on-drag', - ]), - /** - * Specifies the background color of the drawer. The default value is white. - * If you want to set the opacity of the drawer, use rgba. Example: - * - * ``` - * return ( - * - * - * ); - * ``` - */ - drawerBackgroundColor: ColorPropType, - /** - * Specifies the side of the screen from which the drawer will slide in. - */ - drawerPosition: ReactPropTypes.oneOf([ - DrawerConsts.DrawerPosition.Left, - DrawerConsts.DrawerPosition.Right - ]), - /** - * Specifies the width of the drawer, more precisely the width of the view that be pulled in - * from the edge of the window. - */ - drawerWidth: ReactPropTypes.number, - /** - * Specifies the lock mode of the drawer. The drawer can be locked in 3 states: - * - unlocked (default), meaning that the drawer will respond (open/close) to touch gestures. - * - locked-closed, meaning that the drawer will stay closed and not respond to gestures. - * - locked-open, meaning that the drawer will stay opened and not respond to gestures. - * The drawer may still be opened and closed programmatically (`openDrawer`/`closeDrawer`). - */ - drawerLockMode: ReactPropTypes.oneOf([ - 'unlocked', - 'locked-closed', - 'locked-open' - ]), - /** - * Function called whenever there is an interaction with the navigation view. - */ - onDrawerSlide: ReactPropTypes.func, - /** - * Function called when the drawer state has changed. The drawer can be in 3 states: - * - idle, meaning there is no interaction with the navigation view happening at the time - * - dragging, meaning there is currently an interaction with the navigation view - * - settling, meaning that there was an interaction with the navigation view, and the - * navigation view is now finishing its closing or opening animation - */ - onDrawerStateChanged: ReactPropTypes.func, - /** - * Function called whenever the navigation view has been opened. - */ - onDrawerOpen: ReactPropTypes.func, - /** - * Function called whenever the navigation view has been closed. - */ - onDrawerClose: ReactPropTypes.func, - /** - * The navigation view that will be rendered to the side of the screen and can be pulled in. - */ - renderNavigationView: ReactPropTypes.func.isRequired, - - /** - * Make the drawer take the entire screen and draw the background of the - * status bar to allow it to open over the status bar. It will only have an - * effect on API 21+. - */ - statusBarBackgroundColor: ColorPropType, - }, - - mixins: [NativeMethodsMixin], - - statics: { - positions: DrawerConsts.DrawerPosition - }, - - openDrawer() { - // do nothing - }, - - closeDrawer() { - // do nothing - }, - - render() { - return null; - } - -}); - -module.exports = DrawerLayoutAndroid; diff --git a/src/components/Image.js b/src/components/Image.js deleted file mode 100644 index 3bc21fa..0000000 --- a/src/components/Image.js +++ /dev/null @@ -1,127 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Image/Image.ios.js - */ -import React from 'react'; -import styleSheetPropType from '../propTypes/StyleSheetPropType'; -import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; -import EdgeInsetsPropType from '../propTypes/EdgeInsetsPropType'; -import ImageStylePropTypes from '../propTypes/ImageStylePropTypes'; -import ImageResizeMode from '../propTypes/ImageResizeMode'; - -const { PropTypes } = React; - -const Image = React.createClass({ - propTypes: { - style: styleSheetPropType(ImageStylePropTypes), - /** - * `uri` is a string representing the resource identifier for the image, which - * could be an http address, a local file path, or the name of a static image - * resource (which should be wrapped in the `require('./path/to/image.png')` function). - */ - source: PropTypes.oneOfType([ - PropTypes.shape({ - uri: PropTypes.string, - }), - // Opaque type returned by require('./image.jpg') - PropTypes.number, - ]), - /** - * A static image to display while loading the image source. - * @platform ios - */ - defaultSource: PropTypes.oneOfType([ - PropTypes.shape({ - uri: PropTypes.string, - }), - // Opaque type returned by require('./image.jpg') - PropTypes.number, - ]), - /** - * When true, indicates the image is an accessibility element. - * @platform ios - */ - accessible: PropTypes.bool, - /** - * The text that's read by the screen reader when the user interacts with - * the image. - * @platform ios - */ - accessibilityLabel: PropTypes.string, - /** - * When the image is resized, the corners of the size specified - * by capInsets will stay a fixed size, but the center content and borders - * of the image will be stretched. This is useful for creating resizable - * rounded buttons, shadows, and other resizable assets. More info on - * [Apple documentation](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/index.html#//apple_ref/occ/instm/UIImage/resizableImageWithCapInsets) - * @platform ios - */ - capInsets: EdgeInsetsPropType, - /** - * Determines how to resize the image when the frame doesn't match the raw - * image dimensions. - * - * 'cover': Scale the image uniformly (maintain the image's aspect ratio) - * so that both dimensions (width and height) of the image will be equal - * to or larger than the corresponding dimension of the view (minus padding). - * - * 'contain': Scale the image uniformly (maintain the image's aspect ratio) - * so that both dimensions (width and height) of the image will be equal to - * or less than the corresponding dimension of the view (minus padding). - * - * 'stretch': Scale width and height independently, This may change the - * aspect ratio of the src. - * - * `repeat`: Repeat the image to cover the frame of the view. The - * image will keep it's size and aspect ratio. (iOS only) - * - */ - resizeMode: PropTypes.oneOf(ImageResizeMode), - /** - * A unique identifier for this element to be used in UI Automation - * testing scripts. - */ - testID: PropTypes.string, - /** - * Invoked on mount and layout changes with - * `{nativeEvent: {layout: {x, y, width, height}}}`. - */ - onLayout: PropTypes.func, - /** - * Invoked on load start - */ - onLoadStart: PropTypes.func, - /** - * Invoked on download progress with `{nativeEvent: {loaded, total}}` - * @platform ios - */ - onProgress: PropTypes.func, - /** - * Invoked on load error with `{nativeEvent: {error}}` - * @platform ios - */ - onError: PropTypes.func, - /** - * Invoked when load completes successfully - */ - onLoad: PropTypes.func, - /** - * Invoked when load either succeeds or fails - */ - onLoadEnd: PropTypes.func, - }, - mixins: [NativeMethodsMixin], - statics: { - resizeMode: ImageResizeMode, - getSize(uri, success, failure) { - - }, - prefetch(uri) { - - } - }, - render() { - return null; - }, -}); - -module.exports = Image; diff --git a/src/components/ListView.js b/src/components/ListView.js deleted file mode 100644 index f546f45..0000000 --- a/src/components/ListView.js +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react'; -import ScrollResponder from '../mixins/ScrollResponder'; -import TimerMixin from 'react-timer-mixin'; -import ScrollView from './ScrollView'; -import ListViewDataSource from '../api/ListViewDataSource'; - -const { PropTypes } = React; -const SCROLLVIEW_REF = 'listviewscroll'; - - -const ListView = React.createClass({ - propTypes: { - ...ScrollView.propTypes, - - dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, - /** - * (sectionID, rowID, adjacentRowHighlighted) => renderable - * - * If provided, a renderable component to be rendered as the separator - * below each row but not the last row if there is a section header below. - * Take a sectionID and rowID of the row above and whether its adjacent row - * is highlighted. - */ - renderSeparator: PropTypes.func, - /** - * (rowData, sectionID, rowID, highlightRow) => renderable - * - * Takes a data entry from the data source and its ids and should return - * a renderable component to be rendered as the row. By default the data - * is exactly what was put into the data source, but it's also possible to - * provide custom extractors. ListView can be notified when a row is - * being highlighted by calling highlightRow function. The separators above and - * below will be hidden when a row is highlighted. The highlighted state of - * a row can be reset by calling highlightRow(null). - */ - renderRow: PropTypes.func.isRequired, - /** - * How many rows to render on initial component mount. Use this to make - * it so that the first screen worth of data appears at one time instead of - * over the course of multiple frames. - */ - initialListSize: PropTypes.number, - /** - * Called when all rows have been rendered and the list has been scrolled - * to within onEndReachedThreshold of the bottom. The native scroll - * event is provided. - */ - onEndReached: PropTypes.func, - /** - * Threshold in pixels for onEndReached. - */ - onEndReachedThreshold: PropTypes.number, - /** - * Number of rows to render per event loop. - */ - pageSize: PropTypes.number, - /** - * () => renderable - * - * The header and footer are always rendered (if these props are provided) - * on every render pass. If they are expensive to re-render, wrap them - * in StaticContainer or other mechanism as appropriate. Footer is always - * at the bottom of the list, and header at the top, on every render pass. - */ - renderFooter: PropTypes.func, - renderHeader: PropTypes.func, - /** - * (sectionData, sectionID) => renderable - * - * If provided, a sticky header is rendered for this section. The sticky - * behavior means that it will scroll with the content at the top of the - * section until it reaches the top of the screen, at which point it will - * stick to the top until it is pushed off the screen by the next section - * header. - */ - renderSectionHeader: PropTypes.func, - /** - * (props) => renderable - * - * A function that returns the scrollable component in which the list rows - * are rendered. Defaults to returning a ScrollView with the given props. - */ - renderScrollComponent: React.PropTypes.func.isRequired, - /** - * How early to start rendering rows before they come on screen, in - * pixels. - */ - scrollRenderAheadDistance: React.PropTypes.number, - /** - * (visibleRows, changedRows) => void - * - * Called when the set of visible rows changes. `visibleRows` maps - * { sectionID: { rowID: true }} for all the visible rows, and - * `changedRows` maps { sectionID: { rowID: true | false }} for the rows - * that have changed their visibility, with true indicating visible, and - * false indicating the view has moved out of view. - */ - onChangeVisibleRows: React.PropTypes.func, - /** - * A performance optimization for improving scroll perf of - * large lists, used in conjunction with overflow: 'hidden' on the row - * containers. This is enabled by default. - */ - removeClippedSubviews: React.PropTypes.bool, - /** - * An array of child indices determining which children get docked to the - * top of the screen when scrolling. For example, passing - * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the - * top of the scroll view. This property is not supported in conjunction - * with `horizontal={true}`. - * @platform ios - */ - stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number), - }, - mixins: [ScrollResponder.Mixin, TimerMixin], - - statics: { - DataSource: ListViewDataSource, - }, - - /** - * Exports some data, e.g. for perf investigations or analytics. - */ - getMetrics() { // eslint-disable-line react/sort-comp - // It's fixed, but the linter doesnt want to recognise it... - return { - contentLength: this.scrollProperties.contentLength, - totalRows: this.props.dataSource.getRowCount(), - renderedRows: this.state.curRenderedRowsCount, - visibleRows: Object.keys(this._visibleRows).length, - }; - }, - - scrollTo(destY, destX) { - this.getScrollResponder().scrollResponderScrollTo(destX || 0, destY || 0); - }, - - /** - * Provides a handle to the underlying scroll responder to support operations - * such as scrollTo. - */ - getScrollResponder() { - return this.refs[SCROLLVIEW_REF] && - this.refs[SCROLLVIEW_REF].getScrollResponder && - this.refs[SCROLLVIEW_REF].getScrollResponder(); - }, - - setNativeProps(props) { - this.refs[SCROLLVIEW_REF].setNativeProps(props); - }, - - getDefaultProps() { - return { - renderScrollComponent: (props) => - }; - }, - - getInnerViewNode() { - return this.refs[SCROLLVIEW_REF].getInnerViewNode(); - }, - - render() { - return null; - }, -}); - -module.exports = ListView; diff --git a/src/components/Navigator.js b/src/components/Navigator.js deleted file mode 100644 index 14a1d57..0000000 --- a/src/components/Navigator.js +++ /dev/null @@ -1,103 +0,0 @@ -import React, { PropTypes } from 'react'; -import createMockComponent from './createMockComponent'; -import View from './View'; - -const NavigatorSceneConfigType = PropTypes.shape({ - gestures: PropTypes.object, - springFriction: PropTypes.number, - springTension: PropTypes.number, - defaultTransitionVelocity: PropTypes.number, - animationInterpolators: React.PropTypes.object, -}); - -const NavigatorSceneConfigs = { - PushFromRight: NavigatorSceneConfigType, - FloatFromRight: NavigatorSceneConfigType, - FloatFromLeft: NavigatorSceneConfigType, - FloatFromBottom: NavigatorSceneConfigType, - FloatFromBottomAndroid: NavigatorSceneConfigType, - FadeAndroid: NavigatorSceneConfigType, - HorizontalSwipeJump: NavigatorSceneConfigType, - HorizontalSwipeJumpFromRight: NavigatorSceneConfigType, - VerticalUpSwipeJump: NavigatorSceneConfigType, - VerticalDownSwipeJump: NavigatorSceneConfigType -}; - -const Navigator = React.createClass({ - propTypes: { - /** - * Optional function that allows configuration about scene animations and - * gestures. Will be invoked with the route and the routeStack and should - * return a scene configuration object - * - * ``` - * (route, routeStack) => Navigator.SceneConfigs.FloatFromRight - * ``` - */ - configureScene: PropTypes.func, - - /** - * Required function which renders the scene for a given route. Will be - * invoked with the route and the navigator object - * - * ``` - * (route, navigator) => - * - * ``` - */ - renderScene: PropTypes.func.isRequired, - - /** - * Specify a route to start on. A route is an object that the navigator - * will use to identify each scene to render. `initialRoute` must be - * a route in the `initialRouteStack` if both props are provided. The - * `initialRoute` will default to the last item in the `initialRouteStack`. - */ - initialRoute: PropTypes.object, - - /** - * Provide a set of routes to initially mount. Required if no initialRoute - * is provided. Otherwise, it will default to an array containing only the - * `initialRoute` - */ - initialRouteStack: PropTypes.arrayOf(PropTypes.object), - - /** - * Will emit the target route upon mounting and before each nav transition - */ - onWillFocus: PropTypes.func, - - /** - * Will be called with the new route of each scene after the transition is - * complete or after the initial mounting - */ - onDidFocus: PropTypes.func, - - /** - * Optionally provide a navigation bar that persists across scene - * transitions - */ - navigationBar: PropTypes.node, - - /** - * Optionally provide the navigator object from a parent Navigator - */ - navigator: PropTypes.object, - - /** - * Styles to apply to the container of each scene - */ - sceneStyle: View.propTypes.style, - }, - - statics: { - BreadcrumbNavigationBar: createMockComponent('NavigatorBreadcrumbNavigationBar'), - NavigationBar: createMockComponent('NavigatorNavigationBar'), - SceneConfigs: NavigatorSceneConfigs, - }, - render() { - return null; - } -}); - -module.exports = Navigator; diff --git a/src/components/Picker.js b/src/components/Picker.js deleted file mode 100644 index 97fc0eb..0000000 --- a/src/components/Picker.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import createMockComponent from './createMockComponent'; - -const Picker = React.createClass({ - propTypes: { - children: React.PropTypes.node - }, - statics: { - Item: createMockComponent('Picker.Item') - }, - render() { - return null; - } -}); - -module.exports = Picker; diff --git a/src/components/ScrollView.js b/src/components/ScrollView.js deleted file mode 100644 index 2832369..0000000 --- a/src/components/ScrollView.js +++ /dev/null @@ -1,324 +0,0 @@ -import React from 'react'; -import EdgeInsetsPropType from '../propTypes/EdgeInsetsPropType'; -import PointPropType from '../propTypes/PointPropType'; -import ScrollResponder from '../mixins/ScrollResponder'; -import View from './View'; -import ViewStylePropTypes from '../propTypes/ViewStylePropTypes'; -import ScrollViewManager from '../NativeModules/ScrollViewManager'; -import styleSheetPropType from '../propTypes/StyleSheetPropType'; - -const { PropTypes } = React; - -const SCROLLVIEW = 'ScrollView'; -const INNERVIEW = 'InnerScrollView'; - -const ScrollView = React.createClass({ - propTypes: { - ...View.propTypes, - /** - * Controls whether iOS should automatically adjust the content inset - * for scroll views that are placed behind a navigation bar or - * tab bar/ toolbar. The default value is true. - * @platform ios - */ - automaticallyAdjustContentInsets: PropTypes.bool, - /** - * The amount by which the scroll view content is inset from the edges - * of the scroll view. Defaults to `{0, 0, 0, 0}`. - * @platform ios - */ - contentInset: EdgeInsetsPropType, - /** - * Used to manually set the starting scroll offset. - * The default value is `{x: 0, y: 0}`. - * @platform ios - */ - contentOffset: PointPropType, - /** - * When true, the scroll view bounces when it reaches the end of the - * content if the content is larger then the scroll view along the axis of - * the scroll direction. When false, it disables all bouncing even if - * the `alwaysBounce*` props are true. The default value is true. - * @platform ios - */ - bounces: PropTypes.bool, - /** - * When true, gestures can drive zoom past min/max and the zoom will animate - * to the min/max value at gesture end, otherwise the zoom will not exceed - * the limits. - * @platform ios - */ - bouncesZoom: PropTypes.bool, - /** - * When true, the scroll view bounces horizontally when it reaches the end - * even if the content is smaller than the scroll view itself. The default - * value is true when `horizontal={true}` and false otherwise. - * @platform ios - */ - alwaysBounceHorizontal: PropTypes.bool, - /** - * When true, the scroll view bounces vertically when it reaches the end - * even if the content is smaller than the scroll view itself. The default - * value is false when `horizontal={true}` and true otherwise. - * @platform ios - */ - alwaysBounceVertical: PropTypes.bool, - /** - * When true, the scroll view automatically centers the content when the - * content is smaller than the scroll view bounds; when the content is - * larger than the scroll view, this property has no effect. The default - * value is false. - * @platform ios - */ - centerContent: PropTypes.bool, - /** - * These styles will be applied to the scroll view content container which - * wraps all of the child views. Example: - * - * return ( - * - * - * ); - * ... - * var styles = StyleSheet.create({ - * contentContainer: { - * paddingVertical: 20 - * } - * }); - */ - contentContainerStyle: styleSheetPropType(ViewStylePropTypes), - /** - * A floating-point number that determines how quickly the scroll view - * decelerates after the user lifts their finger. You may also use string - * shortcuts `"normal"` and `"fast"` which match the underlying iOS settings - * for `UIScrollViewDecelerationRateNormal` and - * `UIScrollViewDecelerationRateFast` respectively. - * - Normal: 0.998 (the default) - * - Fast: 0.9 - * @platform ios - */ - decelerationRate: PropTypes.oneOfType([ - PropTypes.oneOf(['fast', 'normal']), - PropTypes.number, - ]), - /** - * When true, the scroll view's children are arranged horizontally in a row - * instead of vertically in a column. The default value is false. - */ - horizontal: PropTypes.bool, - /** - * The style of the scroll indicators. - * - `default` (the default), same as `black`. - * - `black`, scroll indicator is black. - * - `white`, scroll indicator is white. - * @platform ios - */ - indicatorStyle: PropTypes.oneOf([ - 'default', // default - 'black', - 'white', - ]), - /** - * When true, the ScrollView will try to lock to only vertical or horizontal - * scrolling while dragging. The default value is false. - * @platform ios - */ - directionalLockEnabled: PropTypes.bool, - /** - * When false, once tracking starts, won't try to drag if the touch moves. - * The default value is true. - * @platform ios - */ - canCancelContentTouches: PropTypes.bool, - /** - * Determines whether the keyboard gets dismissed in response to a drag. - * - 'none' (the default), drags do not dismiss the keyboard. - * - 'on-drag', the keyboard is dismissed when a drag begins. - * - 'interactive', the keyboard is dismissed interactively with the drag and moves in - * synchrony with the touch; dragging upwards cancels the dismissal. - * On android this is not supported and it will have the same behavior as 'none'. - */ - keyboardDismissMode: PropTypes.oneOf([ - 'none', // default - 'interactive', - 'on-drag', - ]), - /** - * Determines when the keyboard should stay visible after a tap. - * - * - 'never' (the default), tapping outside of the focused text input when the keyboard - * is up dismisses the keyboard. When this happens, children won't receive the tap. - * - 'always', the keyboard will not dismiss automatically, and the scroll view will not - * catch taps, but children of the scroll view can catch taps. - * - 'handled', the keyboard will not dismiss automatically when the tap was handled by - * a children, (or captured by an ancestor). - * - false, deprecated, use 'never' instead - * - true, deprecated, use 'always' instead - */ - keyboardShouldPersistTaps: PropTypes.oneOf(['always', 'never', 'handled', false, true]), - /** - * The maximum allowed zoom scale. The default value is 1.0. - * @platform ios - */ - maximumZoomScale: PropTypes.number, - /** - * The minimum allowed zoom scale. The default value is 1.0. - * @platform ios - */ - minimumZoomScale: PropTypes.number, - /** - * Fires at most once per frame during scrolling. The frequency of the - * events can be controlled using the `scrollEventThrottle` prop. - */ - onScroll: PropTypes.func, - /** - * Called when a scrolling animation ends. - * @platform ios - */ - onScrollAnimationEnd: PropTypes.func, - /** - * Called when scrollable content view of the ScrollView changes. It's - * implemented using onLayout handler attached to the content container - * which this ScrollView renders. - */ - onContentSizeChange: PropTypes.func, - /** - * When true, the scroll view stops on multiples of the scroll view's size - * when scrolling. This can be used for horizontal pagination. The default - * value is false. - * @platform ios - */ - pagingEnabled: PropTypes.bool, - /** - * When false, the content does not scroll. - * The default value is true. - * @platform ios - */ - scrollEnabled: PropTypes.bool, - /** - * This controls how often the scroll event will be fired while scrolling - * (in events per seconds). A higher number yields better accuracy for code - * that is tracking the scroll position, but can lead to scroll performance - * problems due to the volume of information being send over the bridge. - * The default value is zero, which means the scroll event will be sent - * only once each time the view is scrolled. - * @platform ios - */ - scrollEventThrottle: PropTypes.number, - /** - * The amount by which the scroll view indicators are inset from the edges - * of the scroll view. This should normally be set to the same value as - * the `contentInset`. Defaults to `{0, 0, 0, 0}`. - * @platform ios - */ - scrollIndicatorInsets: EdgeInsetsPropType, - /** - * When true, the scroll view scrolls to top when the status bar is tapped. - * The default value is true. - * @platform ios - */ - scrollsToTop: PropTypes.bool, - /** - * When true, momentum events will be sent from Android - * This is internal and set automatically by the framework if you have - * onMomentumScrollBegin or onMomentumScrollEnd set on your ScrollView - * @platform android - */ - sendMomentumEvents: PropTypes.bool, - /** - * When true, shows a horizontal scroll indicator. - */ - showsHorizontalScrollIndicator: PropTypes.bool, - /** - * When true, shows a vertical scroll indicator. - */ - showsVerticalScrollIndicator: PropTypes.bool, - /** - * An array of child indices determining which children get docked to the - * top of the screen when scrolling. For example, passing - * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the - * top of the scroll view. This property is not supported in conjunction - * with `horizontal={true}`. - * @platform ios - */ - stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number), - style: styleSheetPropType(ViewStylePropTypes), - /** - * When set, causes the scroll view to stop at multiples of the value of - * `snapToInterval`. This can be used for paginating through children - * that have lengths smaller than the scroll view. Used in combination - * with `snapToAlignment`. - * @platform ios - */ - snapToInterval: PropTypes.number, - /** - * When `snapToInterval` is set, `snapToAlignment` will define the relationship - * of the the snapping to the scroll view. - * - `start` (the default) will align the snap at the left (horizontal) or top (vertical) - * - `center` will align the snap in the center - * - `end` will align the snap at the right (horizontal) or bottom (vertical) - * @platform ios - */ - snapToAlignment: PropTypes.oneOf([ - 'start', // default - 'center', - 'end', - ]), - /** - * Experimental: When true, offscreen child views (whose `overflow` value is - * `hidden`) are removed from their native backing superview when offscreen. - * This can improve scrolling performance on long lists. The default value is - * true. - */ - removeClippedSubviews: PropTypes.bool, - /** - * The current scale of the scroll view content. The default value is 1.0. - * @platform ios - */ - zoomScale: PropTypes.number, - - /** - * A RefreshControl component, used to provide pull-to-refresh - * functionality for the ScrollView. - * - * See [RefreshControl](http://facebook.github.io/react-native/docs/refreshcontrol.html). - */ - refreshControl: PropTypes.element, - }, - - mixins: [ScrollResponder.Mixin], - - setNativeProps(props) { - this.refs[SCROLLVIEW].setNativeProps(props); - }, - - /** - * Returns a reference to the underlying scroll responder, which supports - * operations like `scrollTo`. All ScrollView-like components should - * implement this method so that they can be composed while providing access - * to the underlying scroll responder's methods. - */ - getScrollResponder() { - return this; - }, - - getInnerViewNode() { - return React.findNodeHandle(this.refs[INNERVIEW]); - }, - - endRefreshin() { - ScrollViewManager.endRefreshing( - React.findNodeHandle(this) - ); - }, - - scrollTo(destY = 0, destX = 0, animated = true) { - - }, - - render() { - return null; - }, -}); - -module.exports = ScrollView; diff --git a/src/components/StatusBar.js b/src/components/StatusBar.js deleted file mode 100644 index f9f51f3..0000000 --- a/src/components/StatusBar.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Components/StatusBar/StatusBar.js - */ -import React from 'react'; -import ColorPropType from '../propTypes/ColorPropType'; - - -let _backgroundColor = ''; -let _barStyle = {}; -let _hidden = false; -let _networkActivityIndicatorVisible = false; -let _translucent = false; - -const StatusBar = React.createClass({ - propTypes: { - animated: React.PropTypes.bool, - barStyle: React.PropTypes.oneOf(['default', 'light-content']), - backgroundColor: ColorPropType, - hidden: React.PropTypes.bool, - networkActivityIndicatorVisible: React.PropTypes.bool, - showHideTransition: React.PropTypes.oneOf(['fade', 'slide']), - translucent: React.PropTypes.bool - }, - - statics: { - setBackgroundColor(backgroundColor, animated) { - _backgroundColor = backgroundColor; - }, - - setBarStyle(barStyle, animated) { - _barStyle = barStyle; - }, - - setHidden(hidden, animated) { - _hidden = hidden; - }, - - setNetworkActivityIndicatorVisible(visible) { - _networkActivityIndicatorVisible = visible; - }, - - setTranslucent(translucent) { - _translucent = translucent; - }, - - __getBackgroundColor() { - return _backgroundColor; - }, - - __getBarStyle() { - return _barStyle; - }, - - __getHidden() { - return _hidden; - }, - - __getNetworkActivityIndicatorVisible() { - return _networkActivityIndicatorVisible; - }, - - __getTranslucent() { - return _translucent; - } - }, - - render() { - return null; - } -}); - -module.exports = StatusBar; diff --git a/src/components/TabBarIOS.js b/src/components/TabBarIOS.js deleted file mode 100644 index d474eee..0000000 --- a/src/components/TabBarIOS.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import createMockComponent from './createMockComponent'; - -const TabBarIOS = React.createClass({ - propTypes: { - children: React.PropTypes.node - }, - statics: { - Item: createMockComponent('TabBarIOS.Item') - }, - render() { - return null; - } -}); - -module.exports = TabBarIOS; diff --git a/src/components/Text.js b/src/components/Text.js deleted file mode 100644 index a8a267b..0000000 --- a/src/components/Text.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Text/Text.js - */ -import React from 'react'; -import styleSheetPropType from '../propTypes/StyleSheetPropType'; -import TextStylePropTypes from '../propTypes/TextStylePropTypes'; -import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; - -const stylePropType = styleSheetPropType(TextStylePropTypes); - -const Text = React.createClass({ - propTypes: { - /** - * Used to truncate the text with an ellipsis after computing the text - * layout, including line wrapping, such that the total number of lines - * does not exceed this number. - */ - numberOfLines: React.PropTypes.number, - /** - * Invoked on mount and layout changes with - * - * `{nativeEvent: {layout: {x, y, width, height}}}` - */ - onLayout: React.PropTypes.func, - /** - * This function is called on press. - */ - onPress: React.PropTypes.func, - /** - * When true, no visual change is made when text is pressed down. By - * default, a gray oval highlights the text on press down. - * @platform ios - */ - suppressHighlighting: React.PropTypes.bool, - style: stylePropType, - /** - * Used to locate this view in end-to-end tests. - */ - testID: React.PropTypes.string, - /** - * Specifies should fonts scale to respect Text Size accessibility setting on iOS. - * @platform ios - */ - allowFontScaling: React.PropTypes.bool, - }, - mixins: [NativeMethodsMixin], - - render() { - return null; - }, -}); - -module.exports = Text; diff --git a/src/components/TextInput.js b/src/components/TextInput.js deleted file mode 100644 index 5cec014..0000000 --- a/src/components/TextInput.js +++ /dev/null @@ -1,249 +0,0 @@ -import React from 'react'; -import TextInputState from '../api/TextInputState'; -import TimerMixin from 'react-timer-mixin'; -import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; -import View from './View'; -import Text from './Text'; - -const { PropTypes } = React; - -const TextInput = React.createClass({ - propTypes: { - ...View.propTypes, - /** - * Can tell TextInput to automatically capitalize certain characters. - * - * - characters: all characters, - * - words: first letter of each word - * - sentences: first letter of each sentence (default) - * - none: don't auto capitalize anything - */ - autoCapitalize: PropTypes.oneOf([ - 'none', - 'sentences', - 'words', - 'characters', - ]), - /** - * If false, disables auto-correct. The default value is true. - */ - autoCorrect: PropTypes.bool, - /** - * If true, focuses the input on componentDidMount. - * The default value is false. - */ - autoFocus: PropTypes.bool, - /** - * If false, text is not editable. The default value is true. - */ - editable: PropTypes.bool, - /** - * Determines which keyboard to open, e.g.`numeric`. - * - * The following values work across platforms: - * - default - * - numeric - * - email-address - */ - keyboardType: PropTypes.oneOf([ - // Cross-platform - 'default', - 'email-address', - 'numeric', - 'phone-pad', - // iOS-only - 'ascii-capable', - 'numbers-and-punctuation', - 'url', - 'number-pad', - 'name-phone-pad', - 'decimal-pad', - 'twitter', - 'web-search', - ]), - /** - * Determines the color of the keyboard. - * @platform ios - */ - keyboardAppearance: PropTypes.oneOf([ - 'default', - 'light', - 'dark', - ]), - /** - * Determines how the return key should look. - * @platform ios - */ - returnKeyType: PropTypes.oneOf([ - 'default', - 'go', - 'google', - 'join', - 'next', - 'route', - 'search', - 'send', - 'yahoo', - 'done', - 'emergency-call', - ]), - /** - * Limits the maximum number of characters that can be entered. Use this - * instead of implementing the logic in JS to avoid flicker. - */ - maxLength: PropTypes.number, - /** - * Sets the number of lines for a TextInput. Use it with multiline set to - * true to be able to fill the lines. - * @platform android - */ - numberOfLines: PropTypes.number, - /** - * If true, the keyboard disables the return key when there is no text and - * automatically enables it when there is text. The default value is false. - * @platform ios - */ - enablesReturnKeyAutomatically: PropTypes.bool, - /** - * If true, the text input can be multiple lines. - * The default value is false. - */ - multiline: PropTypes.bool, - /** - * Callback that is called when the text input is blurred - */ - onBlur: PropTypes.func, - /** - * Callback that is called when the text input is focused - */ - onFocus: PropTypes.func, - /** - * Callback that is called when the text input's text changes. - */ - onChange: PropTypes.func, - /** - * Callback that is called when the text input's text changes. - * Changed text is passed as an argument to the callback handler. - */ - onChangeText: PropTypes.func, - /** - * Callback that is called when text input ends. - */ - onEndEditing: PropTypes.func, - /** - * Callback that is called when the text input selection is changed - */ - onSelectionChange: PropTypes.func, - /** - * Callback that is called when the text input's submit button is pressed. - * Invalid if multiline={true} is specified. - */ - onSubmitEditing: PropTypes.func, - /** - * Callback that is called when a key is pressed. - * Pressed key value is passed as an argument to the callback handler. - * Fires before onChange callbacks. - * @platform ios - */ - onKeyPress: PropTypes.func, - /** - * Invoked on mount and layout changes with `{x, y, width, height}`. - */ - onLayout: PropTypes.func, - /** - * The string that will be rendered before text input has been entered - */ - placeholder: PropTypes.string, - /** - * The text color of the placeholder string - */ - placeholderTextColor: PropTypes.string, - /** - * If true, the text input obscures the text entered so that sensitive text - * like passwords stay secure. The default value is false. - */ - secureTextEntry: PropTypes.bool, - /** - * See DocumentSelectionState.js, some state that is responsible for - * maintaining selection information for a document - * @platform ios - */ - // TODO(lmr): requireLibrary - // selectionState: PropTypes.instanceOf(DocumentSelectionState), - /** - * The value to show for the text input. TextInput is a controlled - * component, which means the native value will be forced to match this - * value prop if provided. For most uses this works great, but in some - * cases this may cause flickering - one common cause is preventing edits - * by keeping value the same. In addition to simply setting the same value, - * either set `editable={false}`, or set/update `maxLength` to prevent - * unwanted edits without flicker. - */ - value: PropTypes.string, - /** - * Provides an initial value that will change when the user starts typing. - * Useful for simple use-cases where you don't want to deal with listening - * to events and updating the value prop to keep the controlled state in sync. - */ - defaultValue: PropTypes.string, - /** - * When the clear button should appear on the right side of the text view - * @platform ios - */ - clearButtonMode: PropTypes.oneOf([ - 'never', - 'while-editing', - 'unless-editing', - 'always', - ]), - /** - * If true, clears the text field automatically when editing begins - * @platform ios - */ - clearTextOnFocus: PropTypes.bool, - /** - * If true, all text will automatically be selected on focus - * @platform ios - */ - selectTextOnFocus: PropTypes.bool, - /** - * If true, the text field will blur when submitted. - * The default value is true for single-line fields and false for - * multiline fields. Note that for multiline fields, setting blurOnSubmit - * to true means that pressing return will blur the field and trigger the - * onSubmitEditing event instead of inserting a newline into the field. - * @platform ios - */ - blurOnSubmit: PropTypes.bool, - /** - * Styles - */ - style: Text.propTypes.style, - /** - * Used to locate this view in end-to-end tests - */ - testID: PropTypes.string, - /** - * The color of the textInput underline. - * @platform android - */ - underlineColorAndroid: PropTypes.string, - }, - mixins: [NativeMethodsMixin, TimerMixin], - statics: { - State: TextInputState, - }, - isFocused() { - // TODO(lmr): React.findNodeHandle - return TextInputState.currentlyFocusedField() === - React.findNodeHandle(this.refs.input); - }, - clear() { - - }, - render() { - return null; - }, -}); - -module.exports = TextInput; diff --git a/src/components/TouchableNativeFeedback.js b/src/components/TouchableNativeFeedback.js deleted file mode 100644 index bd606a5..0000000 --- a/src/components/TouchableNativeFeedback.js +++ /dev/null @@ -1,22 +0,0 @@ - -import React from 'react'; - -import TouchableWithoutFeedback from './TouchableWithoutFeedback'; - -const TouchableNativeFeedback = React.createClass({ - propTypes: { - ...TouchableWithoutFeedback.propTypes, - - background: React.PropTypes.object - }, - statics: { - SelectableBackground() {}, - SelectableBackgroundBorderless() {}, - Ripple(color, borderless) {} - }, - render() { - return null; - } -}); - -module.exports = TouchableNativeFeedback; diff --git a/src/components/TouchableOpacity.js b/src/components/TouchableOpacity.js deleted file mode 100644 index aab17a8..0000000 --- a/src/components/TouchableOpacity.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Components/Touchable/TouchableOpacity.js - */ -import React from 'react'; - -import TouchableWithoutFeedback from './TouchableWithoutFeedback'; - -const TouchableOpacity = React.createClass({ - propTypes: { - ...TouchableWithoutFeedback.propTypes, - - /** - * Determines what the opacity of the wrapped view should be when touch is - * active. Defaults to 0.2. - */ - activeOpacity: React.PropTypes.number, - }, - - render() { - return null; - }, -}); - -module.exports = TouchableOpacity; diff --git a/src/components/TouchableWithoutFeedback.js b/src/components/TouchableWithoutFeedback.js deleted file mode 100644 index cf1d3e2..0000000 --- a/src/components/TouchableWithoutFeedback.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Components/Touchable/TouchableWithoutFeedback.js - */ -import React from 'react'; -import EdgeInsetsPropType from '../propTypes/EdgeInsetsPropType'; -import View from './View'; - -const TouchableWithoutFeedback = React.createClass({ - propTypes: { - accessible: React.PropTypes.bool, - accessibilityComponentType: React.PropTypes.oneOf(View.AccessibilityComponentType), - accessibilityTraits: React.PropTypes.oneOfType([ - React.PropTypes.oneOf(View.AccessibilityTraits), - React.PropTypes.arrayOf(React.PropTypes.oneOf(View.AccessibilityTraits)), - ]), - /** - * If true, disable all interactions for this component. - */ - disabled: React.PropTypes.bool, - /** - * Called when the touch is released, but not if cancelled (e.g. by a scroll - * that steals the responder lock). - */ - onPress: React.PropTypes.func, - onPressIn: React.PropTypes.func, - onPressOut: React.PropTypes.func, - /** - * Invoked on mount and layout changes with - * - * `{nativeEvent: {layout: {x, y, width, height}}}` - */ - onLayout: React.PropTypes.func, - - onLongPress: React.PropTypes.func, - - /** - * Delay in ms, from the start of the touch, before onPressIn is called. - */ - delayPressIn: React.PropTypes.number, - /** - * Delay in ms, from the release of the touch, before onPressOut is called. - */ - delayPressOut: React.PropTypes.number, - /** - * Delay in ms, from onPressIn, before onLongPress is called. - */ - delayLongPress: React.PropTypes.number, - /** - * When the scroll view is disabled, this defines how far your touch may - * move off of the button, before deactivating the button. Once deactivated, - * try moving it back and you'll see that the button is once again - * reactivated! Move it back and forth several times while the scroll view - * is disabled. Ensure you pass in a constant to reduce memory allocations. - */ - pressRetentionOffset: EdgeInsetsPropType, - /** - * This defines how far your touch can start away from the button. This is - * added to `pressRetentionOffset` when moving off of the button. - * ** NOTE ** - * The touch area never extends past the parent view bounds and the Z-index - * of sibling views always takes precedence if a touch hits two overlapping - * views. - */ - hitSlop: EdgeInsetsPropType, - }, - render() { - return null; - }, -}); - -module.exports = TouchableWithoutFeedback; diff --git a/src/components/View.js b/src/components/View.js deleted file mode 100644 index 75799d8..0000000 --- a/src/components/View.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Components/View/View.js - */ -import React from 'react'; -import ViewAccessibility from './ViewAccessibility'; -import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; -import ViewPropTypes from '../propTypes/ViewPropTypes'; - -const { AccessibilityTraits, AccessibilityComponentTypes } = ViewAccessibility; - -const forceTouchAvailable = false; - -const statics = { - AccessibilityComponentType: AccessibilityComponentTypes, - AccessibilityTraits, - /** - * Is 3D Touch / Force Touch available (i.e. will touch events include `force`) - * @platform ios - */ - forceTouchAvailable, -}; - -const View = React.createClass({ - propTypes: ViewPropTypes, - - mixins: [NativeMethodsMixin], - - statics: { - ...statics, - }, - - render() { - return null; - }, -}); - -module.exports = View; diff --git a/src/components/ViewAccessibility.js b/src/components/ViewAccessibility.js deleted file mode 100644 index dc989b1..0000000 --- a/src/components/ViewAccessibility.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - AccessibilityTraits: [ - 'none', - 'button', - 'link', - 'header', - 'search', - 'image', - 'selected', - 'plays', - 'key', - 'text', - 'summary', - 'disabled', - 'frequentUpdates', - 'startsMedia', - 'adjustable', - 'allowsDirectInteraction', - 'pageTurn', - ], - AccessibilityComponentTypes: [ - 'none', - 'button', - 'radiobutton_checked', - 'radiobutton_unchecked', - ], -}; diff --git a/src/components/WebView.js b/src/components/WebView.js deleted file mode 100644 index b4aac8b..0000000 --- a/src/components/WebView.js +++ /dev/null @@ -1,145 +0,0 @@ -import EdgeInsetsPropType from '../propTypes/EdgeInsetsPropType'; -import React from 'react'; -import View from './View'; -import ScrollView from './ScrollView'; -import WebViewManager from '../NativeModules/WebViewManager'; - -const { PropTypes } = React; - -const RCT_WEBVIEW_REF = 'webview'; - -const NavigationType = { - click: WebViewManager.NavigationType.LinkClicked, - formsubmit: WebViewManager.NavigationType.FormSubmitted, - backforward: WebViewManager.NavigationType.BackForward, - reload: WebViewManager.NavigationType.Reload, - formresubmit: WebViewManager.NavigationType.FormResubmitted, - other: WebViewManager.NavigationType.Other, -}; - -const JSNavigationScheme = WebViewManager.JSNavigationScheme; - -const WebView = React.createClass({ - propTypes: { - ...View.propTypes, - url: PropTypes.string, - html: PropTypes.string, - /** - * Function that returns a view to show if there's an error. - */ - renderError: PropTypes.func, // view to show if there's an error - /** - * Function that returns a loading indicator. - */ - renderLoading: PropTypes.func, - /** - * Invoked when load finish - */ - onLoad: PropTypes.func, - /** - * Invoked when load either succeeds or fails - */ - onLoadEnd: PropTypes.func, - /** - * Invoked on load start - */ - onLoadStart: PropTypes.func, - /** - * Invoked when load fails - */ - onError: PropTypes.func, - /** - * @platform ios - */ - bounces: PropTypes.bool, - /** - * A floating-point number that determines how quickly the scroll view - * decelerates after the user lifts their finger. You may also use string - * shortcuts `"normal"` and `"fast"` which match the underlying iOS settings - * for `UIScrollViewDecelerationRateNormal` and - * `UIScrollViewDecelerationRateFast` respectively. - * - Normal: 0.998 - * - Fast: 0.9 (the default for iOS WebView) - * @platform ios - */ - decelerationRate: ScrollView.propTypes.decelerationRate, - /** - * @platform ios - */ - scrollEnabled: PropTypes.bool, - automaticallyAdjustContentInsets: PropTypes.bool, - contentInset: EdgeInsetsPropType, - onNavigationStateChange: PropTypes.func, - startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load - style: View.propTypes.style, - - /** - * Used on Android only, JS is enabled by default for WebView on iOS - * @platform android - */ - javaScriptEnabled: PropTypes.bool, - - /** - * Used on Android only, controls whether DOM Storage is enabled or not - * @platform android - */ - domStorageEnabled: PropTypes.bool, - - /** - * Sets the JS to be injected when the webpage loads. - */ - injectedJavaScript: PropTypes.string, - - /** - * Sets whether the webpage scales to fit the view and the user can change the scale. - * @platform ios - */ - scalesPageToFit: PropTypes.bool, - - /** - * Allows custom handling of any webview requests by a JS handler. Return true - * or false from this method to continue loading the request. - * @platform ios - */ - onShouldStartLoadWithRequest: PropTypes.func, - - /** - * Determines whether HTML5 videos play inline or use the native full-screen - * controller. - * default value `false` - * **NOTE** : "In order for video to play inline, not only does this - * property need to be set to true, but the video element in the HTML - * document must also include the webkit-playsinline attribute." - * @platform ios - */ - allowsInlineMediaPlayback: PropTypes.bool, - }, - - statics: { - JSNavigationScheme, - NavigationType, - }, - - getWebViewHandle() { - // TODO(lmr): React.findNodeHandle - return React.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); - }, - - reload() { - // do nothing - }, - - goForward() { - // do nothing - }, - - goBack() { - // do nothing - }, - - render() { - return null; - }, -}); - -module.exports = WebView; diff --git a/src/components/createMockComponent.js b/src/components/createMockComponent.js deleted file mode 100644 index f7b1a32..0000000 --- a/src/components/createMockComponent.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -function createMockComponent(displayName) { - return React.createClass({ - displayName, - render() { - return null; - }, - }); -} - -module.exports = createMockComponent; diff --git a/src/createMockComponent.js b/src/createMockComponent.js new file mode 100644 index 0000000..f31117f --- /dev/null +++ b/src/createMockComponent.js @@ -0,0 +1,33 @@ +import React from 'react'; + +export default (name) => { + const RealComponent = require(name); + const realComponentName = RealComponent.name === 'Component' ? name : RealComponent.name; + const componentName = (RealComponent.displayName || realComponentName || name).replace(/^(RCT|RK)/, ''); + + const Component = class extends RealComponent { // eslint-disable-line react/prefer-stateless-function + render() { + return React.createElement( + componentName, + this.props, + this.props.children + ); + } + }; + + Component.displayName = componentName; + Component.name = componentName; + + return Component; +}; + +export const MOCK_COMPONENTS = [ + 'Image', + 'Text', + 'TextInput', + 'Modal', + 'View', + 'ActivityIndicator', + 'RefreshControl', + 'ScrollView' +]; diff --git a/src/defineGlobalProperty.js b/src/defineGlobalProperty.js index b849756..c307e13 100644 --- a/src/defineGlobalProperty.js +++ b/src/defineGlobalProperty.js @@ -1,8 +1,6 @@ -function defineGlobalProperty(name, value) { +export default function defineGlobalProperty(name, value) { Object.defineProperty(global, name, { configurable: true, - value: value(), + value }); } - -export default defineGlobalProperty; diff --git a/src/haste.js b/src/haste.js new file mode 100644 index 0000000..4be4cf8 --- /dev/null +++ b/src/haste.js @@ -0,0 +1,53 @@ +/* eslint-disable no-var */ + +var fs = require('fs'); +var glob = require('glob'); +var path = require('path'); +var _ = require('underscore'); +var perfy = require('perfy'); + +var providesRegex = /\r?\n \* @providesModule (\S+)(?=\r?\n)/; +var validName = /^[a-z0-9-_].+$/i; +var iosTest = /ios/gi; +var androidTest = /android/gi; +var CWD = process.cwd(); +var PROJECT_ROOT = path.join(CWD, '..', '..'); +var PROJECT_NODE_MODULES = path.join(PROJECT_ROOT, 'node_modules'); +var TIMER = 'time'; + +if (!fs.existsSync(PROJECT_NODE_MODULES)) { + PROJECT_NODE_MODULES = path.join(CWD, 'node_modules'); // For tests +} + +perfy.start(TIMER); + +var data = { + hasteMap: {}, + version: require('./react-native-version') +}; + +var files = glob.sync(path.join(PROJECT_NODE_MODULES, '**/*.js'), { + nodir: true +}); + +_.forEach(files, function (file) { + var matches = providesRegex.exec(fs.readFileSync(file).toString()); + if (matches && validName.test(matches[1])) { + var component = matches[1]; + if (component.match(iosTest) && file.endsWith('.android.js')) { // Dont add IOS components if they end in android.js + return; + } + if (component.match(androidTest) && file.endsWith('.ios.js')) { // Dont add Android components if they end in ios.js + return; + } + data.hasteMap[component] = file.replace(PROJECT_NODE_MODULES + '/', ''); + } +}); + +fs.writeFileSync(path.join(CWD, 'haste-map.json'), JSON.stringify(data, null, 2)); + +var results = perfy.end(TIMER); + +if (process.env.NODE_ENV !== 'test') { + console.log(results.summary); // eslint-disable-line no-console +} diff --git a/src/image-compiler.js b/src/image-compiler.js new file mode 100644 index 0000000..7557275 --- /dev/null +++ b/src/image-compiler.js @@ -0,0 +1,16 @@ +/* + Thanks to @zhaotai and @miracle2k for this! + https://github.com/RealOrangeOne/react-native-mock/issues/11#issuecomment-230435835s +*/ + +const m = require('module'); + +const originalLoader = m._load; + +m._load = function hookedLoader(request, parent, isMain) { + if (request.match(/.jpeg|.jpg|.png$/)) { + return { uri: request }; + } + + return originalLoader(request, parent, isMain); +}; diff --git a/src/mixins/NativeMethodsMixin.js b/src/mixins/NativeMethodsMixin.js deleted file mode 100644 index 47e6cf6..0000000 --- a/src/mixins/NativeMethodsMixin.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/ReactIOS/NativeMethodsMixin.js - */ -module.exports = { - measure(callback) { - - }, - measureLayout(relativeToNativeNode, onSuccess, onFail) { - - }, - setNativeProps(nativeProps) { - - }, - focus() { - - }, - blur() { - - }, -}; diff --git a/src/mixins/ScrollResponder.js b/src/mixins/ScrollResponder.js deleted file mode 100644 index afea6ef..0000000 --- a/src/mixins/ScrollResponder.js +++ /dev/null @@ -1,509 +0,0 @@ -import ScrollViewManager from '../NativeModules/ScrollViewManager'; -import Platform from '../plugins/Platform'; -import Dimensions from '../api/Dimensions'; -import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; -import React from 'react'; -import invariant from 'invariant'; -import warning from 'warning'; -import TextInputState from '../api/TextInputState'; -import Subscribable from './Subscribable'; -import UIManager from '../NativeModules/UIManager'; - -/** - * Mixin that can be integrated in order to handle scrolling that plays well - * with `ResponderEventPlugin`. Integrate with your platform specific scroll - * views, or even your custom built (every-frame animating) scroll views so that - * all of these systems play well with the `ResponderEventPlugin`. - * - * iOS scroll event timing nuances: - * =============================== - * - * - * Scrolling without bouncing, if you touch down: - * ------------------------------- - * - * 1. `onMomentumScrollBegin` (when animation begins after letting up) - * ... physical touch starts ... - * 2. `onTouchStartCapture` (when you press down to stop the scroll) - * 3. `onTouchStart` (same, but bubble phase) - * 4. `onResponderRelease` (when lifting up - you could pause forever before * lifting) - * 5. `onMomentumScrollEnd` - * - * - * Scrolling with bouncing, if you touch down: - * ------------------------------- - * - * 1. `onMomentumScrollBegin` (when animation begins after letting up) - * ... bounce begins ... - * ... some time elapses ... - * ... physical touch during bounce ... - * 2. `onMomentumScrollEnd` (Makes no sense why this occurs first during bounce) - * 3. `onTouchStartCapture` (immediately after `onMomentumScrollEnd`) - * 4. `onTouchStart` (same, but bubble phase) - * 5. `onTouchEnd` (You could hold the touch start for a long time) - * 6. `onMomentumScrollBegin` (When releasing the view starts bouncing back) - * - * So when we receive an `onTouchStart`, how can we tell if we are touching - * *during* an animation (which then causes the animation to stop)? The only way - * to tell is if the `touchStart` occurred immediately after the - * `onMomentumScrollEnd`. - * - * This is abstracted out for you, so you can just call this.scrollResponderIsAnimating() if - * necessary - * - * `ScrollResponder` also includes logic for blurring a currently focused input - * if one is focused while scrolling. The `ScrollResponder` is a natural place - * to put this logic since it can support not dismissing the keyboard while - * scrolling, unless a recognized "tap"-like gesture has occurred. - * - * The public lifecycle API includes events for keyboard interaction, responder - * interaction, and scrolling (among others). The keyboard callbacks - * `onKeyboardWill/Did/*` are *global* events, but are invoked on scroll - * responder's props so that you can guarantee that the scroll responder's - * internal state has been updated accordingly (and deterministically) by - * the time the props callbacks are invoke. Otherwise, you would always wonder - * if the scroll responder is currently in a state where it recognizes new - * keyboard positions etc. If coordinating scrolling with keyboard movement, - * *always* use these hooks instead of listening to your own global keyboard - * events. - * - * Public keyboard lifecycle API: (props callbacks) - * - * Standard Keyboard Appearance Sequence: - * - * this.props.onKeyboardWillShow - * this.props.onKeyboardDidShow - * - * `onScrollResponderKeyboardDismissed` will be invoked if an appropriate - * tap inside the scroll responder's scrollable region was responsible - * for the dismissal of the keyboard. There are other reasons why the - * keyboard could be dismissed. - * - * this.props.onScrollResponderKeyboardDismissed - * - * Standard Keyboard Hide Sequence: - * - * this.props.onKeyboardWillHide - * this.props.onKeyboardDidHide - */ - -const IS_ANIMATING_TOUCH_START_THRESHOLD_MS = 16; - -const ScrollResponderMixin = { - mixins: [Subscribable.Mixin], - scrollResponderMixinGetInitialState() { - return { - isTouching: false, - lastMomentumScrollBeginTime: 0, - lastMomentumScrollEndTime: 0, - - // Reset to false every time becomes responder. This is used to: - // - Determine if the scroll view has been scrolled and therefore should - // refuse to give up its responder lock. - // - Determine if releasing should dismiss the keyboard when we are in - // tap-to-dismiss mode (!this.props.keyboardShouldPersistTaps). - observedScrollSinceBecomingResponder: false, - becameResponderWhileAnimating: false, - }; - }, - - /** - * Invoke this from an `onScroll` event. - */ - scrollResponderHandleScrollShouldSetResponder() { - return this.state.isTouching; - }, - - /** - * Merely touch starting is not sufficient for a scroll view to become the - * responder. Being the "responder" means that the very next touch move/end - * event will result in an action/movement. - * - * Invoke this from an `onStartShouldSetResponder` event. - * - * `onStartShouldSetResponder` is used when the next move/end will trigger - * some UI movement/action, but when you want to yield priority to views - * nested inside of the view. - * - * There may be some cases where scroll views actually should return `true` - * from `onStartShouldSetResponder`: Any time we are detecting a standard tap - * that gives priority to nested views. - * - * - If a single tap on the scroll view triggers an action such as - * recentering a map style view yet wants to give priority to interaction - * views inside (such as dropped pins or labels), then we would return true - * from this method when there is a single touch. - * - * - Similar to the previous case, if a two finger "tap" should trigger a - * zoom, we would check the `touches` count, and if `>= 2`, we would return - * true. - * - */ - scrollResponderHandleStartShouldSetResponder() { - return false; - }, - - /** - * There are times when the scroll view wants to become the responder - * (meaning respond to the next immediate `touchStart/touchEnd`), in a way - * that *doesn't* give priority to nested views (hence the capture phase): - * - * - Currently animating. - * - Tapping anywhere that is not the focused input, while the keyboard is - * up (which should dismiss the keyboard). - * - * Invoke this from an `onStartShouldSetResponderCapture` event. - */ - scrollResponderHandleStartShouldSetResponderCapture(e) { - // First see if we want to eat taps while the keyboard is up - const currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); - if (!this.props.keyboardShouldPersistTaps && - currentlyFocusedTextInput != null && - e.target !== currentlyFocusedTextInput) { - return true; - } - return this.scrollResponderIsAnimating(); - }, - - /** - * Invoke this from an `onResponderReject` event. - * - * Some other element is not yielding its role as responder. Normally, we'd - * just disable the `UIScrollView`, but a touch has already began on it, the - * `UIScrollView` will not accept being disabled after that. The easiest - * solution for now is to accept the limitation of disallowing this - * altogether. To improve this, find a way to disable the `UIScrollView` after - * a touch has already started. - */ - scrollResponderHandleResponderReject() { - warning(false, "ScrollView doesn't take rejection well - scrolls anyway"); - }, - - /** - * We will allow the scroll view to give up its lock iff it acquired the lock - * during an animation. This is a very useful default that happens to satisfy - * many common user experiences. - * - * - Stop a scroll on the left edge, then turn that into an outer view's - * backswipe. - * - Stop a scroll mid-bounce at the top, continue pulling to have the outer - * view dismiss. - * - However, without catching the scroll view mid-bounce (while it is - * motionless), if you drag far enough for the scroll view to become - * responder (and therefore drag the scroll view a bit), any backswipe - * navigation of a swipe gesture higher in the view hierarchy, should be - * rejected. - */ - scrollResponderHandleTerminationRequest() { - return !this.state.observedScrollSinceBecomingResponder; - }, - - /** - * Invoke this from an `onTouchEnd` event. - * - * @param {SyntheticEvent} e Event. - */ - scrollResponderHandleTouchEnd(e) { - const nativeEvent = e.nativeEvent; - this.state.isTouching = nativeEvent.touches.length !== 0; - if (this.props.onTouchEnd) { - this.props.onTouchEnd(e); - } - }, - - /** - * Invoke this from an `onResponderRelease` event. - */ - scrollResponderHandleResponderRelease(e) { - if (this.props.onResponderRelease) { - this.props.onResponderRelease(e); - } - - // By default scroll views will unfocus a textField - // if another touch occurs outside of it - const currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); - if (!this.props.keyboardShouldPersistTaps && - currentlyFocusedTextInput != null && - e.target !== currentlyFocusedTextInput && - !this.state.observedScrollSinceBecomingResponder && - !this.state.becameResponderWhileAnimating) { - if (this.props.onScrollResponderKeyboardDismissed) { - this.props.onScrollResponderKeyboardDismissed(e); - } - TextInputState.blurTextInput(currentlyFocusedTextInput); - } - }, - - scrollResponderHandleScroll(e) { - this.state.observedScrollSinceBecomingResponder = true; - if (this.props.onScroll) { - this.props.onScroll(e); - } - }, - - /** - * Invoke this from an `onResponderGrant` event. - */ - scrollResponderHandleResponderGrant(e) { - this.state.observedScrollSinceBecomingResponder = false; - if (this.props.onResponderGrant) { - this.props.onResponderGrant(e); - } - this.state.becameResponderWhileAnimating = this.scrollResponderIsAnimating(); - }, - - /** - * Unfortunately, `onScrollBeginDrag` also fires when *stopping* the scroll - * animation, and there's not an easy way to distinguish a drag vs. stopping - * momentum. - * - * Invoke this from an `onScrollBeginDrag` event. - */ - scrollResponderHandleScrollBeginDrag(e) { - if (this.props.onScrollBeginDrag) { - this.props.onScrollBeginDrag(e); - } - }, - - /** - * Invoke this from an `onScrollEndDrag` event. - */ - scrollResponderHandleScrollEndDrag(e) { - if (this.props.onScrollEndDrag) { - this.props.onScrollEndDrag(e); - } - }, - - /** - * Invoke this from an `onMomentumScrollBegin` event. - */ - scrollResponderHandleMomentumScrollBegin(e) { - this.state.lastMomentumScrollBeginTime = Date.now(); - if (this.props.onMomentumScrollBegin) { - this.props.onMomentumScrollBegin(e); - } - }, - - /** - * Invoke this from an `onMomentumScrollEnd` event. - */ - scrollResponderHandleMomentumScrollEnd(e) { - this.state.lastMomentumScrollEndTime = Date.now(); - if (this.props.onMomentumScrollEnd) { - this.props.onMomentumScrollEnd(e); - } - }, - - /** - * Invoke this from an `onTouchStart` event. - * - * Since we know that the `SimpleEventPlugin` occurs later in the plugin - * order, after `ResponderEventPlugin`, we can detect that we were *not* - * permitted to be the responder (presumably because a contained view became - * responder). The `onResponderReject` won't fire in that case - it only - * fires when a *current* responder rejects our request. - * - * @param {SyntheticEvent} e Touch Start event. - */ - scrollResponderHandleTouchStart(e) { - this.state.isTouching = true; - if (this.props.onTouchStart) { - this.props.onTouchStart(e); - } - }, - - /** - * Invoke this from an `onTouchMove` event. - * - * Since we know that the `SimpleEventPlugin` occurs later in the plugin - * order, after `ResponderEventPlugin`, we can detect that we were *not* - * permitted to be the responder (presumably because a contained view became - * responder). The `onResponderReject` won't fire in that case - it only - * fires when a *current* responder rejects our request. - * - * @param {SyntheticEvent} e Touch Start event. - */ - scrollResponderHandleTouchMove(e) { - if (this.props.onTouchMove) { - this.props.onTouchMove(e); - } - }, - - /** - * A helper function for this class that lets us quickly determine if the - * view is currently animating. This is particularly useful to know when - * a touch has just started or ended. - */ - scrollResponderIsAnimating() { - const now = Date.now(); - const timeSinceLastMomentumScrollEnd = now - this.state.lastMomentumScrollEndTime; - const isAnimating = timeSinceLastMomentumScrollEnd < IS_ANIMATING_TOUCH_START_THRESHOLD_MS || - this.state.lastMomentumScrollEndTime < this.state.lastMomentumScrollBeginTime; - return isAnimating; - }, - - /** - * A helper function to scroll to a specific point in the scrollview. - * This is currently used to help focus on child textviews, but this - * can also be used to quickly scroll to any element we want to focus - */ - scrollResponderScrollTo(offsetX, offsetY, animated = true) { - - }, - - /** - * A helper function to zoom to a specific rect in the scrollview. - * @param {object} rect Should have shape {x, y, width, height} - * @param {bool} animated Specify whether zoom is instant or animated - */ - scrollResponderZoomTo(rect, animated = true) { - if (Platform.OS === 'android') { - invariant('zoomToRect is not implemented'); - } else { - ScrollViewManager.zoomToRect(React.findNodeHandle(this), rect, animated); - } - }, - - /** - * This method should be used as the callback to onFocus in a TextInputs' - * parent view. Note that any module using this mixin needs to return - * the parent view's ref in getScrollViewRef() in order to use this method. - * @param {any} nodeHandle The TextInput node handle - * @param {number} additionalOffset The scroll view's top "contentInset". - * Default is 0. - * @param {bool} preventNegativeScrolling Whether to allow pulling the content - * down to make it meet the keyboard's top. Default is false. - */ - scrollResponderScrollNativeHandleToKeyboard( - nodeHandle, - additionalOffset, - preventNegativeScrollOffset) { - this.additionalScrollOffset = additionalOffset || 0; - this.preventNegativeScrollOffset = !!preventNegativeScrollOffset; - UIManager.measureLayout( - nodeHandle, - React.findNodeHandle(this.getInnerViewNode()), - this.scrollResponderTextInputFocusError, - this.scrollResponderInputMeasureAndScrollToKeyboard - ); - }, - - /** - * The calculations performed here assume the scroll view takes up the entire - * screen - even if has some content inset. We then measure the offsets of the - * keyboard, and compensate both for the scroll view's "contentInset". - * - * @param {number} left Position of input w.r.t. table view. - * @param {number} top Position of input w.r.t. table view. - * @param {number} width Width of the text input. - * @param {number} height Height of the text input. - */ - scrollResponderInputMeasureAndScrollToKeyboard(left, top, width, height) { - let keyboardScreenY = Dimensions.get('window').height; - if (this.keyboardWillOpenTo) { - keyboardScreenY = this.keyboardWillOpenTo.endCoordinates.screenY; - } - let scrollOffsetY = top - keyboardScreenY + height + this.additionalScrollOffset; - - // By default, this can scroll with negative offset, pulling the content - // down so that the target component's bottom meets the keyboard's top. - // If requested otherwise, cap the offset at 0 minimum to avoid content - // shifting down. - if (this.preventNegativeScrollOffset) { - scrollOffsetY = Math.max(0, scrollOffsetY); - } - this.scrollResponderScrollTo(0, scrollOffsetY); - - this.additionalOffset = 0; - this.preventNegativeScrollOffset = false; - }, - - scrollResponderTextInputFocusError(e) { - console.error('Error measuring text field: ', e); - }, - - /** - * `componentWillMount` is the closest thing to a standard "constructor" for - * React components. - * - * The `keyboardWillShow` is called before input focus. - */ - componentWillMount() { - this.keyboardWillOpenTo = null; - this.additionalScrollOffset = 0; - this.addListenerOn( - DeviceEventEmitter, 'keyboardWillShow', this.scrollResponderKeyboardWillShow - ); - this.addListenerOn( - DeviceEventEmitter, 'keyboardWillHide', this.scrollResponderKeyboardWillHide - ); - this.addListenerOn(DeviceEventEmitter, 'keyboardDidShow', this.scrollResponderKeyboardDidShow); - this.addListenerOn(DeviceEventEmitter, 'keyboardDidHide', this.scrollResponderKeyboardDidHide); - }, - - /** - * Warning, this may be called several times for a single keyboard opening. - * It's best to store the information in this method and then take any action - * at a later point (either in `keyboardDidShow` or other). - * - * Here's the order that events occur in: - * - focus - * - willShow {startCoordinates, endCoordinates} several times - * - didShow several times - * - blur - * - willHide {startCoordinates, endCoordinates} several times - * - didHide several times - * - * The `ScrollResponder` providesModule callbacks for each of these events. - * Even though any user could have easily listened to keyboard events - * themselves, using these `props` callbacks ensures that ordering of events - * is consistent - and not dependent on the order that the keyboard events are - * subscribed to. This matters when telling the scroll view to scroll to where - * the keyboard is headed - the scroll responder better have been notified of - * the keyboard destination before being instructed to scroll to where the - * keyboard will be. Stick to the `ScrollResponder` callbacks, and everything - * will work. - * - * WARNING: These callbacks will fire even if a keyboard is displayed in a - * different navigation pane. Filter out the events to determine if they are - * relevant to you. (For example, only if you receive these callbacks after - * you had explicitly focused a node etc). - */ - scrollResponderKeyboardWillShow(e) { - this.keyboardWillOpenTo = e; - if (this.props.onKeyboardWillShow) { - this.props.onKeyboardWillShow(e); - } - }, - - scrollResponderKeyboardWillHide(e) { - this.keyboardWillOpenTo = null; - if (this.props.onKeyboardWillHide) { - this.props.onKeyboardWillHide(e); - } - }, - - scrollResponderKeyboardDidShow(e) { - // TODO(7693961): The event for DidShow is not available on iOS yet. - // Use the one from WillShow and do not assign. - if (e) { - this.keyboardWillOpenTo = e; - } - if (this.props.onKeyboardDidShow) { - this.props.onKeyoardDidShow(e); - } - }, - - scrollResponderKeyboardDidHide(e) { - this.keyboardWillOpenTo = null; - if (this.props.onKeyboardDidHide) { - this.props.onKeyboardDidHide(e); - } - } -}; - -const ScrollResponder = { - Mixin: ScrollResponderMixin, -}; - -module.exports = ScrollResponder; diff --git a/src/mixins/Subscribable.js b/src/mixins/Subscribable.js deleted file mode 100644 index 7bdc5aa..0000000 --- a/src/mixins/Subscribable.js +++ /dev/null @@ -1,39 +0,0 @@ - -const SubscribableMixin = { - - componentWillMount() { - this._subscribableSubscriptions = []; - }, - - componentWillUnmount() { - this._subscribableSubscriptions.forEach( - (subscription) => subscription.remove() - ); - this._subscribableSubscriptions = null; - }, - - /** - * Special form of calling `addListener` that *guarantees* that a - * subscription *must* be tied to a component instance, and therefore will - * be cleaned up when the component is unmounted. It is impossible to create - * the subscription and pass it in - this method must be the one to create - * the subscription and therefore can guarantee it is retained in a way that - * will be cleaned up. - * - * @param {EventEmitter} eventEmitter emitter to subscribe to. - * @param {string} eventType Type of event to listen to. - * @param {function} listener Function to invoke when event occurs. - * @param {object} context Object to use as listener context. - */ - addListenerOn(eventEmitter, eventType, listener, context) { - this._subscribableSubscriptions.push( - eventEmitter.addListener(eventType, listener, context) - ); - } -}; - -const Subscribable = { - Mixin: SubscribableMixin, -}; - -module.exports = Subscribable; diff --git a/src/mocks/AsyncStorage.js b/src/mocks/AsyncStorage.js new file mode 100644 index 0000000..b8e58e0 --- /dev/null +++ b/src/mocks/AsyncStorage.js @@ -0,0 +1,34 @@ +const AsyncStorage = require('AsyncStorage'); // eslint-disable-line import/no-unresolved + +let _data = {}; + +AsyncStorage.getItem = function (key, callback) { + return new Promise(function (resolve, reject) { + const foundData = _data[key]; + if (foundData === undefined) { + callback && callback(); + reject(); + } else { + callback && callback(null, foundData); + resolve(foundData); + } + }); +}; + +AsyncStorage.setItem = function (key, value, callback) { + return new Promise(function (resolve, reject) { + _data[key] = value; + callback && callback(); + resolve(); + }); +}; + +AsyncStorage.clear = function (callback) { + return new Promise(function (resolve, reject) { + _data = {}; + callback && callback(); + resolve(); + }); +}; + +module.exports = AsyncStorage; diff --git a/src/mocks/ErrorUtils.js b/src/mocks/ErrorUtils.js new file mode 100644 index 0000000..0407dec --- /dev/null +++ b/src/mocks/ErrorUtils.js @@ -0,0 +1,18 @@ +import sinon from 'sinon'; + +function execute(fun, context, args) { + return fun.apply(context, args); +} + +function reportError(error) { + throw error; +} + +module.exports = { + apply: execute, + applyWithGuard: execute, + guard: sinon.spy(callback => callback), + inGuard: sinon.spy(() => true), + reportError, + setGlobalHandler: sinon.spy(() => undefined) +}; diff --git a/src/mocks/ListView.js b/src/mocks/ListView.js new file mode 100644 index 0000000..e2d96f5 --- /dev/null +++ b/src/mocks/ListView.js @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of react-native. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +/* eslint-disable react/prop-types */ + +const React = require('react'); +const ScrollView = require('ScrollView'); // eslint-disable-line import/no-unresolved +const StaticRenderer = require('StaticRenderer'); // eslint-disable-line import/no-unresolved +const View = require('View'); // eslint-disable-line import/no-unresolved + +class ListViewMock extends React.Component { + componentDidMount() { + ListViewMock.latestRef = this; + } + + render() { + const { dataSource, renderFooter, renderHeader } = this.props; + const rows = [renderHeader && renderHeader()]; + const allRowIDs = dataSource.rowIdentities; + for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx += 1) { + const sectionID = dataSource.sectionIdentities[sectionIdx]; + const rowIDs = allRowIDs[sectionIdx]; + for (let rowIdx = 0; rowIdx < rowIDs.length; rowIdx += 1) { + const rowID = rowIDs[rowIdx]; + rows.push(); + } + } + + renderFooter && rows.push(renderFooter()); + + return ( + + {this.props.renderScrollComponent({ children: rows })} + + ); + } +} + +ListViewMock.defaultProps = { + renderScrollComponent: props => +}; + +ListViewMock.DataSource = require('ListViewDataSource'); // eslint-disable-line import/no-unresolved + +module.exports = ListViewMock; diff --git a/src/mocks/ListViewDataSource.js b/src/mocks/ListViewDataSource.js new file mode 100644 index 0000000..fcbda9b --- /dev/null +++ b/src/mocks/ListViewDataSource.js @@ -0,0 +1,20 @@ +const DataSource = require('ListViewDataSource'); // eslint-disable-line import/no-unresolved + +DataSource.prototype.toJSON = function () { + function ListViewDataSource(dataBlob) { + this.items = 0; + // Ensure this doesn't throw. + try { + Object.keys(dataBlob).forEach((key) => { + this.items += dataBlob[key] && ( + dataBlob[key].length || dataBlob[key].size || 0 + ); + }); + } catch (e) { + this.items = 'unknown'; + } + } + return new ListViewDataSource(this._dataBlob); +}; + +module.exports = DataSource; diff --git a/src/no-import.js b/src/no-import.js new file mode 100644 index 0000000..fd87bfd --- /dev/null +++ b/src/no-import.js @@ -0,0 +1,8 @@ +/* eslint-disable no-console */ + +// eslint-disable-next-line no-throw-literal +throw ` +Looks like you're trying to require react-native-mock directly. +You dont need to do this to access the mocked version of react-native. +Please re-check the instructions in the README for how to use it correctly! +`; diff --git a/src/plugins/DeviceEventEmitter.js b/src/plugins/DeviceEventEmitter.js deleted file mode 100644 index b04f78d..0000000 --- a/src/plugins/DeviceEventEmitter.js +++ /dev/null @@ -1,5 +0,0 @@ -const EventEmitter = require('events').EventEmitter; - -const DeviceEventEmitter = new EventEmitter(); - -module.exports = DeviceEventEmitter; diff --git a/src/plugins/NativeAppEventEmitter.js b/src/plugins/NativeAppEventEmitter.js deleted file mode 100644 index e58c8b2..0000000 --- a/src/plugins/NativeAppEventEmitter.js +++ /dev/null @@ -1,5 +0,0 @@ -const EventEmitter = require('events').EventEmitter; - -const NativeAppEventEmitter = new EventEmitter(); - -module.exports = NativeAppEventEmitter; diff --git a/src/plugins/Platform.js b/src/plugins/Platform.js deleted file mode 100644 index e24ad72..0000000 --- a/src/plugins/Platform.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Utilities/Platform.android.js - */ -const Platform = { - OS: 'ios', - Version: undefined, - - /** - * Exposed in react-native-mock for testing purposes. Not part of real API. - */ - __setOS(os) { - Platform.OS = os; - }, - - select(objs) { - return objs[Platform.OS]; - }, - - /** - * Exposed in react-native-mock for testing purposes. Not part of real API. - */ - __setVersion(version) { - Platform.Version = version; - }, -}; - -module.exports = Platform; diff --git a/src/plugins/processColor.js b/src/plugins/processColor.js deleted file mode 100644 index f43fee9..0000000 --- a/src/plugins/processColor.js +++ /dev/null @@ -1,6 +0,0 @@ -function processColor() { - // TODO(lmr): seems like a good example of something we should pull directly from RN. - return 0; -} - -module.exports = processColor; diff --git a/src/plugins/requireNativeComponent.js b/src/plugins/requireNativeComponent.js deleted file mode 100644 index 6a35800..0000000 --- a/src/plugins/requireNativeComponent.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/ReactIOS/requireNativeComponent.js - */ -import React from 'react'; - -function requireNativeComponent(viewName, componentInterface, extraConfig) { - return React.createClass({ - displayName: viewName, - render() { - return null; - }, - }); -} - -module.exports = requireNativeComponent; diff --git a/src/propTypes/ColorPropType.js b/src/propTypes/ColorPropType.js deleted file mode 100644 index 6a0e1fb..0000000 --- a/src/propTypes/ColorPropType.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/ColorPropType.js - */ -const ColorPropType = function (props, propName) { - const color = props[propName]; - if (color === undefined || color === null) { - // return; - } - - if (typeof color === 'number') { - // Developers should not use a number, but we are using the prop type - // both for user provided colors and for transformed ones. This isn't ideal - // and should be fixed but will do for now... - // return; - } - - // TODO(lmr): test color -}; - -module.exports = ColorPropType; diff --git a/src/propTypes/EdgeInsetsPropType.js b/src/propTypes/EdgeInsetsPropType.js deleted file mode 100644 index 14cd25a..0000000 --- a/src/propTypes/EdgeInsetsPropType.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/EdgeInsetsPropType.js - */ -import React from 'react'; - -const { PropTypes } = React; - -const EdgeInsetsPropType = PropTypes.shape({ - top: PropTypes.number, - left: PropTypes.number, - bottom: PropTypes.number, - right: PropTypes.number, -}); - -module.exports = EdgeInsetsPropType; diff --git a/src/propTypes/ImageResizeMode.js b/src/propTypes/ImageResizeMode.js deleted file mode 100644 index 055321c..0000000 --- a/src/propTypes/ImageResizeMode.js +++ /dev/null @@ -1,13 +0,0 @@ -import Platfrom from '../plugins/Platform'; - -const resizePropTypes = [ - 'contain', - 'cover', - 'stretch', -]; - -if (Platfrom.OS === 'ios') { - resizePropTypes.push('repeat', 'center'); -} - -module.exports = resizePropTypes; diff --git a/src/propTypes/ImageStylePropTypes.js b/src/propTypes/ImageStylePropTypes.js deleted file mode 100644 index 2522dea..0000000 --- a/src/propTypes/ImageStylePropTypes.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Image/ImageStylePropTypes.js - */ -import React from 'react'; -import ColorPropType from './ColorPropType'; -import TransformPropTypes from './TransformPropTypes'; -import ShadowPropTypesIOS from './ShadowPropTypesIOS'; -import LayoutPropTypes from './LayoutPropTypes'; -import ImageResizeMode from './ImageResizeMode'; - -const { PropTypes } = React; - -const ImageStylePropTypes = { - ...LayoutPropTypes, - ...ShadowPropTypesIOS, - ...TransformPropTypes, - resizeMode: PropTypes.oneOf(ImageResizeMode), - backfaceVisibility: PropTypes.oneOf(['visible', 'hidden']), - backgroundColor: ColorPropType, - borderColor: ColorPropType, - borderWidth: PropTypes.number, - borderRadius: PropTypes.number, - overflow: PropTypes.oneOf(['visible', 'hidden']), - - /** - * iOS-Specific style to "tint" an image. - * Changes the color of all the non-transparent pixels to the tintColor. - * @platform ios - */ - tintColor: ColorPropType, - opacity: PropTypes.number, - /** - * When the image has rounded corners, specifying an overlayColor will - * cause the remaining space in the corners to be filled with a solid color. - * This is useful in cases which are not supported by the Android - * implementation of rounded corners: - * - Certain resize modes, such as 'contain' - * - Animated GIFs - * - * A typical way to use this prop is with images displayed on a solid - * background and setting the `overlayColor` to the same color - * as the background. - * - * For details of how this works under the hood, see - * http://frescolib.org/docs/rounded-corners-and-circles.html - * - * @platform android - */ - overlayColor: PropTypes.string, -}; - -module.exports = ImageStylePropTypes; diff --git a/src/propTypes/LayoutPropTypes.js b/src/propTypes/LayoutPropTypes.js deleted file mode 100644 index 020520f..0000000 --- a/src/propTypes/LayoutPropTypes.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/LayoutPropTypes.js - */ -import React from 'react'; - -const { PropTypes } = React; - -/** - * React Native's layout system is based on Flexbox and is powered both - * on iOS and Android by an open source project called css-layout: - * https://github.com/facebook/css-layout - * - * The implementation in css-layout is slightly different from what the - * Flexbox spec defines - for example, we chose more sensible default - * values. Please refer to the css-layout README for details. - * - * These properties are a subset of our styles that are consumed by the layout - * algorithm and affect the positioning and sizing of views. - */ -const LayoutPropTypes = { - width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - top: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - left: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - right: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - bottom: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - margin: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - marginVertical: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - marginHorizontal: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - marginTop: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - marginBottom: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - marginLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - marginRight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - padding: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - paddingVertical: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - paddingHorizontal: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - paddingTop: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - paddingBottom: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - paddingLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - paddingRight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - borderWidth: PropTypes.number, - borderTopWidth: PropTypes.number, - borderRightWidth: PropTypes.number, - borderBottomWidth: PropTypes.number, - borderLeftWidth: PropTypes.number, - - position: PropTypes.oneOf([ - 'absolute', - 'relative' - ]), - - // https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction - flexDirection: PropTypes.oneOf([ - 'row', - 'row-reverse', - 'column', - 'column-reverse' - ]), - - // https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap - flexWrap: PropTypes.oneOf([ - 'wrap', - 'nowrap' - ]), - - // How to align children in the main direction - // https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content - justifyContent: PropTypes.oneOf([ - 'flex-start', - 'flex-end', - 'center', - 'space-between', - 'space-around' - ]), - - // How to align children in the cross direction - // https://developer.mozilla.org/en-US/docs/Web/CSS/align-items - alignItems: PropTypes.oneOf([ - 'flex-start', - 'flex-end', - 'center', - 'stretch' - ]), - - // How to align the element in the cross direction - // https://developer.mozilla.org/en-US/docs/Web/CSS/align-items - alignSelf: PropTypes.oneOf([ - 'auto', - 'flex-start', - 'flex-end', - 'center', - 'stretch' - ]), - - // https://developer.mozilla.org/en-US/docs/Web/CSS/flex - flex: PropTypes.number, -}; - -module.exports = LayoutPropTypes; diff --git a/src/propTypes/PointPropType.js b/src/propTypes/PointPropType.js deleted file mode 100644 index e62e116..0000000 --- a/src/propTypes/PointPropType.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/PointPropType.js - */ -import React from 'react'; - -const { PropTypes } = React; - -const PointPropType = PropTypes.shape({ - x: PropTypes.number, - y: PropTypes.number, -}); - -module.exports = PointPropType; diff --git a/src/propTypes/ShadowPropTypesIOS.js b/src/propTypes/ShadowPropTypesIOS.js deleted file mode 100644 index 8899315..0000000 --- a/src/propTypes/ShadowPropTypesIOS.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import ColorPropType from './ColorPropType'; - -const { PropTypes } = React; - -const ShadowPropTypesIOS = { - /** - * Sets the drop shadow color - * @platform ios - */ - shadowColor: ColorPropType, - /** - * Sets the drop shadow offset - * @platform ios - */ - shadowOffset: PropTypes.shape({ - width: PropTypes.number, - height: PropTypes.number, - }), - /** - * Sets the drop shadow opacity (multiplied by the color's alpha component) - * @platform ios - */ - shadowOpacity: PropTypes.number, - /** - * Sets the drop shadow blur radius - * @platform ios - */ - shadowRadius: PropTypes.number, -}; - -module.exports = ShadowPropTypesIOS; diff --git a/src/propTypes/StyleSheetPropType.js b/src/propTypes/StyleSheetPropType.js deleted file mode 100644 index c244660..0000000 --- a/src/propTypes/StyleSheetPropType.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/StyleSheetPropType.js - */ -import React from 'react'; -import flattenStyle from './flattenStyle'; - -const { PropTypes } = React; - -function StyleSheetPropType(shape) { - const shapePropType = PropTypes.shape(shape); - return function (props, propName, componentName, ...rest) { - let newProps = props; - if (props[propName]) { - // Just make a dummy prop object with only the flattened style - newProps = {}; - newProps[propName] = flattenStyle(props[propName]); - } - return shapePropType(newProps, propName, componentName, ...rest); - }; -} - -module.exports = StyleSheetPropType; diff --git a/src/propTypes/TextStylePropTypes.js b/src/propTypes/TextStylePropTypes.js deleted file mode 100644 index 143f0cc..0000000 --- a/src/propTypes/TextStylePropTypes.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Text/TextStylePropTypes.js - */ -import React from 'react'; -import ColorPropType from './ColorPropType'; -import ViewStylePropTypes from './ViewStylePropTypes'; - -const { PropTypes } = React; - -// TODO: use spread instead of Object.assign/create after #6560135 is fixed -const TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), { - color: ColorPropType, - fontFamily: PropTypes.string, - fontSize: PropTypes.number, - fontStyle: PropTypes.oneOf(['normal', 'italic']), - /** - * Specifies font weight. The values 'normal' and 'bold' are supported for - * most fonts. Not all fonts have a variant for each of the numeric values, - * in that case the closest one is chosen. - */ - fontWeight: PropTypes.oneOf( - ['normal', 'bold', - '100', '200', '300', '400', '500', '600', '700', '800', '900'] - ), - textShadowOffset: PropTypes.shape( - { - width: PropTypes.number, - height: PropTypes.number - } - ), - textShadowRadius: PropTypes.number, - textShadowColor: ColorPropType, - /** - * @platform ios - */ - letterSpacing: PropTypes.number, - lineHeight: PropTypes.number, - /** - * Specifies text alignment. The value 'justify' is only supported on iOS. - */ - textAlign: PropTypes.oneOf( - ['auto', 'left', 'right', 'center', 'justify'] - ), - /** - * @platform android - */ - textAlignVertical: PropTypes.oneOf( - ['auto', 'top', 'bottom', 'center'] - ), - /** - * @platform ios - */ - textDecorationLine: PropTypes.oneOf( - ['none', 'underline', 'line-through', 'underline line-through'] - ), - /** - * @platform ios - */ - textDecorationStyle: PropTypes.oneOf( - ['solid', 'double', 'dotted', 'dashed'] - ), - /** - * @platform ios - */ - textDecorationColor: ColorPropType, - /** - * @platform ios - */ - writingDirection: PropTypes.oneOf( - ['auto', 'ltr', 'rtl'] - ), -}); - -module.exports = TextStylePropTypes; diff --git a/src/propTypes/TransformPropTypes.js b/src/propTypes/TransformPropTypes.js deleted file mode 100644 index b49bfe8..0000000 --- a/src/propTypes/TransformPropTypes.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/TransformPropTypes.js - */ -import React from 'react'; - -const { PropTypes } = React; - -const arrayOfNumberPropType = PropTypes.arrayOf(PropTypes.number); - -const transformMatrixPropType = function (props, propName, componentName, ...rest) { - if (props.transform && props.transformMatrix) { - return new Error( - 'transformMatrix and transform styles cannot be used on the same ' + - 'component' - ); - } - return arrayOfNumberPropType(props, propName, componentName, ...rest); -}; - -const transformPropTypes = { - transform: PropTypes.arrayOf( - PropTypes.oneOfType([ - PropTypes.shape({ perspective: PropTypes.number }), - PropTypes.shape({ rotate: PropTypes.string }), - PropTypes.shape({ rotateX: PropTypes.string }), - PropTypes.shape({ rotateY: PropTypes.string }), - PropTypes.shape({ rotateZ: PropTypes.string }), - PropTypes.shape({ scale: PropTypes.number }), - PropTypes.shape({ scaleX: PropTypes.number }), - PropTypes.shape({ scaleY: PropTypes.number }), - PropTypes.shape({ translateX: PropTypes.number }), - PropTypes.shape({ translateY: PropTypes.number }), - PropTypes.shape({ skewX: PropTypes.string }), - PropTypes.shape({ skewY: PropTypes.string }), - ]) - ), - transformMatrix: transformMatrixPropType, -}; - -module.exports = transformPropTypes; diff --git a/src/propTypes/ViewPropTypes.js b/src/propTypes/ViewPropTypes.js deleted file mode 100644 index 673c0ab..0000000 --- a/src/propTypes/ViewPropTypes.js +++ /dev/null @@ -1,402 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Components/View/ViewPropTypes.js - */ - -import React from 'react'; -import EdgeInsetsPropType from './EdgeInsetsPropType'; -import styleSheetPropType from './StyleSheetPropType'; -import ViewStylePropTypes from './ViewStylePropTypes'; -import { AccessibilityComponentTypes, AccessibilityTraits } from '../components/ViewAccessibility'; - -const stylePropType = styleSheetPropType(ViewStylePropTypes); - -const { PropTypes } = React; - -const ViewPropTypes = { - /** - * When `true`, indicates that the view is an accessibility element. By default, - * all the touchable elements are accessible. - */ - accessible: PropTypes.bool, - - /** - * Overrides the text that's read by the screen reader when the user interacts - * with the element. By default, the label is constructed by traversing all the - * children and accumulating all the `Text` nodes separated by space. - */ - accessibilityLabel: PropTypes.node, - - /** - * Indicates to accessibility services to treat UI component like a - * native one. Works for Android only. - * - * Possible values are one of: - * - * - `'none'` - * - `'button'` - * - `'radiobutton_checked'` - * - `'radiobutton_unchecked'` - * - * @platform android - */ - accessibilityComponentType: PropTypes.oneOf(AccessibilityComponentTypes), - - /** - * Indicates to accessibility services whether the user should be notified - * when this view changes. Works for Android API >= 19 only. - * Possible values: - * - * - `'none'` - Accessibility services should not announce changes to this view. - * - `'polite'`- Accessibility services should announce changes to this view. - * - `'assertive'` - Accessibility services should interrupt ongoing speech to immediately announce changes to this view. - * - * See the [Android `View` docs](http://developer.android.com/reference/android/view/View.html#attr_android:accessibilityLiveRegion) - * for reference. - * - * @platform android - */ - accessibilityLiveRegion: PropTypes.oneOf([ - 'none', - 'polite', - 'assertive', - ]), - - /** - * Controls how view is important for accessibility which is if it - * fires accessibility events and if it is reported to accessibility services - * that query the screen. Works for Android only. - * - * Possible values: - * - * - `'auto'` - The system determines whether the view is important for accessibility - - * default (recommended). - * - `'yes'` - The view is important for accessibility. - * - `'no'` - The view is not important for accessibility. - * - `'no-hide-descendants'` - The view is not important for accessibility, - * nor are any of its descendant views. - * - * See the [Android `importantForAccessibility` docs](http://developer.android.com/reference/android/R.attr.html#importantForAccessibility) - * for reference. - * - * @platform android - */ - importantForAccessibility: PropTypes.oneOf([ - 'auto', - 'yes', - 'no', - 'no-hide-descendants', - ]), - - /** - * Provides additional traits to screen reader. By default no traits are - * provided unless specified otherwise in element. - * - * You can provide one trait or an array of many traits. - * - * Possible values for `AccessibilityTraits` are: - * - * - `'none'` - The element has no traits. - * - `'button'` - The element should be treated as a button. - * - `'link'` - The element should be treated as a link. - * - `'header'` - The element is a header that divides content into sections. - * - `'search'` - The element should be treated as a search field. - * - `'image'` - The element should be treated as an image. - * - `'selected'` - The element is selected. - * - `'plays'` - The element plays sound. - * - `'key'` - The element should be treated like a keyboard key. - * - `'text'` - The element should be treated as text. - * - `'summary'` - The element provides app summary information. - * - `'disabled'` - The element is disabled. - * - `'frequentUpdates'` - The element frequently changes its value. - * - `'startsMedia'` - The element starts a media session. - * - `'adjustable'` - The element allows adjustment over a range of values. - * - `'allowsDirectInteraction'` - The element allows direct touch interaction for VoiceOver users. - * - `'pageTurn'` - Informs VoiceOver that it should scroll to the next page when it finishes reading the contents of the element. - * - * See the [Accessibility guide](docs/accessibility.html#accessibilitytraits-ios) - * for more information. - * - * @platform ios - */ - accessibilityTraits: PropTypes.oneOfType([ - PropTypes.oneOf(AccessibilityTraits), - PropTypes.arrayOf(PropTypes.oneOf(AccessibilityTraits)), - ]), - - /** - * A value indicating whether VoiceOver should ignore the elements - * within views that are siblings of the receiver. - * Default is `false`. - * - * See the [Accessibility guide](docs/accessibility.html#accessibilitytraits-ios) - * for more information. - * - * @platform ios - */ - accessibilityViewIsModal: PropTypes.bool, - - /** - * When `accessible` is true, the system will try to invoke this function - * when the user performs accessibility tap gesture. - */ - onAccessibilityTap: PropTypes.func, - - /** - * When `accessible` is `true`, the system will invoke this function when the - * user performs the magic tap gesture. - */ - onMagicTap: PropTypes.func, - - /** - * Used to locate this view in end-to-end tests. - * - * > This disables the 'layout-only view removal' optimization for this view! - */ - testID: PropTypes.string, - - /** - * Used to locate this view from native classes. - * - * > This disables the 'layout-only view removal' optimization for this view! - * - * @platform android - */ - nativeID: PropTypes.string, - - /** - * For most touch interactions, you'll simply want to wrap your component in - * `TouchableHighlight` or `TouchableOpacity`. Check out `Touchable.js`, - * `ScrollResponder.js` and `ResponderEventPlugin.js` for more discussion. - */ - - /** - * The View is now responding for touch events. This is the time to highlight and show the user - * what is happening. - * - * `View.props.onResponderGrant: (event) => {}`, where `event` is a synthetic touch event as - * described above. - */ - onResponderGrant: PropTypes.func, - - /** - * The user is moving their finger. - * - * `View.props.onResponderMove: (event) => {}`, where `event` is a synthetic touch event as - * described above. - */ - onResponderMove: PropTypes.func, - - /** - * Another responder is already active and will not release it to that `View` asking to be - * the responder. - * - * `View.props.onResponderReject: (event) => {}`, where `event` is a synthetic touch event as - * described above. - */ - onResponderReject: PropTypes.func, - - /** - * Fired at the end of the touch. - * - * `View.props.onResponderRelease: (event) => {}`, where `event` is a synthetic touch event as - * described above. - */ - onResponderRelease: PropTypes.func, - - /** - * The responder has been taken from the `View`. Might be taken by other views after a call to - * `onResponderTerminationRequest`, or might be taken by the OS without asking (e.g., happens - * with control center/ notification center on iOS) - * - * `View.props.onResponderTerminate: (event) => {}`, where `event` is a synthetic touch event as - * described above. - */ - onResponderTerminate: PropTypes.func, - - /** - * Some other `View` wants to become responder and is asking this `View` to release its - * responder. Returning `true` allows its release. - * - * `View.props.onResponderTerminationRequest: (event) => {}`, where `event` is a synthetic touch - * event as described above. - */ - onResponderTerminationRequest: PropTypes.func, - - /** - * Does this view want to become responder on the start of a touch? - * - * `View.props.onStartShouldSetResponder: (event) => [true | false]`, where `event` is a - * synthetic touch event as described above. - */ - onStartShouldSetResponder: PropTypes.func, - - /** - * If a parent `View` wants to prevent a child `View` from becoming responder on a touch start, - * it should have this handler which returns `true`. - * - * `View.props.onStartShouldSetResponderCapture: (event) => [true | false]`, where `event` is a - * synthetic touch event as described above. - */ - onStartShouldSetResponderCapture: PropTypes.func, - - /** - * Does this view want to "claim" touch responsiveness? This is called for every touch move on - * the `View` when it is not the responder. - * - * `View.props.onMoveShouldSetResponder: (event) => [true | false]`, where `event` is a - * synthetic touch event as described above. - */ - onMoveShouldSetResponder: PropTypes.func, - - /** - * If a parent `View` wants to prevent a child `View` from becoming responder on a move, - * it should have this handler which returns `true`. - * - * `View.props.onMoveShouldSetResponderCapture: (event) => [true | false]`, where `event` is a - * synthetic touch event as described above. - */ - onMoveShouldSetResponderCapture: PropTypes.func, - - /** - * This defines how far a touch event can start away from the view. - * Typical interface guidelines recommend touch targets that are at least - * 30 - 40 points/density-independent pixels. - * - * For example, if a touchable view has a height of 20 the touchable height can be extended to - * 40 with `hitSlop={{top: 10, bottom: 10, left: 0, right: 0}}` - * - * > The touch area never extends past the parent view bounds and the Z-index - * > of sibling views always takes precedence if a touch hits two overlapping - * > views. - */ - hitSlop: EdgeInsetsPropType, - - /** - * Invoked on mount and layout changes with: - * - * `{nativeEvent: { layout: {x, y, width, height}}}` - * - * This event is fired immediately once the layout has been calculated, but - * the new layout may not yet be reflected on the screen at the time the - * event is received, especially if a layout animation is in progress. - */ - onLayout: PropTypes.func, - - /** - * Controls whether the `View` can be the target of touch events. - * - * - `'auto'`: The View can be the target of touch events. - * - `'none'`: The View is never the target of touch events. - * - `'box-none'`: The View is never the target of touch events but it's - * subviews can be. It behaves like if the view had the following classes - * in CSS: - * ``` - * .box-none { - * pointer-events: none; - * } - * .box-none * { - * pointer-events: all; - * } - * ``` - * - `'box-only'`: The view can be the target of touch events but it's - * subviews cannot be. It behaves like if the view had the following classes - * in CSS: - * ``` - * .box-only { - * pointer-events: all; - * } - * .box-only * { - * pointer-events: none; - * } - * ``` - * > Since `pointerEvents` does not affect layout/appearance, and we are - * > already deviating from the spec by adding additional modes, we opt to not - * > include `pointerEvents` on `style`. On some platforms, we would need to - * > implement it as a `className` anyways. Using `style` or not is an - * > implementation detail of the platform. - */ - pointerEvents: PropTypes.oneOf([ - 'box-none', - 'none', - 'box-only', - 'auto', - ]), - style: stylePropType, - - /** - * This is a special performance property exposed by `RCTView` and is useful - * for scrolling content when there are many subviews, most of which are - * offscreen. For this property to be effective, it must be applied to a - * view that contains many subviews that extend outside its bound. The - * subviews must also have `overflow: hidden`, as should the containing view - * (or one of its superviews). - */ - removeClippedSubviews: PropTypes.bool, - - /** - * Whether this `View` should render itself (and all of its children) into a - * single hardware texture on the GPU. - * - * On Android, this is useful for animations and interactions that only - * modify opacity, rotation, translation, and/or scale: in those cases, the - * view doesn't have to be redrawn and display lists don't need to be - * re-executed. The texture can just be re-used and re-composited with - * different parameters. The downside is that this can use up limited video - * memory, so this prop should be set back to false at the end of the - * interaction/animation. - * - * @platform android - */ - renderToHardwareTextureAndroid: PropTypes.bool, - - /** - * Whether this `View` should be rendered as a bitmap before compositing. - * - * On iOS, this is useful for animations and interactions that do not - * modify this component's dimensions nor its children; for example, when - * translating the position of a static view, rasterization allows the - * renderer to reuse a cached bitmap of a static view and quickly composite - * it during each frame. - * - * Rasterization incurs an off-screen drawing pass and the bitmap consumes - * memory. Test and measure when using this property. - * - * @platform ios - */ - shouldRasterizeIOS: PropTypes.bool, - - /** - * Views that are only used to layout their children or otherwise don't draw - * anything may be automatically removed from the native hierarchy as an - * optimization. Set this property to `false` to disable this optimization and - * ensure that this `View` exists in the native view hierarchy. - * - * @platform android - */ - collapsable: PropTypes.bool, - - /** - * Whether this `View` needs to rendered offscreen and composited with an alpha - * in order to preserve 100% correct colors and blending behavior. The default - * (`false`) falls back to drawing the component and its children with an alpha - * applied to the paint used to draw each element instead of rendering the full - * component offscreen and compositing it back with an alpha value. This default - * may be noticeable and undesired in the case where the `View` you are setting - * an opacity on has multiple overlapping elements (e.g. multiple overlapping - * `View`s, or text and a background). - * - * Rendering offscreen to preserve correct alpha behavior is extremely - * expensive and hard to debug for non-native developers, which is why it is - * not turned on by default. If you do need to enable this property for an - * animation, consider combining it with renderToHardwareTextureAndroid if the - * view **contents** are static (i.e. it doesn't need to be redrawn each frame). - * If that property is enabled, this View will be rendered off-screen once, - * saved in a hardware texture, and then composited onto the screen with an alpha - * each frame without having to switch rendering targets on the GPU. - * - * @platform android - */ - needsOffscreenAlphaCompositing: PropTypes.bool, -}; - -module.exports = ViewPropTypes; diff --git a/src/propTypes/ViewStylePropTypes.js b/src/propTypes/ViewStylePropTypes.js deleted file mode 100644 index b8e168a..0000000 --- a/src/propTypes/ViewStylePropTypes.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/Components/View/ViewStylePropTypes.js - */ -import React from 'react'; -import ColorPropType from './ColorPropType'; -import LayoutPropTypes from './LayoutPropTypes'; -import ShadowPropTypesIOS from './ShadowPropTypesIOS'; -import TransformPropTypes from './TransformPropTypes'; - -const { PropTypes } = React; - -/** - * Warning: Some of these properties may not be supported in all releases. - */ -const ViewStylePropTypes = { - ...LayoutPropTypes, - ...ShadowPropTypesIOS, - ...TransformPropTypes, - backfaceVisibility: PropTypes.oneOf(['visible', 'hidden']), - backgroundColor: ColorPropType, - borderColor: ColorPropType, - borderTopColor: ColorPropType, - borderRightColor: ColorPropType, - borderBottomColor: ColorPropType, - borderLeftColor: ColorPropType, - borderRadius: PropTypes.number, - borderTopLeftRadius: PropTypes.number, - borderTopRightRadius: PropTypes.number, - borderBottomLeftRadius: PropTypes.number, - borderBottomRightRadius: PropTypes.number, - borderStyle: PropTypes.oneOf(['solid', 'dotted', 'dashed']), - borderWidth: PropTypes.number, - borderTopWidth: PropTypes.number, - borderRightWidth: PropTypes.number, - borderBottomWidth: PropTypes.number, - borderLeftWidth: PropTypes.number, - opacity: PropTypes.number, - overflow: PropTypes.oneOf(['visible', 'hidden']), - /** - * (Android-only) Sets the elevation of a view, using Android's underlying - * [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation). - * This adds a drop shadow to the item and affects z-order for overlapping views. - * Only supported on Android 5.0+, has no effect on earlier versions. - * @platform android - */ - elevation: PropTypes.number, -}; - -module.exports = ViewStylePropTypes; diff --git a/src/propTypes/flattenStyle.js b/src/propTypes/flattenStyle.js deleted file mode 100644 index 9e7d1f7..0000000 --- a/src/propTypes/flattenStyle.js +++ /dev/null @@ -1,11 +0,0 @@ -function flattenStyle(style) { - if (!style) { - return undefined; - } - if (!Array.isArray(style)) { - return style; - } - return Object.assign({}, ...style); -} - -module.exports = flattenStyle; diff --git a/src/react-native-mock.js b/src/react-native-mock.js new file mode 100644 index 0000000..6e3fb82 --- /dev/null +++ b/src/react-native-mock.js @@ -0,0 +1,77 @@ +import mockery from 'mockery'; +import _ from 'underscore'; +import React from 'react'; +import sinon from 'sinon'; + +import defineGlobalProperty from './defineGlobalProperty'; +import createMockComponent, { MOCK_COMPONENTS } from './createMockComponent'; +import mockNativeModules from './NativeModules'; + + +// Setup babel to build react-native source +require('./babel'); + + +// Enable mockery +mockery.enable({ + warnOnReplace: false, + warnOnUnregistered: false +}); + + +// Set react-native to be in dev mode +defineGlobalProperty('__DEV__', true); + + +// Fix Facebooks odd requiring stuff from generated map +_.mapObject(require('../haste-map.json').hasteMap, function (val, key) { + mockery.registerSubstitute(key, val); +}); + + +// miscellaneous mocks +mockery.registerMock('ensureComponentIsNative', () => true); +mockery.registerMock('requireNativeComponent', sinon.spy(viewName => props => React.createElement( + viewName, + props, + props.children // eslint-disable-line react/prop-types +))); +mockery.registerMock('ErrorUtils', require('./mocks/ErrorUtils')); + + +// Setup native modules +_.forEach(Object.keys(mockNativeModules), function (mod) { + mockery.registerMock(mod, mockNativeModules[mod]); +}); + +mockery.registerMock('NativeModules', mockNativeModules); + + +// Add native prop registry +const mockPropRegistry = {}; +mockery.registerMock('ReactNativePropRegistry', { + register: sinon.spy(id => id), + getByID: sinon.spy(() => mockPropRegistry) +}); + + +// Mock base components +_.forEach(MOCK_COMPONENTS, function (component) { + mockery.registerMock(component, createMockComponent(component)); +}); + + +// Setup mocks for extra components / apis +mockery.registerMock('ListViewDataSource', require('./mocks/ListViewDataSource')); +mockery.registerMock('ListView', require('./mocks/ListView')); +mockery.registerMock('AsyncStorage', require('./mocks/AsyncStorage')); + + +// Allow requiring of images +require('./image-compiler'); + + + +// Setup globals at the end, incase anything above tries to override them +defineGlobalProperty('Promise', require('promise')); +defineGlobalProperty('regeneratorRuntime', require('regenerator-runtime/runtime')); diff --git a/src/react-native-version.js b/src/react-native-version.js new file mode 100644 index 0000000..2497091 --- /dev/null +++ b/src/react-native-version.js @@ -0,0 +1 @@ +module.exports = require('react-native/package').version; diff --git a/src/react-native.js b/src/react-native.js deleted file mode 100644 index 2ca8d22..0000000 --- a/src/react-native.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * https://github.com/facebook/react-native/blob/master/Libraries/react-native/react-native.js - */ -import React from 'react'; - -import createMockComponent from './components/createMockComponent'; -import defineGlobalProperty from './defineGlobalProperty'; - -// Export React, plus some native additions. -const ReactNative = { - // Components - ActivityIndicator: require('./components/ActivityIndicator'), - ActivityIndicatorIOS: require('./components/ActivityIndicatorIOS'), - ART: require('./components/ART'), - Button: createMockComponent('Button'), - DatePickerIOS: createMockComponent('DatePickerIOS'), - DrawerLayoutAndroid: require('./components/DrawerLayoutAndroid'), - Image: require('./components/Image'), - ImageEditor: createMockComponent('ImageEditor'), - ImageStore: createMockComponent('ImageStore'), - KeyboardAvoidingView: createMockComponent('KeyboardAvoidingView'), - ListView: require('./components/ListView'), - MapView: createMockComponent('MapView'), - Modal: createMockComponent('Modal'), - Navigator: require('./components/Navigator'), - NavigatorIOS: createMockComponent('NavigatorIOS'), - Picker: require('./components/Picker'), - PickerIOS: createMockComponent('PickerIOS'), - ProgressBarAndroid: createMockComponent('ProgressBarAndroid'), - ProgressViewIOS: createMockComponent('ProgressViewIOS'), - ScrollView: require('./components/ScrollView'), - SegmentedControlIOS: createMockComponent('SegmentedControlIOS'), - SliderIOS: createMockComponent('SliderIOS'), - SnapshotViewIOS: createMockComponent('SnapshotViewIOS'), - Switch: createMockComponent('Switch'), - PullToRefreshViewAndroid: createMockComponent('PullToRefreshViewAndroid'), - RecyclerViewBackedScrollView: createMockComponent('RecyclerViewBackedScrollView'), - RefreshControl: createMockComponent('RefreshControl'), - StatusBar: require('./components/StatusBar'), - SwitchAndroid: createMockComponent('SwitchAndroid'), - SwitchIOS: createMockComponent('SwitchIOS'), - TabBarIOS: require('./components/TabBarIOS'), - Text: require('./components/Text'), - TextInput: require('./components/TextInput'), - ToastAndroid: createMockComponent('ToastAndroid'), - ToolbarAndroid: createMockComponent('ToolbarAndroid'), - Touchable: createMockComponent('Touchable'), - TouchableHighlight: createMockComponent('TouchableHighlight'), - TouchableNativeFeedback: require('./components/TouchableNativeFeedback'), - TouchableOpacity: require('./components/TouchableOpacity'), - TouchableWithoutFeedback: require('./components/TouchableWithoutFeedback'), - View: require('./components/View'), - ViewPagerAndroid: createMockComponent('ViewPagerAndroid'), - WebView: require('./components/WebView'), - - // APIs - ActionSheetIOS: require('./api/ActionSheetIOS'), - Alert: require('./api/Alert'), - AlertIOS: require('./api/AlertIOS'), - Animated: require('./api/Animated'), - AppRegistry: require('./api/AppRegistry'), - AppState: require('./api/AppState'), - AppStateIOS: require('./api/AppStateIOS'), - AsyncStorage: require('./api/AsyncStorage'), - BackAndroid: require('./api/BackAndroid'), - CameraRoll: require('./api/CameraRoll'), - Clipboard: require('./NativeModules/Clipboard'), - DatePickerAndroid: require('./api/DatePickerAndroid'), - Dimensions: require('./api/Dimensions'), - Easing: require('./api/Animated/Easing'), - ImagePickerIOS: require('./api/ImagePickerIOS'), - IntentAndroid: require('./api/IntentAndroid'), - InteractionManager: require('./api/InteractionManager'), - Keyboard: require('./api/Keyboard'), - LayoutAnimation: require('./api/LayoutAnimation'), - Linking: require('./api/Linking'), - LinkingIOS: require('./api/LinkingIOS'), - NetInfo: require('./api/NetInfo'), - PanResponder: require('./api/PanResponder'), - PixelRatio: require('./api/PixelRatio'), - PushNotificationIOS: require('./api/PushNotificationIOS'), - Settings: require('./api/Settings'), - Share: require('./api/Share'), - StatusBarIOS: require('./api/StatusBarIOS'), - StyleSheet: require('./api/StyleSheet'), - TimePickerAndroid: require('./api/TimePickerAndroid'), - UIManager: require('./NativeModules/UIManager'), - VibrationIOS: require('./api/VibrationIOS'), - - // Plugins - DeviceEventEmitter: require('./plugins/DeviceEventEmitter'), - NativeAppEventEmitter: require('./plugins/NativeAppEventEmitter'), - NativeEventEmitter: require('./Libraries/EventEmitter/NativeEventEmitter'), - NativeModules: require('./NativeModules'), - Platform: require('./plugins/Platform'), - processColor: require('./plugins/processColor'), - requireNativeComponent: require('./plugins/requireNativeComponent'), - - // Prop Types - ColorPropType: require('./propTypes/ColorPropType'), - EdgeInsetsPropType: require('./propTypes/EdgeInsetsPropType'), - PointPropType: require('./propTypes/PointPropType'), - NavigationExperimental: require('./Libraries/NavigationExperimental'), - ViewPropTypes: require('./propTypes/ViewPropTypes'), -}; - - -// See http://facebook.github.io/react/docs/addons.html -const ReactNativeAddons = { - // LinkedStateMixin: require('react-addons-linked-state-mixin') deprecated, - Perf: require('react-addons-perf'), - PureRenderMixin: require('react-addons-pure-render-mixin'), - TestModule: require('./NativeModules/TestModule'), - TestUtils: require('react-addons-test-utils'), - // TODO(lmr): not sure where to find this - // batchedUpdates: require('ReactUpdates').batchedUpdates, deprecated - // cloneWithProps: require('react-addons-clone-with-props'), deprecated - createFragment: require('react-addons-create-fragment'), - update: require('react-addons-update'), -}; - -Object.assign(ReactNative, React, { addons: ReactNativeAddons }); - -// Global properties defined in https://github.com/facebook/react-native/blob/master/Libraries/Core/InitializeCore.js -defineGlobalProperty('XMLHttpRequest', () => require('./Libraries/Network/XMLHttpRequest')); -defineGlobalProperty('FormData', () => require('./Libraries/Network/FormData')); -defineGlobalProperty('Headers', () => require('./Libraries/Network/Headers')); -defineGlobalProperty('Response', () => require('./Libraries/Network/Response')); - -module.exports = ReactNative; diff --git a/src/requireLibrary.js b/src/requireLibrary.js deleted file mode 100644 index e94a286..0000000 --- a/src/requireLibrary.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Playing around with the idea of requiring libraries from RN directly. - * - * Next steps: utilize RN's packager transform in order to parse the code. - */ -const path = require('path'); -const absolutePathToRN = require.resolve('react-native'); -const relativePathToRN = path.relative(__filename, absolutePathToRN); -const pathToLibraries = path.join(relativePathToRN, '../../'); - - -function requireLibrary(lib) { - const relPath = path.join(pathToLibraries, lib); - const absPath = path.resolve(__filename, relPath); - return require(absPath); -} - -// Example Usage: -// var normalizeColor = requireLibrary('StyleSheet/normalizeColor.js'); -module.exports = requireLibrary; diff --git a/test/StyleSheet.js b/test/StyleSheet.js deleted file mode 100644 index fef8bc7..0000000 --- a/test/StyleSheet.js +++ /dev/null @@ -1,56 +0,0 @@ -import { StyleSheet } from '../src/react-native'; -import { expect } from 'chai'; - - -describe('StyleSheet', () => { - let styles; - - beforeEach(function () { - styles = StyleSheet.create({ - listItem: { - flex: 1, - fontSize: 16, - color: 'white' - }, - selectedListItem: { - color: 'green' - }, - headerItem: { - fontWeight: 'bold' - } - }); - }); - - it('flatten', () => { - const result = StyleSheet.flatten(styles.listItem); - const expectedResult = { - flex: 1, - fontSize: 16, - color: 'white' - }; - expect(result).to.deep.equal(expectedResult); - }); - - it('flatten with array', () => { - const result = StyleSheet.flatten([styles.listItem, styles.selectedListItem]); - const expectedResult = { - flex: 1, - fontSize: 16, - color: 'green' - }; - expect(result).to.deep.equal(expectedResult); - }); - - it('flatten with nested array', () => { - const result = StyleSheet.flatten( - [styles.listItem, [styles.headerItem, styles.selectedListItem]] - ); - const expectedResult = { - flex: 1, - fontSize: 16, - color: 'green', - fontWeight: 'bold' - }; - expect(result).to.deep.equal(expectedResult); - }); -}); diff --git a/test/basic-test.js b/test/basic-test.js deleted file mode 100644 index e5368e6..0000000 --- a/test/basic-test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from '../src/react-native'; -import { expect } from 'chai'; - -describe('Requires', () => { - it('requires', () => { - console.log(Object.keys(React)); // eslint-disable-line no-console - expect(true).to.equal(true); - }); -}); diff --git a/test/components/DrawerLayoutAndroid.js b/test/components/DrawerLayoutAndroid.js deleted file mode 100644 index 5e1af92..0000000 --- a/test/components/DrawerLayoutAndroid.js +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint no-unused-expressions: 0 */ -import React from 'react'; -import { DrawerLayoutAndroid } from '../../src/react-native'; -import { expect } from 'chai'; -import ReactTestUtils from 'react-addons-test-utils'; - -describe('DrawerLayoutAndroid', () => { - it('should render an empty DrawerLayoutAndroid', () => { - const renderer = ReactTestUtils.createRenderer(); - const wrapper = renderer.getRenderOutput( {}} />); - expect(wrapper).to.be.null; - }); - - it('should have static properties for the positions', () => { - expect(DrawerLayoutAndroid.positions).to.be.an.object; - expect(DrawerLayoutAndroid.positions).to.have.property('Left'); - expect(DrawerLayoutAndroid.positions).to.have.property('Right'); - }); -}); diff --git a/test/components/Picker.js b/test/components/Picker.js deleted file mode 100644 index 21484d4..0000000 --- a/test/components/Picker.js +++ /dev/null @@ -1,12 +0,0 @@ -import { expect } from 'chai'; -import Picker from '../../src/components/Picker.js'; - -describe('Picker', () => { - it('is renderable', () => { - expect(Picker).to.be.a('function'); - }); - - it('.Item is renderable', () => { - expect(Picker.Item).to.be.a('function'); - }); -}); diff --git a/test/components/TabBarIOS.js b/test/components/TabBarIOS.js deleted file mode 100644 index 73498ad..0000000 --- a/test/components/TabBarIOS.js +++ /dev/null @@ -1,12 +0,0 @@ -import { expect } from 'chai'; -import TabBarIOS from '../../src/components/TabBarIOS.js'; - -describe('TabBarIOS', () => { - it('is renderable', () => { - expect(TabBarIOS).to.be.a('function'); - }); - - it('.Item is renderable', () => { - expect(TabBarIOS.Item).to.be.a('function'); - }); -}); diff --git a/tests/globals.test.js b/tests/globals.test.js new file mode 100644 index 0000000..86744b0 --- /dev/null +++ b/tests/globals.test.js @@ -0,0 +1,29 @@ +import { expect } from 'chai'; +import defineGlobalProperty from '../src/defineGlobalProperty'; + + +describe('Globals', function () { + it('should have promise globally', function () { + expect(global.Promise).to.deep.equal(require('promise')); + }); + + it('should have regenerator runtime globally', function () { + expect(global.regeneratorRuntime).to.deep.equal(require('regenerator-runtime/runtime')); + }); + + it('should be in dev', function () { + expect(global.__DEV__).to.be.true; + }); + + it('should have global navigator', function () { + expect(global.navigator).to.exist; + expect(global.navigator).to.have.any.keys('geolocation'); + }); + + describe('describeGlobalProperty', function () { + it('Should define property', function () { + defineGlobalProperty('testKey1', 'testValue1'); + expect(global.testKey1).to.equal('testValue1'); + }); + }); +}); diff --git a/tests/haste-map.test.js b/tests/haste-map.test.js new file mode 100644 index 0000000..56f12d8 --- /dev/null +++ b/tests/haste-map.test.js @@ -0,0 +1,49 @@ +import { expect } from 'chai'; +import fs from 'fs'; +import path from 'path'; + +const MAP_LOCATION = path.join(__dirname, '../haste-map.json'); + +describe('Haste Map', function () { + it('Should have one initially', function () { + const stat = fs.statSync(MAP_LOCATION); + expect(stat.isFile()).to.be.true; + }); + + it('should contain correct data', function () { + const HasteMap = require(MAP_LOCATION); + expect(HasteMap).to.have.all.keys('hasteMap', 'version'); + expect(HasteMap.hasteMap).to.be.an('object'); + expect(HasteMap.version).to.be.a('string'); + expect(Object.keys(HasteMap.hasteMap)).to.have.length.above(1); + }); + + describe('Haste Map Builder', function () { + it('Should build the map', function () { + fs.unlinkSync(MAP_LOCATION); + require('../src/haste'); + const stat = fs.statSync(MAP_LOCATION); + expect(stat.isFile()).to.be.true; + }); + }); + + describe('Modules', function () { + const HasteMap = require(MAP_LOCATION).hasteMap; + + it('shouldnt import android version of ios components', function () { + Object.keys(HasteMap).forEach(function (mod) { + if (mod.match(/ios/gi)) { + expect(HasteMap[mod].endsWith('.android.js')).to.be.false; + } + }); + }); + + it('shouldnt import ios version of android components', function () { + Object.keys(HasteMap).forEach(function (mod) { + if (mod.match(/android/gi)) { + expect(HasteMap[mod].endsWith('.ios.js')).to.be.false; + } + }); + }); + }); +}); diff --git a/tests/image-compiler.test.js b/tests/image-compiler.test.js new file mode 100644 index 0000000..c1ed71a --- /dev/null +++ b/tests/image-compiler.test.js @@ -0,0 +1,23 @@ +import { expect } from 'chai'; + + +describe('Image Compiler', function () { + it('should require a jpg image', function () { + expect(require('foo.jpg')).to.deep.equal({ uri: 'foo.jpg' }); // eslint-disable-line import/no-unresolved + }); + + it('should require a jpeg image', function () { + expect(require('foo.jpeg')).to.deep.equal({ uri: 'foo.jpeg' }); // eslint-disable-line import/no-unresolved + }); + + it('should require a png image', function () { + expect(require('foo.png')).to.deep.equal({ uri: 'foo.png' }); // eslint-disable-line import/no-unresolved + }); + + it('shouldnt require non-image', function () { + function throwRequire() { + return require('foo.foo'); // eslint-disable-line import/no-unresolved + } + expect(throwRequire).to.throw(Error); + }); +}); diff --git a/tests/integration/apis/Alert.test.js b/tests/integration/apis/Alert.test.js new file mode 100644 index 0000000..d4a60dd --- /dev/null +++ b/tests/integration/apis/Alert.test.js @@ -0,0 +1,32 @@ +import { expect } from 'chai'; + + +describe('Alert', () => { + const { Alert } = require('react-native'); + + it('should be callable', function () { + const alert = Alert.alert( + 'Alert Title', + 'My Alert Msg', + [ + { + text: 'Ask me later', + onPress: () => {} + }, + { + text: 'Cancel', + onPress: () => {}, + style: 'cancel' + }, + { + text: 'OK', + onPress: () => {} + } + ], + { + cancelable: false + } + ); + expect(alert).to.be.undefined; + }); +}); diff --git a/tests/integration/apis/AlertIOS.test.js b/tests/integration/apis/AlertIOS.test.js new file mode 100644 index 0000000..402a56f --- /dev/null +++ b/tests/integration/apis/AlertIOS.test.js @@ -0,0 +1,47 @@ +import { expect } from 'chai'; + + +describe('AlertIOS', () => { + const { AlertIOS } = require('react-native'); + + it('should have alert', function () { + const alert = AlertIOS.alert( + 'Update available', + 'Keep your app up to date to enjoy the latest features', + [ + { + text: 'Cancel', + onPress: () => {}, + style: 'cancel' + }, + { + text: 'Install', + onPress: () => {} + } + ] + ); + + expect(alert).to.be.undefined; + }); + + it('should have prompt', function () { + const prompt = AlertIOS.prompt( + 'Enter password', + 'Enter your password to claim your $1.5B in lottery winnings', + [ + { + text: 'Cancel', + onPress: () => {}, + style: 'cancel' + }, + { + text: 'OK', + onPress: (password) => {} + } + ], + 'secure-text' + ); + + expect(prompt).to.be.undefined; + }); +}); diff --git a/tests/integration/apis/AppRegistry.test.js b/tests/integration/apis/AppRegistry.test.js new file mode 100644 index 0000000..b996277 --- /dev/null +++ b/tests/integration/apis/AppRegistry.test.js @@ -0,0 +1,16 @@ +import { expect } from 'chai'; + + +describe('AppRegistry', () => { + const { AppRegistry, View } = require('react-native'); + + it('should have register component', function () { + const component = AppRegistry.registerComponent('react-native-mock', () => View); + expect(component).to.equal('react-native-mock'); + }); + + it('should list app keys', function () { + AppRegistry.registerComponent('react-native-mock', () => View); + expect(AppRegistry.getAppKeys()).to.deep.equal(['react-native-mock']); + }); +}); diff --git a/tests/integration/apis/AppState.test.js b/tests/integration/apis/AppState.test.js new file mode 100644 index 0000000..a16a4ae --- /dev/null +++ b/tests/integration/apis/AppState.test.js @@ -0,0 +1,11 @@ +import { expect } from 'chai'; + + +describe('AppState', () => { + const { AppState } = require('react-native'); + + it('should allow event binds', function () { + expect(AppState.addEventListener).to.be.a('function'); + expect(AppState.removeEventListener).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/AsyncStorage.test.js b/tests/integration/apis/AsyncStorage.test.js new file mode 100644 index 0000000..1e47b8f --- /dev/null +++ b/tests/integration/apis/AsyncStorage.test.js @@ -0,0 +1,31 @@ +import { expect } from 'chai'; +import MockAsyncStorage from '../../../src/mocks/AsyncStorage'; + + +describe('AsyncStorage', () => { + const { AsyncStorage } = require('react-native'); + + beforeEach(function (done) { + AsyncStorage.clear(done); + }); + + it('should be the mock', function () { + expect(AsyncStorage).to.deep.equal(MockAsyncStorage); + }); + + it('should have right functions', function () { + expect(AsyncStorage.setItem).to.be.a('function'); + expect(AsyncStorage.getItem).to.be.a('function'); + expect(AsyncStorage.clear).to.be.a('function'); + }); + + it('should set and get', function (done) { + AsyncStorage.setItem('key', 'value').then(function () { + expect(AsyncStorage.getItem('key')).to.eventually.equal('value').notify(done); + }); + }); + + it('should get invalid key', function (done) { + expect(AsyncStorage.getItem('key')).to.be.rejected.notify(done); + }); +}); diff --git a/tests/integration/apis/BackAndroid.test.js b/tests/integration/apis/BackAndroid.test.js new file mode 100644 index 0000000..ee5b048 --- /dev/null +++ b/tests/integration/apis/BackAndroid.test.js @@ -0,0 +1,11 @@ +import { expect } from 'chai'; + +describe('BackAndroid', () => { + const { BackAndroid } = require('react-native'); + + it('should bind events', function () { + expect(BackAndroid.addEventListener).to.be.a('function'); + expect(BackAndroid.removeEventListener).to.be.a('function'); + expect(BackAndroid.exitApp).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/BackHandler.test.js b/tests/integration/apis/BackHandler.test.js new file mode 100644 index 0000000..a201bd6 --- /dev/null +++ b/tests/integration/apis/BackHandler.test.js @@ -0,0 +1,11 @@ +import { expect } from 'chai'; + +describe('BackHandler', () => { + const { BackHandler } = require('react-native'); + + it('should bind events', function () { + expect(BackHandler.addEventListener).to.be.a('function'); + expect(BackHandler.removeEventListener).to.be.a('function'); + expect(BackHandler.exitApp).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/CameraRoll.test.js b/tests/integration/apis/CameraRoll.test.js new file mode 100644 index 0000000..12996ae --- /dev/null +++ b/tests/integration/apis/CameraRoll.test.js @@ -0,0 +1,12 @@ +import { expect } from 'chai'; + + +describe('CameraRoll', () => { + const { CameraRoll } = require('react-native'); + + it('should have proper functions', function () { + expect(CameraRoll.saveImageWithTag).to.be.a('function'); + expect(CameraRoll.saveToCameraRoll).to.be.a('function'); + expect(CameraRoll.getPhotos).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/Clipboard.test.js b/tests/integration/apis/Clipboard.test.js new file mode 100644 index 0000000..c9d345a --- /dev/null +++ b/tests/integration/apis/Clipboard.test.js @@ -0,0 +1,11 @@ +import { expect } from 'chai'; + + +describe('Clipboard', () => { + const { Clipboard } = require('react-native'); + + it('should have proper functions', function () { + expect(Clipboard.getString).to.be.a('function'); + expect(Clipboard.setString).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/DatePickerAndroid.test.js b/tests/integration/apis/DatePickerAndroid.test.js new file mode 100644 index 0000000..c552de3 --- /dev/null +++ b/tests/integration/apis/DatePickerAndroid.test.js @@ -0,0 +1,12 @@ +import { expect } from 'chai'; + + +describe('DatePickerAndroid', () => { + const { DatePickerAndroid } = require('react-native'); + + it('should have proper functions', function () { + expect(DatePickerAndroid.open).to.be.a('function'); + expect(DatePickerAndroid.dateSetAction).to.equal('dateSetAction'); + expect(DatePickerAndroid.dismissedAction).to.equal('dismissedAction'); + }); +}); diff --git a/tests/integration/apis/Dimensions.test.js b/tests/integration/apis/Dimensions.test.js new file mode 100644 index 0000000..83b4e89 --- /dev/null +++ b/tests/integration/apis/Dimensions.test.js @@ -0,0 +1,19 @@ +import { expect } from 'chai'; + + +describe('Dimensions', () => { + const { Dimensions } = require('react-native'); + + it('should get dimensions', function () { + expect(Dimensions.get('window')).to.deep.equal({ + fontScale: 2, + height: 667, + scale: 2, + width: 375 + }); + }); + + it('should throw on invalid dimensions', function () { + expect(() => Dimensions.get('notWindow')).to.throw; + }); +}); diff --git a/tests/integration/apis/Easing.test.js b/tests/integration/apis/Easing.test.js new file mode 100644 index 0000000..1af5102 --- /dev/null +++ b/tests/integration/apis/Easing.test.js @@ -0,0 +1,154 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of react-native. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +import { expect } from 'chai'; + + +describe('Easing', () => { + const { Easing } = require('react-native'); + + it('should have all functions', () => { + expect(Easing.step0).to.be.a('function'); + expect(Easing.step1).to.be.a('function'); + expect(Easing.linear).to.be.a('function'); + expect(Easing.ease).to.be.a('function'); + expect(Easing.quad).to.be.a('function'); + expect(Easing.cubic).to.be.a('function'); + expect(Easing.poly).to.be.a('function'); + expect(Easing.sin).to.be.a('function'); + expect(Easing.circle).to.be.a('function'); + expect(Easing.exp).to.be.a('function'); + expect(Easing.elastic).to.be.a('function'); + expect(Easing.back).to.be.a('function'); + expect(Easing.bounce).to.be.a('function'); + expect(Easing.bezier).to.be.a('function'); + expect(Easing.in).to.be.a('function'); + expect(Easing.out).to.be.a('function'); + expect(Easing.inOut).to.be.a('function'); + }); + + describe('linear', () => { + it('should work', () => { + const easing = Easing.linear; + + expect(easing(0)).to.equal(0); + expect(easing(0.5)).to.equal(0.5); + expect(easing(0.8)).to.equal(0.8); + expect(easing(1)).to.equal(1); + }); + + it('should work with ease in', () => { + const easing = Easing.in(Easing.linear); + expect(easing(0)).to.equal(0); + expect(easing(0.5)).to.equal(0.5); + expect(easing(0.8)).to.equal(0.8); + expect(easing(1)).to.equal(1); + }); + + it('should work with ease out', () => { + const easing = Easing.out(Easing.linear); + expect(easing(0)).to.equal(0); + expect(easing(0.5)).to.equal(0.5); + expect(easing(0.6)).to.equal(0.6); + expect(easing(1)).to.equal(1); + }); + }); + + describe('quad', () => { + it('should work with ease in', () => { + function easeInQuad(t) { + return t * t; + } + const easing = Easing.in(Easing.quad); + for (let t = -0.5; t < 1.5; t += 0.1) { + expect(easing(t)).to.equal(easeInQuad(t)); + } + }); + + it('should work with ease out', () => { + function easeOutQuad(t) { + return -t * (t - 2); + } + const easing = Easing.out(Easing.quad); + for (let t = 0; t <= 1; t += 0.1) { + expect(easing(1)).to.equal(easeOutQuad(1)); + } + }); + + it('should work with ease in-out', () => { + function easeInOutQuad(t) { + if (t < 1) { + return 0.5 * t * t; + } + return -(((t - 1) * (t - 3)) - 1) / 2; + } + const easing = Easing.inOut(Easing.quad); + for (let t = -0.5; t < 1.5; t += 0.1) { + expect(easing(t)).to.be.closeTo(easeInOutQuad(t * 2), 4); + } + }); + }); + + it('should satisfy boundary conditions with elastic', () => { + for (let b = 0; b < 4; b += 0.3) { + const easing = Easing.elastic(b); + expect(easing(0)).to.equal(0); + expect(easing(1)).to.equal(1); + } + }); + + describe('samples', () => { + function sampleEasingFunction(easing) { + const DURATION = 300; + const tickCount = Math.round((DURATION * 60) / 1000); + const samples = []; + for (let i = 0; i <= tickCount; i += 1) { + samples.push(easing(i / tickCount)); + } + return samples; + } + + const SAMPLES = { + in_quad: [0, 0.0030864197530864196, 0.012345679012345678, 0.027777777777777776, 0.04938271604938271, 0.0771604938271605, 0.1111111111111111, 0.15123456790123457, 0.19753086419753085, 0.25, 0.308641975308642, 0.37345679012345684, 0.4444444444444444, 0.5216049382716049, 0.6049382716049383, 0.6944444444444445, 0.7901234567901234, 0.8919753086419753, 1], // eslint-disable-line max-len + out_quad: [0, 0.10802469135802469, 0.20987654320987653, 0.3055555555555555, 0.3950617283950617, 0.47839506172839513, 0.5555555555555556, 0.6265432098765432, 0.691358024691358, 0.75, 0.8024691358024691, 0.8487654320987654, 0.888888888888889, 0.9228395061728394, 0.9506172839506174, 0.9722222222222221, 0.9876543209876543, 0.9969135802469136, 1], // eslint-disable-line max-len + inOut_quad: [0, 0.006172839506172839, 0.024691358024691357, 0.05555555555555555, 0.09876543209876543, 0.154320987654321, 0.2222222222222222, 0.30246913580246915, 0.3950617283950617, 0.5, 0.6049382716049383, 0.697530864197531, 0.7777777777777777, 0.845679012345679, 0.9012345679012346, 0.9444444444444444, 0.9753086419753086, 0.9938271604938271, 1], // eslint-disable-line max-len + in_cubic: [0, 0.00017146776406035664, 0.0013717421124828531, 0.004629629629629629, 0.010973936899862825, 0.021433470507544586, 0.037037037037037035, 0.05881344307270234, 0.0877914951989026, 0.125, 0.1714677640603567, 0.22822359396433475, 0.2962962962962963, 0.37671467764060357, 0.4705075445816187, 0.5787037037037038, 0.7023319615912208, 0.8424211248285322, 1], // eslint-disable-line max-len + out_cubic: [0, 0.15757887517146785, 0.2976680384087792, 0.42129629629629617, 0.5294924554183813, 0.6232853223593964, 0.7037037037037036, 0.7717764060356652, 0.8285322359396433, 0.875, 0.9122085048010974, 0.9411865569272977, 0.9629629629629629, 0.9785665294924554, 0.9890260631001372, 0.9953703703703703, 0.9986282578875172, 0.9998285322359396, 1], // eslint-disable-line max-len + inOut_cubic: [0, 0.0006858710562414266, 0.0054869684499314125, 0.018518518518518517, 0.0438957475994513, 0.08573388203017834, 0.14814814814814814, 0.23525377229080935, 0.3511659807956104, 0.5, 0.6488340192043895, 0.7647462277091908, 0.8518518518518519, 0.9142661179698217, 0.9561042524005487, 0.9814814814814815, 0.9945130315500685, 0.9993141289437586, 1], // eslint-disable-line max-len + in_sin: [0, 0.003805301908254455, 0.01519224698779198, 0.03407417371093169, 0.06030737921409157, 0.09369221296335006, 0.1339745962155613, 0.1808479557110082, 0.233955556881022, 0.2928932188134524, 0.35721239031346064, 0.42642356364895384, 0.4999999999999999, 0.5773817382593005, 0.6579798566743311, 0.7411809548974793, 0.8263518223330696, 0.9128442572523416, 0.9999999999999999], // eslint-disable-line max-len + out_sin: [0, 0.08715574274765817, 0.17364817766693033, 0.25881904510252074, 0.3420201433256687, 0.42261826174069944, 0.49999999999999994, 0.573576436351046, 0.6427876096865393, 0.7071067811865475, 0.766044443118978, 0.8191520442889918, 0.8660254037844386, 0.9063077870366499, 0.9396926207859083, 0.9659258262890683, 0.984807753012208, 0.9961946980917455, 1], // eslint-disable-line max-len + inOut_sin: [0, 0.00759612349389599, 0.030153689607045786, 0.06698729810778065, 0.116977778440511, 0.17860619515673032, 0.24999999999999994, 0.32898992833716556, 0.4131759111665348, 0.49999999999999994, 0.5868240888334652, 0.6710100716628343, 0.7499999999999999, 0.8213938048432696, 0.883022221559489, 0.9330127018922194, 0.9698463103929542, 0.9924038765061041, 1], // eslint-disable-line max-len + in_exp: [0, 0.0014352875901128893, 0.002109491677524035, 0.0031003926796253885, 0.004556754060844206, 0.006697218616039631, 0.009843133202303688, 0.014466792379488908, 0.021262343752724643, 0.03125, 0.045929202883612456, 0.06750373368076916, 0.09921256574801243, 0.1458161299470146, 0.2143109957132682, 0.31498026247371835, 0.46293735614364506, 0.6803950000871883, 1], // eslint-disable-line max-len + out_exp: [0, 0.31960499991281155, 0.5370626438563548, 0.6850197375262816, 0.7856890042867318, 0.8541838700529854, 0.9007874342519875, 0.9324962663192309, 0.9540707971163875, 0.96875, 0.9787376562472754, 0.9855332076205111, 0.9901568667976963, 0.9933027813839603, 0.9954432459391558, 0.9968996073203746, 0.9978905083224759, 0.9985647124098871, 1], // eslint-disable-line max-len + inOut_exp: [0, 0.0010547458387620175, 0.002278377030422103, 0.004921566601151844, 0.010631171876362321, 0.022964601441806228, 0.049606282874006216, 0.1071554978566341, 0.23146867807182253, 0.5, 0.7685313219281775, 0.892844502143366, 0.9503937171259937, 0.9770353985581938, 0.9893688281236377, 0.9950784333988482, 0.9977216229695779, 0.998945254161238, 1], // eslint-disable-line max-len + in_circle: [0, 0.0015444024660317135, 0.006192010000093506, 0.013986702816730645, 0.025003956956430873, 0.03935464078941209, 0.057190958417936644, 0.07871533601238889, 0.10419358352238339, 0.1339745962155614, 0.1685205807169019, 0.20845517506805522, 0.2546440075000701, 0.3083389112228482, 0.37146063894529113, 0.4472292016074334, 0.5418771527091488, 0.6713289009389102, 1], // eslint-disable-line max-len + out_circle: [0, 0.3286710990610898, 0.45812284729085123, 0.5527707983925666, 0.6285393610547089, 0.6916610887771518, 0.7453559924999298, 0.7915448249319448, 0.8314794192830981, 0.8660254037844386, 0.8958064164776166, 0.9212846639876111, 0.9428090415820634, 0.9606453592105879, 0.9749960430435691, 0.9860132971832694, 0.9938079899999065, 0.9984555975339683, 1], // eslint-disable-line max-len + inOut_circle: [0, 0.003096005000046753, 0.012501978478215436, 0.028595479208968322, 0.052096791761191696, 0.08426029035845095, 0.12732200375003505, 0.18573031947264557, 0.2709385763545744, 0.5, 0.7290614236454256, 0.8142696805273546, 0.8726779962499649, 0.915739709641549, 0.9479032082388084, 0.9714045207910317, 0.9874980215217846, 0.9969039949999532, 1], // eslint-disable-line max-len + in_back_: [0, -0.004788556241426612, -0.017301289437585736, -0.0347587962962963, -0.05438167352537723, -0.07339051783264748, -0.08900592592592595, -0.09844849451303156, -0.0989388203017833, -0.08769750000000004, -0.06194513031550073, -0.018902307956104283, 0.044210370370370254, 0.13017230795610413, 0.2417629080932785, 0.3817615740740742, 0.5529477091906719, 0.7581007167352535, 0.9999999999999998], // eslint-disable-line max-len + out_back_: [2.220446049250313e-16, 0.24189928326474652, 0.44705229080932807, 0.6182384259259258, 0.7582370919067215, 0.8698276920438959, 0.9557896296296297, 1.0189023079561044, 1.0619451303155008, 1.0876975, 1.0989388203017834, 1.0984484945130315, 1.089005925925926, 1.0733905178326475, 1.0543816735253773, 1.0347587962962963, 1.0173012894375857, 1.0047885562414267, 1] // eslint-disable-line max-len + }; + + Object.keys(SAMPLES).forEach((type) => { + it(`should ease ${type}`, () => { + const [modeName, easingName, isFunction] = type.split('_'); + let easing = Easing[easingName]; + if (isFunction !== undefined) { + easing = easing(); + } + const computed = sampleEasingFunction(Easing[modeName](easing)); + const samples = SAMPLES[type]; + + computed.forEach((value, key) => { + expect(value).to.be.closeTo(samples[key], 2); + }); + }); + }); + }); +}); diff --git a/tests/integration/apis/Geolocation.test.js b/tests/integration/apis/Geolocation.test.js new file mode 100644 index 0000000..e9addbd --- /dev/null +++ b/tests/integration/apis/Geolocation.test.js @@ -0,0 +1,13 @@ +import { expect } from 'chai'; + + +describe('Geolocation', () => { + const Geolocation = global.navigator.geolocation; + + it('should have right functions', function () { + expect(Geolocation.getCurrentPosition).to.be.a('function'); + expect(Geolocation.watchPosition).to.be.a('function'); + expect(Geolocation.clearWatch).to.be.a('function'); + expect(Geolocation.stopObserving).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/ImageEditor.test.js b/tests/integration/apis/ImageEditor.test.js new file mode 100644 index 0000000..e0bfaf8 --- /dev/null +++ b/tests/integration/apis/ImageEditor.test.js @@ -0,0 +1,10 @@ +import { expect } from 'chai'; + + +describe('ImageEditor', () => { + const { ImageEditor } = require('react-native'); + + it('should have right functions', function () { + expect(ImageEditor.cropImage).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/ImagePickerIOS.test.js b/tests/integration/apis/ImagePickerIOS.test.js new file mode 100644 index 0000000..8971757 --- /dev/null +++ b/tests/integration/apis/ImagePickerIOS.test.js @@ -0,0 +1,13 @@ +import { expect } from 'chai'; + + +describe('ImagePickerIOS', () => { + const { ImagePickerIOS } = require('react-native'); + + it('should have right functions', function () { + expect(ImagePickerIOS.canRecordVideos).to.be.a('function'); + expect(ImagePickerIOS.canUseCamera).to.be.a('function'); + expect(ImagePickerIOS.openCameraDialog).to.be.a('function'); + expect(ImagePickerIOS.openSelectDialog).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/ImageStore.test.js b/tests/integration/apis/ImageStore.test.js new file mode 100644 index 0000000..bf6c29e --- /dev/null +++ b/tests/integration/apis/ImageStore.test.js @@ -0,0 +1,12 @@ +import { expect } from 'chai'; + +describe('ImageStore', () => { + const { ImageStore } = require('react-native'); + + it('should have right functions', function () { + expect(ImageStore.hasImageForTag).to.be.a('function'); + expect(ImageStore.removeImageForTag).to.be.a('function'); + expect(ImageStore.addImageFromBase64).to.be.a('function'); + expect(ImageStore.getBase64ForTag).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/InteractionManager.test.js b/tests/integration/apis/InteractionManager.test.js new file mode 100644 index 0000000..e50ef28 --- /dev/null +++ b/tests/integration/apis/InteractionManager.test.js @@ -0,0 +1,21 @@ +import { expect } from 'chai'; + +describe('InteractionManager', () => { + const { InteractionManager } = require('react-native'); + + it('should have right functions', function () { + expect(InteractionManager.runAfterInteractions).to.be.a('function'); + expect(InteractionManager.createInteractionHandle).to.be.a('function'); + expect(InteractionManager.clearInteractionHandle).to.be.a('function'); + expect(InteractionManager.setDeadline).to.be.a('function'); + expect(InteractionManager.addListener).to.be.a('function'); + }); + + it('should schedule task', function () { + const cancelablePromise = InteractionManager.runAfterInteractions(() => {}); + expect(cancelablePromise).to.have.all.keys('then', 'done', 'cancel'); + expect(cancelablePromise.then).to.be.a('function'); + expect(cancelablePromise.done).to.be.a('function'); + expect(cancelablePromise.cancel).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/Keyboard.test.js b/tests/integration/apis/Keyboard.test.js new file mode 100644 index 0000000..e58a2ab --- /dev/null +++ b/tests/integration/apis/Keyboard.test.js @@ -0,0 +1,13 @@ +import { expect } from 'chai'; + +describe('Keyboard', () => { + const { Keyboard } = require('react-native'); + + it('should bind events', function () { + expect(Keyboard.addListener).to.be.a('function'); + expect(Keyboard.addListener().remove).to.be.a('function'); + expect(Keyboard.removeListener).to.be.a('function'); + expect(Keyboard.removeAllListeners).to.be.a('function'); + expect(Keyboard.dismiss).to.be.a('function'); + }); +}); diff --git a/tests/integration/apis/Linking.test.js b/tests/integration/apis/Linking.test.js new file mode 100644 index 0000000..c66a4d0 --- /dev/null +++ b/tests/integration/apis/Linking.test.js @@ -0,0 +1,17 @@ +import { expect } from 'chai'; + +describe('Linking', () => { + const { Linking } = require('react-native'); + + it('should have right functions', function () { + expect(Linking.addListener).to.be.a('function'); + expect(Linking.removeEventListener).to.be.a('function'); + expect(Linking.openURL).to.be.a('function'); + expect(Linking.canOpenURL).to.be.a('function'); + expect(Linking.getInitialURL).to.be.a('function'); + }); + + it('should be allowed to open url', function (done) { + expect(Linking.canOpenURL('test')).to.eventually.equal(true).notify(done); + }); +}); diff --git a/tests/integration/apis/Platform.test.js b/tests/integration/apis/Platform.test.js new file mode 100644 index 0000000..3f2ec76 --- /dev/null +++ b/tests/integration/apis/Platform.test.js @@ -0,0 +1,23 @@ +import { expect } from 'chai'; +import { assuming } from 'mocha-assume'; +import { reactNativeVersion } from '../../test-utils'; + +describe('Platform', () => { + const { Platform } = require('react-native'); + it('should have correct keys', function () { + expect(Platform.OS).to.equal('ios'); + expect(Platform.Version).to.equal('10'); + }); + + assuming(reactNativeVersion >= 0.41).it('should have tvOS check', function () { + expect(Platform.isTVOS).to.be.false; + }); + + assuming(reactNativeVersion >= 0.42).it('should have testing check', function () { + expect(Platform.isTesting).to.be.true; + }); + + assuming(reactNativeVersion >= 0.43).it('should have iPad check', function () { + expect(Platform.isPad).to.be.true; + }); +}); diff --git a/tests/integration/apis/StyleSheet.test.js b/tests/integration/apis/StyleSheet.test.js new file mode 100644 index 0000000..94be83e --- /dev/null +++ b/tests/integration/apis/StyleSheet.test.js @@ -0,0 +1,58 @@ +import { expect } from 'chai'; + + +describe('StyleSheet', () => { + const { StyleSheet } = require('react-native'); + let styles, rawStyles; + + beforeEach(function () { + rawStyles = { + listItem: { + flex: 1, + fontSize: 16, + color: 'white' + }, + selectedListItem: { + color: 'green' + }, + headerItem: { + fontWeight: 'bold' + } + }; + styles = StyleSheet.create(rawStyles); + }); + + it('shouldnt mutate', function () { + expect(rawStyles).to.deep.equal(styles); + }); + + describe('Flatten', function () { + it('should flatten', () => { + const result = StyleSheet.flatten(styles.listItem); + expect(result).to.deep.equal({ + flex: 1, + fontSize: 16, + color: 'white' + }); + }); + + it('should flatten with array', () => { + const result = StyleSheet.flatten([styles.listItem, styles.selectedListItem]); + expect(result).to.deep.equal({ + flex: 1, + fontSize: 16, + color: 'green' + }); + }); + + it('should flatten with nested array', () => { + const result = StyleSheet.flatten([styles.listItem, [styles.headerItem, styles.selectedListItem]]); + expect(result).to.deep.equal({ + flex: 1, + fontSize: 16, + color: 'green', + fontWeight: 'bold' + }); + }); + }); +}); diff --git a/tests/integration/components.test.js b/tests/integration/components.test.js new file mode 100644 index 0000000..4acd618 --- /dev/null +++ b/tests/integration/components.test.js @@ -0,0 +1,188 @@ +import React from 'react'; // eslint-disable-line no-unused-vars +import { shallow } from 'enzyme'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { buildComponentHTML } from '../test-utils'; + +const COMPONENTS = [ + 'Image', + 'Text', + 'TextInput', + 'Modal', + 'View', + 'ScrollView', + 'RefreshControl', + + 'ToolbarAndroid' +]; + +describe('Components', function () { + const ReactNative = require('react-native'); + + COMPONENTS.forEach(function (component) { // Render loads fast! + it(`should render ${component}`, function () { + const Component = ReactNative[component]; + const instance = shallow(); + expect(instance.html()).to.include(`<${component}`); + }); + }); + + it('should render ActivityIndicator', function () { + const { ActivityIndicator } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.include('); + expect(instance.html()).to.include('Title'); + }); + + it('should render DatePickerIOS', function () { + const { DatePickerIOS } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.include('DatePicker'); + }); + + it('should render DrawerLayoutAndroid', function () { + const { DrawerLayoutAndroid } = ReactNative; + const handleRenderNavigationView = sinon.spy(); + const instance = shallow(); + expect(instance.html()).to.include(' { + const { DrawerLayoutAndroid } = ReactNative; + expect(DrawerLayoutAndroid.positions).to.deep.equal({ + Left: 'LEFT', + Right: 'RIGHT' + }); + }); + + it('should render ListView', function () { + const { ListView } = ReactNative; + const dataSource = new ListView.DataSource({ + rowHasChanged: (r1, r2) => r1 !== r2 + }).cloneWithRows([]); + const instance = shallow(); + expect(instance.html()).to.include('); + expect(instance.html()).to.include('); + expect(instance.html()).to.include('Title'); + expect(instance.html()).to.include('); + expect(instance.html()).to.include('Picker'); + }); + + it('should render PickerIOS', function () { + const { PickerIOS } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.include('Picker'); + }); + + it('should render ProgressBarAndroid', function () { + const { ProgressBarAndroid } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.include('); + expect(instance.html()).to.include('ProgressView'); + }); + + it('should render SegmentedControlIOS', function () { + const { SegmentedControlIOS } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.include('SegmentedControl'); + }); + + it('should render Slider', function () { + const { Slider } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.include('Slider'); + }); + + it('should render SnapshotViewIOS', function () { + const { SnapshotViewIOS } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.include('); + expect(instance.html()).to.be.null; + }); + + it('should render Switch', function () { + const { Switch } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.include('Switch'); + }); + + it('should render TabBarIOS', function () { + const { TabBarIOS } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.include('TabBar'); + }); + + it('should render TouchableHighlight', function () { + const { TouchableHighlight, Text } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.include('); + expect(instance.html()).to.include('); + expect(instance.html()).to.include('); + expect(instance.html()).to.include('); + expect(instance.html()).to.equal(buildComponentHTML('AndroidViewPager')); + }); + + it('should render WebView', function () { + const { WebView } = ReactNative; + const instance = shallow(); + expect(instance.html()).to.contain('WebView'); + expect(instance.html()).to.contain('); + expect(instance.html()).to.equal(''); + }); + + it('Should replace RCT in name', function () { + testComponent.name = testComponent.displayName = 'RCTtest'; + mockery.registerMock('test', testComponent); + const Component = createMockComponent('test'); + const instance = shallow(); + expect(instance.html()).to.equal(''); + }); + + it('Should replace RK in name', function () { + testComponent.name = testComponent.displayName = 'RKtest'; + mockery.registerMock('test', testComponent); + const Component = createMockComponent('test'); + const instance = shallow(); + expect(instance.html()).to.equal(''); + }); + + it('Should pass props and children', function () { + const Component = createMockComponent('test'); + const instance = shallow(bar); + expect(instance.html()).to.equal('bar'); + }); + + it('should be accessible globally', function () { + const RequiredComponent = require('requireNativeComponent')('test'); // eslint-disable-line import/no-unresolved + const Component = createMockComponent('test'); + expect(shallow().html()).to.equal(shallow().html()); + }); + + describe('Native Components', function () { + MOCK_COMPONENTS.forEach(function (component) { + it('should require ' + component, function () { + const Component = require(component); + expect(Component).to.be.a('function'); + const instance = shallow(); + expect(instance.html()).to.include(component); + }); + }); + }); +}); diff --git a/tests/native-modules.test.js b/tests/native-modules.test.js new file mode 100644 index 0000000..0e7a6dd --- /dev/null +++ b/tests/native-modules.test.js @@ -0,0 +1,400 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; + +import { expectSpy } from './test-utils'; + +const NativeModules = require('../src/NativeModules'); + +describe('Native Modules', function () { + it('Should be defined', function () { + expect(NativeModules).to.be.an('object'); + expect(Object.keys(require('NativeModules'))).to.deep.equal(Object.keys(NativeModules)); // eslint-disable-line import/no-unresolved + }); + + it('should be requirable', function () { + Object.keys(NativeModules).forEach(function (mod) { + expect(Object.keys(require(mod))).to.deep.equal(Object.keys(NativeModules[mod])); + }); + }); + + describe('Specific Modules', function () { + describe('AlertManager', function () { + it('should have alertWithArgs as spy', function () { + expectSpy(NativeModules.AlertManager.alertWithArgs); + }); + }); + + describe('AppState', function () { + it('should have addEventListener as spy', function () { + expectSpy(NativeModules.AppState.addEventListener); + }); + + it('should have removeEventListener as spy', function () { + expectSpy(NativeModules.AppState.removeEventListener); + }); + }); + + describe('AsyncLocalStorage', function () { + it('should have clear as spy', function () { + expectSpy(NativeModules.AsyncLocalStorage.clear); + }); + + it('should have getItem as spy', function () { + expectSpy(NativeModules.AsyncLocalStorage.getItem); + }); + + it('should have removeItem as spy', function () { + expectSpy(NativeModules.AsyncLocalStorage.removeItem); + }); + + it('should have setItem as spy', function () { + expectSpy(NativeModules.AsyncLocalStorage.setItem); + }); + + it('should have multiGet as spy', function () { + expectSpy(NativeModules.AsyncLocalStorage.multiGet); + }); + }); + + describe('BuildInfo', function () { + it('should have version keys', function () { + expect(NativeModules.BuildInfo.appVersion).to.equal('0'); + expect(NativeModules.BuildInfo.buildVersion).to.equal('0'); + }); + }); + + describe('Clipboard', function () { + it('should have setString as spy', function () { + expectSpy(NativeModules.Clipboard.setString); + }); + + it('should have getString as spy', function () { + expectSpy(NativeModules.Clipboard.getString); + }); + }); + + describe('DataManager', function () { + it('should have queryData as spy', function () { + expectSpy(NativeModules.DataManager.queryData); + }); + }); + + describe('FacebookSDK', function () { + it('should have login as spy', function () { + expectSpy(NativeModules.FacebookSDK.login); + }); + + it('should have logout as spy', function () { + expectSpy(NativeModules.FacebookSDK.logout); + }); + + it('should have queryGraphPath as spy', function () { + expectSpy(NativeModules.FacebookSDK.queryGraphPath); + const callback = sinon.spy(); + NativeModules.FacebookSDK.queryGraphPath('', '', '', callback); + expect(NativeModules.FacebookSDK.queryGraphPath).to.have.been.calledOnce; + expect(callback).to.have.been.calledOnce; + }); + }); + + describe('FbRelayNativeAdapter', function () { + it('should have updateCLC as spy', function () { + expectSpy(NativeModules.FbRelayNativeAdapter.updateCLC); + }); + }); + + describe('GraphPhotoUpload', function () { + it('should have upload as spy', function () { + expectSpy(NativeModules.GraphPhotoUpload.upload); + }); + }); + + describe('I18n', function () { + it('should have translation dict', function () { + expect(NativeModules.I18n.translationsDictionary).to.be.a('string'); + const dict = JSON.parse(NativeModules.I18n.translationsDictionary); + expect(Object.keys(dict)).to.have.lengthOf(1); + }); + }); + + describe('ImageLoader', function () { + it('should have getSize as spy', function (done) { + expectSpy(NativeModules.ImageLoader.getSize); + const callback = sinon.spy(function () { + expect(callback).to.have.been.calledWith(320, 240); + done(); + }); + NativeModules.ImageLoader.getSize('', callback); + expect(NativeModules.ImageLoader.getSize).to.have.been.calledOnce; + }); + + it('should have prefetchImage as spy', function () { + expectSpy(NativeModules.ImageLoader.prefetchImage); + }); + }); + + describe('ImagePickerIOS', function () { + it('should have canRecordVideos as spy', function () { + expectSpy(NativeModules.ImagePickerIOS.canRecordVideos); + const callback = sinon.spy(); + NativeModules.ImagePickerIOS.canRecordVideos(callback); + expect(callback).to.have.been.called; + }); + + it('should have canUseCamera as spy', function () { + expectSpy(NativeModules.ImagePickerIOS.canUseCamera); + const callback = sinon.spy(); + NativeModules.ImagePickerIOS.canUseCamera(callback); + expect(callback).to.have.been.called; + }); + + it('should have openCameraDialog as spy', function () { + expectSpy(NativeModules.ImagePickerIOS.openCameraDialog); + }); + + it('should have openSelectDialog as spy', function () { + expectSpy(NativeModules.ImagePickerIOS.openSelectDialog); + }); + }); + + describe('ImageViewManager', function () { + it('should have getSize as spy', function (done) { + expectSpy(NativeModules.ImageViewManager.getSize); + const callback = sinon.spy(function () { + expect(callback).to.have.been.calledWith(320, 240); + done(); + }); + NativeModules.ImageViewManager.getSize('', callback); + expect(NativeModules.ImageViewManager.getSize).to.have.been.calledOnce; + }); + + it('should have prefetchImage as spy', function () { + expectSpy(NativeModules.ImageViewManager.prefetchImage); + }); + }); + + describe('KeyboardObserver', function () { + it('should have addListener as spy', function () { + expectSpy(NativeModules.KeyboardObserver.addListener); + }); + + it('should have removeListeners as spy', function () { + expectSpy(NativeModules.KeyboardObserver.removeListeners); + }); + }); + + describe('IntentAndroid', function () { + it('should have openURL as spy', function () { + expectSpy(NativeModules.IntentAndroid.openURL); + }); + + it('should have canOpenURL as spy', function (done) { + expectSpy(NativeModules.IntentAndroid.canOpenURL); + expect(NativeModules.IntentAndroid.canOpenURL('url')).to.eventually.equal(true).notify(done); + }); + }); + + describe('LinkingManager', function () { + it('should have openURL as spy', function () { + expectSpy(NativeModules.LinkingManager.openURL); + }); + + it('should have canOpenURL as spy', function (done) { + expectSpy(NativeModules.LinkingManager.canOpenURL); + expect(NativeModules.LinkingManager.canOpenURL('url')).to.eventually.equal(true).notify(done); + }); + }); + + describe('ModalFullscreenViewManager', function () { + it('should be empty', function () { + expect(NativeModules.ModalFullscreenViewManager).to.be.an('object'); + expect(Object.keys(NativeModules.ModalFullscreenViewManager)).to.deep.equal([]); + }); + }); + + describe('Networking', function () { + it('should have sendRequest as spy', function () { + expectSpy(NativeModules.Networking.sendRequest); + }); + + it('should have abortRequest as spy', function () { + expectSpy(NativeModules.Networking.abortRequest); + }); + + it('should have addListener as spy', function () { + expectSpy(NativeModules.Networking.addListener); + }); + + it('should have removeListeners as spy', function () { + expectSpy(NativeModules.Networking.removeListeners); + }); + }); + + describe('LocationObserver', function () { + it('should have getCurrentPosition as spy', function () { + expectSpy(NativeModules.LocationObserver.getCurrentPosition); + }); + + it('should have startObserving as spy', function () { + expectSpy(NativeModules.LocationObserver.startObserving); + }); + + it('should have stopObserving as spy', function () { + expectSpy(NativeModules.LocationObserver.stopObserving); + }); + }); + + describe('SourceCode', function () { + it('should have null scriptUrl', function () { + expect(NativeModules.SourceCode.scriptURL).to.be.null; + }); + }); + + describe('StatusBarManager', function () { + it('should have setStyle as spy', function () { + expectSpy(NativeModules.StatusBarManager.setStyle); + }); + + it('should have setColor as spy', function () { + expectSpy(NativeModules.StatusBarManager.setColor); + }); + + it('should have setHidden as spy', function () { + expectSpy(NativeModules.StatusBarManager.setHidden); + }); + + it('should have setNetworkActivityIndicatorVisible as spy', function () { + expectSpy(NativeModules.StatusBarManager.setNetworkActivityIndicatorVisible); + }); + + it('should have setBackgroundColor as spy', function () { + expectSpy(NativeModules.StatusBarManager.setBackgroundColor); + }); + + it('should have setTranslucent as spy', function () { + expectSpy(NativeModules.StatusBarManager.setTranslucent); + }); + }); + + describe('Timing', function () { + it('should have createTimer as spy', function () { + expectSpy(NativeModules.Timing.createTimer); + }); + + it('should have deleteTimer as spy', function () { + expectSpy(NativeModules.Timing.deleteTimer); + }); + }); + + describe('UIManager', function () { + it('should have customBubblingEventTypes as object', function () { + expect(NativeModules.UIManager.customBubblingEventTypes).to.be.an('object'); + expect(Object.keys(NativeModules.UIManager.customBubblingEventTypes)).to.deep.equal([]); + }); + + it('should have customDirectEventTypes as object', function () { + expect(NativeModules.UIManager.customDirectEventTypes).to.be.an('object'); + expect(Object.keys(NativeModules.UIManager.customDirectEventTypes)).to.deep.equal([]); + }); + + it('should have window dimensions', function () { + expect(NativeModules.UIManager.Dimensions).to.be.an('object'); + expect(NativeModules.UIManager.Dimensions.window).to.deep.equal({ + fontScale: 2, + height: 1334, + scale: 2, + width: 750 + }); + + it('should have createView as spy', function () { + expectSpy(NativeModules.UIManager.createView); + }); + + it('should have removeRootView as spy', function () { + expectSpy(NativeModules.UIManager.removeRootView); + }); + + it('should have setChildren as spy', function () { + expectSpy(NativeModules.UIManager.setChildren); + }); + + it('should have manageChildren as spy', function () { + expectSpy(NativeModules.UIManager.manageChildren); + }); + + it('should have updateView as spy', function () { + expectSpy(NativeModules.UIManager.updateView); + }); + + it('should have removeSubviewsFromContainerWithID as spy', function () { + expectSpy(NativeModules.UIManager.removeSubviewsFromContainerWithID); + }); + + it('should have replaceExistingNonRootView as spy', function () { + expectSpy(NativeModules.UIManager.replaceExistingNonRootView); + }); + }); + + it('should have ModalFullscreenView as object', function () { + expect(NativeModules.UIManager.ModalFullscreenView).to.be.an('object'); + expect(NativeModules.UIManager.ModalFullscreenView.Constants).to.be.an('object'); + expect(Object.keys(NativeModules.UIManager.ModalFullscreenView.Constants)).to.deep.equal([]); + }); + + it('should have ScrollView as object', function () { + expect(NativeModules.UIManager.ScrollView).to.be.an('object'); + expect(NativeModules.UIManager.ScrollView.Constants).to.be.an('object'); + expect(Object.keys(NativeModules.UIManager.ScrollView.Constants)).to.deep.equal([]); + }); + + it('should have View as object', function () { + expect(NativeModules.UIManager.View).to.be.an('object'); + expect(NativeModules.UIManager.View.Constants).to.be.an('object'); + expect(Object.keys(NativeModules.UIManager.View.Constants)).to.deep.equal([]); + }); + }); + + describe('WebSocketModule', function () { + it('should have connect as spy', function () { + expectSpy(NativeModules.WebSocketModule.connect); + }); + + it('should have send as spy', function () { + expectSpy(NativeModules.WebSocketModule.send); + }); + + it('should have sendBinary as spy', function () { + expectSpy(NativeModules.WebSocketModule.sendBinary); + }); + + it('should have ping as spy', function () { + expectSpy(NativeModules.WebSocketModule.ping); + }); + + it('should have close as spy', function () { + expectSpy(NativeModules.WebSocketModule.close); + }); + + it('should have addListener as spy', function () { + expectSpy(NativeModules.WebSocketModule.addListener); + }); + + it('should have removeListeners as spy', function () { + expectSpy(NativeModules.WebSocketModule.removeListeners); + }); + }); + + describe('PlatformConstants', function () { + it('should have necessary details', function () { + expect(NativeModules.PlatformConstants.osVersion).to.be.a('string'); + expect(NativeModules.PlatformConstants.interfaceIdiom).to.be.a('string'); + expect(NativeModules.PlatformConstants.isTesting).to.be.a('boolean'); + expect(NativeModules.PlatformConstants.version).to.be.a('string'); + }); + + it('should have valid values', function () { + expect(NativeModules.PlatformConstants.isTesting).to.be.true; + }); + }); + }); +}); diff --git a/tests/no-import.test.js b/tests/no-import.test.js new file mode 100644 index 0000000..dcf9418 --- /dev/null +++ b/tests/no-import.test.js @@ -0,0 +1,14 @@ +import { expect } from 'chai'; +import path from 'path'; + + +describe('React-Native-Mock import', function () { + it('should throw', function () { + expect(() => require('../src/no-import')).to.throw; + }); + + it('should happen when requiring react-native-mock', function () { + const { main } = require('../package.json'); + expect(() => require(path.join('..', main))).to.throw; + }); +}); diff --git a/tests/react-native-mock.test.js b/tests/react-native-mock.test.js new file mode 100644 index 0000000..38193ba --- /dev/null +++ b/tests/react-native-mock.test.js @@ -0,0 +1,7 @@ +import { expect } from 'chai'; + +describe('React Native Mock', function () { + it('Should allow requiring of react-native', function () { + expect(require('react-native')).to.be.ok; + }); +}); diff --git a/tests/react-native-version.test.js b/tests/react-native-version.test.js new file mode 100644 index 0000000..6cf765d --- /dev/null +++ b/tests/react-native-version.test.js @@ -0,0 +1,17 @@ +import { expect } from 'chai'; +import semver from 'semver'; + + +describe('React-Native version', function () { + it('should return a version', function () { + const version = require('../src/react-native-version'); + expect(version).to.be.a('string'); + expect(semver.valid(version)).to.equal(version); + }); + + it('should be a valid version', function () { + const version = require('../src/react-native-version'); + expect(semver.valid(version)).to.equal(version); + expect(semver.clean(version)).to.equal(version); + }); +}); diff --git a/tests/test-utils.js b/tests/test-utils.js new file mode 100644 index 0000000..cf20cf6 --- /dev/null +++ b/tests/test-utils.js @@ -0,0 +1,15 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import semver from 'semver'; + +export const buildComponentHTML = componentName => `<${componentName}>`; + +export const expectSpy = function isSpy(spy) { + expect(spy).to.contain.all.keys(Object.keys(sinon.spy())); + expect(spy.id).to.contain('spy'); + expect(spy.callCount).to.be.a('number'); + expect(spy.called).to.be.a('boolean'); + expect(spy.notCalled).to.be.a('boolean'); +}; + +export const reactNativeVersion = semver.minor(require('../src/react-native-version')) / 100;