[fix] correct types

Fix #465
This commit is contained in:
Nicolas Gallagher
2017-06-05 19:51:34 -07:00
parent 22999d7e5b
commit 77fd867421
18 changed files with 211 additions and 135 deletions
+20 -8
View File
@@ -23,35 +23,47 @@ Install dependencies (requires [yarn](https://yarnpkg.com/en/docs/install):
yarn yarn
``` ```
## Unit tests ## Automated tests
To run flow:
```
npm run flow
```
To run the unit tests: To run the unit tests:
``` ```
npm test npm run jest
``` ```
…in watch mode: …in watch mode:
``` ```
npm run test:watch npm run jest:watch
```
To run all automated tests:
```
npm test
``` ```
## Visual tests ## Visual tests
Run the interactive storybook: To run the interactive storybook:
``` ```
npm run docs:start npm run docs:start
``` ```
Run generate a static build of the storybook: To generate a static build of the storybook:
``` ```
npm run docs:build npm run docs:build
``` ```
Run the performance benchmarks in a browser (opening `./performance/index.html`): To run the performance benchmarks in a browser (opening `./benchmarks/index.html`):
``` ```
npm run benchmarks npm run benchmarks
@@ -59,7 +71,7 @@ npm run benchmarks
## Compile and build ## Compile and build
Compile the source code to `dist`: To compile the source code to `dist`:
``` ```
npm run compile npm run compile
@@ -73,7 +85,7 @@ npm run build
### Pre-commit ### Pre-commit
Before creating a commit run: To format and lint code before commit:
``` ```
npm run precommit npm run precommit
+10 -4
View File
@@ -18,19 +18,25 @@
"flow": "flow", "flow": "flow",
"fmt": "find benchmarks docs src -name '*.js' | grep -v -E '(node_modules|dist)' | xargs npm run fmt:cmd", "fmt": "find benchmarks docs src -name '*.js' | grep -v -E '(node_modules|dist)' | xargs npm run fmt:cmd",
"fmt:cmd": "prettier --print-width=100 --single-quote --write", "fmt:cmd": "prettier --print-width=100 --single-quote --write",
"jest": "jest",
"jest:watch": "npm run test -- --watch",
"lint": "npm run lint:cmd -- benchmarks docs src", "lint": "npm run lint:cmd -- benchmarks docs src",
"lint:cmd": "eslint --fix --ignore-path .gitignore", "lint:cmd": "eslint --fix --ignore-path .gitignore",
"precommit": "lint-staged", "precommit": "lint-staged",
"release": "npm run lint && npm run test && npm run compile && npm run build && npm publish", "release": "npm run lint && npm run test && npm run compile && npm run build && npm publish",
"test": "jest", "test": "flow && jest"
"test:watch": "npm run test -- --watch"
}, },
"babel": { "babel": {
"presets": [ "presets": [
"react-native" "react-native"
], ],
"plugins": [ "plugins": [
[ "transform-react-remove-prop-types", { "mode": "wrap" } ] [
"transform-react-remove-prop-types",
{
"mode": "wrap"
}
]
] ]
}, },
"jest": { "jest": {
@@ -77,7 +83,7 @@
"eslint-plugin-promise": "^3.5.0", "eslint-plugin-promise": "^3.5.0",
"eslint-plugin-react": "^6.10.3", "eslint-plugin-react": "^6.10.3",
"file-loader": "^0.11.1", "file-loader": "^0.11.1",
"flow-bin": "^0.46.0", "flow-bin": "^0.47.0",
"jest": "^19.0.2", "jest": "^19.0.2",
"lint-staged": "^3.4.2", "lint-staged": "^3.4.2",
"node-libs-browser": "^0.5.3", "node-libs-browser": "^0.5.3",
+65
View File
@@ -0,0 +1,65 @@
/**
* @flow
*/
import StyleSheet from '../StyleSheet';
import View from '../../components/View';
import { any, node } from 'prop-types';
import React, { Component } from 'react';
type Context = {
rootTag: any
};
type Props = {
children?: React.Children,
rootTag: any
};
type State = {
mainKey: number
};
class AppContainer extends Component {
props: Props;
state: State = { mainKey: 1 };
static childContextTypes = {
rootTag: any
};
static propTypes = {
children: node,
rootTag: any.isRequired
};
getChildContext(): Context {
return {
rootTag: this.props.rootTag
};
}
render() {
return (
<View pointerEvents="box-none" style={styles.appContainer}>
<View
children={this.props.children}
key={this.state.mainKey}
pointerEvents="box-none"
style={styles.appContainer}
/>
</View>
);
}
}
const styles = StyleSheet.create({
/**
* Ensure that the application covers the whole screen.
*/
appContainer: {
flex: 1
}
});
module.exports = AppContainer;
-41
View File
@@ -1,41 +0,0 @@
/**
* @flow
*/
import StyleSheet from '../StyleSheet';
import View from '../../components/View';
import { any, object } from 'prop-types';
import React, { Component } from 'react';
class ReactNativeApp extends Component {
static propTypes = {
initialProps: object,
rootComponent: any.isRequired,
rootTag: any
};
render() {
const { initialProps, rootComponent: RootComponent, rootTag } = this.props;
return (
<View style={styles.appContainer}>
<RootComponent {...initialProps} rootTag={rootTag} />
</View>
);
}
}
const styles = StyleSheet.create({
/**
* Ensure that the application covers the whole screen.
*/
appContainer: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0
}
});
module.exports = ReactNativeApp;
@@ -1,6 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`apis/AppRegistry/renderApplication getApplication 1`] = ` exports[`apis/AppRegistry/renderApplication getApplication 1`] = `
<AppContainer
rootTag={Object {}}
>
<RootComponent />
</AppContainer>
`;
exports[`apis/AppRegistry/renderApplication getApplication 2`] = `
"<style id=\\"react-native-stylesheet-static\\"> "<style id=\\"react-native-stylesheet-static\\">
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);} html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}
body{margin:0;} body{margin:0;}
@@ -41,6 +49,7 @@ input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit
.rn-boxSizing-deolkf{box-sizing:border-box} .rn-boxSizing-deolkf{box-sizing:border-box}
.rn-display-6koalj{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex} .rn-display-6koalj{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}
.rn-flexShrink-1qe8dj5{-webkit-flex-shrink:0;flex-shrink:0} .rn-flexShrink-1qe8dj5{-webkit-flex-shrink:0;flex-shrink:0}
.rn-flexShrink-1wbh5a2{-webkit-flex-shrink:1;flex-shrink:1}
.rn-flexBasis-1mlwlqe{-webkit-flex-basis:auto;flex-basis:auto} .rn-flexBasis-1mlwlqe{-webkit-flex-basis:auto;flex-basis:auto}
.rn-flexDirection-eqz5dr{-webkit-box-direction:normal;-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column} .rn-flexDirection-eqz5dr{-webkit-box-direction:normal;-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column}
.rn-marginTop-1mnahxq{margin-top:0px} .rn-marginTop-1mnahxq{margin-top:0px}
@@ -55,5 +64,6 @@ input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit
.rn-paddingLeft-gy4na3{padding-left:0px} .rn-paddingLeft-gy4na3{padding-left:0px}
.rn-zIndex-1lgpqti{z-index:0} .rn-zIndex-1lgpqti{z-index:0}
.rn-zIndex-1wyyakw{z-index:-1} .rn-zIndex-1wyyakw{z-index:-1}
.rn-flexGrow-16y2uox{-webkit-flex-grow:1;flex-grow:1}
</style>" </style>"
`; `;
@@ -3,13 +3,13 @@
import { getApplication } from '../renderApplication'; import { getApplication } from '../renderApplication';
import React from 'react'; import React from 'react';
const component = () => <div />; const RootComponent = () => <div />;
describe('apis/AppRegistry/renderApplication', () => { describe('apis/AppRegistry/renderApplication', () => {
test('getApplication', () => { test('getApplication', () => {
const { element, stylesheet } = getApplication(component, {}); const { element, stylesheet } = getApplication(RootComponent, {});
expect(element).toBeTruthy(); expect(element).toMatchSnapshot();
expect(stylesheet).toMatchSnapshot(); expect(stylesheet).toMatchSnapshot();
}); });
}); });
+2 -3
View File
@@ -6,7 +6,6 @@
* @flow * @flow
*/ */
import { Component } from 'react';
import invariant from 'fbjs/lib/invariant'; import invariant from 'fbjs/lib/invariant';
import { unmountComponentAtNode } from 'react-dom'; import { unmountComponentAtNode } from 'react-dom';
import renderApplication, { getApplication } from './renderApplication'; import renderApplication, { getApplication } from './renderApplication';
@@ -14,9 +13,9 @@ import renderApplication, { getApplication } from './renderApplication';
const emptyObject = {}; const emptyObject = {};
const runnables = {}; const runnables = {};
type ComponentProvider = () => Component<any, any, any>; export type ComponentProvider = () => ReactClass<any>;
type AppConfig = { export type AppConfig = {
appKey: string, appKey: string,
component?: ComponentProvider, component?: ComponentProvider,
run?: Function run?: Function
+14 -8
View File
@@ -8,25 +8,31 @@
import invariant from 'fbjs/lib/invariant'; import invariant from 'fbjs/lib/invariant';
import { render } from 'react-dom'; import { render } from 'react-dom';
import ReactNativeApp from './ReactNativeApp'; import AppContainer from './AppContainer';
import StyleSheet from '../../apis/StyleSheet'; import StyleSheet from '../../apis/StyleSheet';
import React, { Component } from 'react'; import React from 'react';
export default function renderApplication( export default function renderApplication(
RootComponent: Component, RootComponent: ReactClass<Object>,
initialProps: Object, initialProps: Object,
rootTag: any rootTag: any
) { ) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag); invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
const component = ( render(
<ReactNativeApp initialProps={initialProps} rootComponent={RootComponent} rootTag={rootTag} /> <AppContainer rootTag={rootTag}>
<RootComponent {...initialProps} />
</AppContainer>,
rootTag
); );
render(component, rootTag);
} }
export function getApplication(RootComponent: Component, initialProps: Object): Object { export function getApplication(RootComponent: ReactClass<Object>, initialProps: Object): Object {
const element = <ReactNativeApp initialProps={initialProps} rootComponent={RootComponent} />; const element = (
<AppContainer rootTag={{}}>
<RootComponent {...initialProps} />
</AppContainer>
);
const stylesheet = StyleSheet.renderToString(); const stylesheet = StyleSheet.renderToString();
return { element, stylesheet }; return { element, stylesheet };
} }
+23 -20
View File
@@ -16,30 +16,33 @@ class Clipboard {
static setString(text) { static setString(text) {
let success = false; let success = false;
const body = document.body;
// add the text to a hidden node if (body) {
const node = document.createElement('span'); // add the text to a hidden node
node.textContent = text; const node = document.createElement('span');
node.style.position = 'absolute'; node.textContent = text;
node.style.opacity = '0'; node.style.position = 'absolute';
document.body.appendChild(node); node.style.opacity = '0';
body.appendChild(node);
// select the text // select the text
const selection = window.getSelection(); const selection = window.getSelection();
selection.removeAllRanges(); selection.removeAllRanges();
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(node); range.selectNodeContents(node);
selection.addRange(range); selection.addRange(range);
// attempt to copy // attempt to copy
try { try {
document.execCommand('copy'); document.execCommand('copy');
success = true; success = true;
} catch (e) {} } catch (e) {}
// remove selection and node // remove selection and node
selection.removeAllRanges(); selection.removeAllRanges();
document.body.removeChild(node); body.removeChild(node);
}
return success; return success;
} }
+3 -1
View File
@@ -24,7 +24,9 @@ const isRTL = () => {
const onChange = () => { const onChange = () => {
if (ExecutionEnvironment.canUseDOM) { if (ExecutionEnvironment.canUseDOM) {
document.documentElement.setAttribute('dir', isRTL() ? 'rtl' : 'ltr'); if (document.documentElement && document.documentElement.setAttribute) {
document.documentElement.setAttribute('dir', isRTL() ? 'rtl' : 'ltr');
}
} }
}; };
+18 -12
View File
@@ -33,19 +33,25 @@ const Linking = {
*/ */
const iframeOpen = url => { const iframeOpen = url => {
const noOpener = url.indexOf('mailto:') !== 0; const noOpener = url.indexOf('mailto:') !== 0;
const iframe = document.createElement('iframe'); const body = document.body;
iframe.style.display = 'none'; if (body) {
document.body.appendChild(iframe); const iframe = document.createElement('iframe');
iframe.style.display = 'none';
body.appendChild(iframe);
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
const script = iframeDoc.createElement('script'); const iframeBody = iframeDoc.body;
const openerExpression = noOpener ? 'child.opener = null' : ''; if (iframeBody) {
script.text = ` const script = iframeDoc.createElement('script');
window.parent = null; window.top = null; window.frameElement = null; const openerExpression = noOpener ? 'child.opener = null' : '';
var child = window.open("${url}"); ${openerExpression}; script.text = `
`; window.parent = null; window.top = null; window.frameElement = null;
iframeDoc.body.appendChild(script); var child = window.open("${url}"); ${openerExpression};
document.body.removeChild(iframe); `;
iframeBody.appendChild(script);
}
body.removeChild(iframe);
}
}; };
module.exports = Linking; module.exports = Linking;
+1 -1
View File
@@ -23,7 +23,7 @@ function getStyle(style) {
} }
function flattenStyle(style: ?StyleObj): ?Object { function flattenStyle(style: ?StyleObj): ?Object {
if (!style) { if (style == null || typeof style === 'boolean') {
return undefined; return undefined;
} }
+9 -5
View File
@@ -11,6 +11,8 @@ import React, { Component } from 'react';
import { bool, number } from 'prop-types'; import { bool, number } from 'prop-types';
class ProgressBar extends Component { class ProgressBar extends Component {
_progressElement = null;
static displayName = 'ProgressBar'; static displayName = 'ProgressBar';
static propTypes = { static propTypes = {
@@ -58,17 +60,19 @@ class ProgressBar extends Component {
); );
} }
_setProgressRef = component => { _setProgressRef = element => {
this._progressRef = component; this._progressElement = element;
}; };
_updateProgressWidth = () => { _updateProgressWidth = () => {
const { indeterminate, progress } = this.props; const { indeterminate, progress } = this.props;
const percentageProgress = indeterminate ? 50 : progress * 100; const percentageProgress = indeterminate ? 50 : progress * 100;
const width = indeterminate ? '25%' : `${percentageProgress}%`; const width = indeterminate ? '25%' : `${percentageProgress}%`;
this._progressRef.setNativeProps({ if (this._progressElement) {
style: { width } this._progressElement.setNativeProps({
}); style: { width }
});
}
}; };
} }
+11 -8
View File
@@ -18,7 +18,8 @@ const thumbDefaultBoxShadow = '0px 1px 3px rgba(0,0,0,0.5)';
const thumbFocusedBoxShadow = `${thumbDefaultBoxShadow}, 0 0 0 10px rgba(0,0,0,0.1)`; const thumbFocusedBoxShadow = `${thumbDefaultBoxShadow}, 0 0 0 10px rgba(0,0,0,0.1)`;
class Switch extends PureComponent { class Switch extends PureComponent {
_checkbox: HTMLInputElement; _checkboxElement: HTMLInputElement;
_thumbElement = null;
static displayName = 'Switch'; static displayName = 'Switch';
@@ -44,11 +45,11 @@ class Switch extends PureComponent {
}; };
blur() { blur() {
UIManager.blur(this._checkbox); UIManager.blur(this._checkboxElement);
} }
focus() { focus() {
UIManager.focus(this._checkbox); UIManager.focus(this._checkboxElement);
} }
render() { render() {
@@ -136,15 +137,17 @@ class Switch extends PureComponent {
_handleFocusState = (event: Object) => { _handleFocusState = (event: Object) => {
const isFocused = event.nativeEvent.type === 'focus'; const isFocused = event.nativeEvent.type === 'focus';
const boxShadow = isFocused ? thumbFocusedBoxShadow : thumbDefaultBoxShadow; const boxShadow = isFocused ? thumbFocusedBoxShadow : thumbDefaultBoxShadow;
this._thumb.setNativeProps({ style: { boxShadow } }); if (this._thumbElement) {
this._thumbElement.setNativeProps({ style: { boxShadow } });
}
}; };
_setCheckboxRef = component => { _setCheckboxRef = element => {
this._checkbox = component; this._checkboxElement = element;
}; };
_setThumbRef = component => { _setThumbRef = element => {
this._thumb = component; this._thumbElement = element;
}; };
} }
@@ -53,6 +53,7 @@ var PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
* ``` * ```
*/ */
var TouchableOpacity = createReactClass({ var TouchableOpacity = createReactClass({
displayName: 'TouchableOpacity',
mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin], mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin],
propTypes: { propTypes: {
@@ -82,6 +82,7 @@ const TouchableWithoutFeedback = createReactClass({
* reactivated! Move it back and forth several times while the scroll view * reactivated! Move it back and forth several times while the scroll view
* is disabled. Ensure you pass in a constant to reduce memory allocations. * is disabled. Ensure you pass in a constant to reduce memory allocations.
*/ */
// $FlowFixMe
pressRetentionOffset: EdgeInsetsPropType, pressRetentionOffset: EdgeInsetsPropType,
/** /**
* This defines how far your touch can start away from the button. This is * This defines how far your touch can start away from the button. This is
@@ -91,6 +92,7 @@ const TouchableWithoutFeedback = createReactClass({
* of sibling views always takes precedence if a touch hits two overlapping * of sibling views always takes precedence if a touch hits two overlapping
* views. * views.
*/ */
// $FlowFixMe
hitSlop: EdgeInsetsPropType hitSlop: EdgeInsetsPropType
}, },
+16 -18
View File
@@ -1,5 +1,3 @@
/* eslint-disable */
/** /**
* Copyright (c) 2015-present, Facebook, Inc. * Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved. * All rights reserved.
@@ -8,29 +6,28 @@
* LICENSE file in the root directory of this source tree. An additional grant * 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. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @providesModule createStrictShapeTypeChecker
* @flow * @flow
*/ */
import invariant from 'fbjs/lib/invariant'; import invariant from 'fbjs/lib/invariant';
import ReactPropTypeLocationNames from '../vendor/ReactPropTypeLocationNames';
import ReactPropTypesSecret from '../vendor/ReactPropTypesSecret';
function createStrictShapeTypeChecker(shapeTypes: { function createStrictShapeTypeChecker(shapeTypes: {
[key: string]: ReactPropsCheckType [key: string]: ReactPropsCheckType
}): ReactPropsChainableTypeChecker { }): ReactPropsChainableTypeChecker {
function checkType(isRequired, props, propName, componentName, location?) { function checkType(isRequired, props, propName, componentName, location?, ...rest) {
if (!props[propName]) { if (!props[propName]) {
if (isRequired) { if (isRequired) {
invariant( invariant(
false, false,
`Required object \`${propName}\` was not specified in ` + `\`${componentName}\`.` `Required object \`${propName}\` was not specified in \`${componentName}\`.`
); );
} }
return; return;
} }
var propValue = props[propName]; const propValue = props[propName];
var propType = typeof propValue; const propType = typeof propValue;
var locationName = (location && ReactPropTypeLocationNames[location]) || '(unknown)'; const locationName = location || '(unknown)';
if (propType !== 'object') { if (propType !== 'object') {
invariant( invariant(
false, false,
@@ -40,24 +37,24 @@ function createStrictShapeTypeChecker(shapeTypes: {
} }
// We need to check all keys in case some are required but missing from // We need to check all keys in case some are required but missing from
// props. // props.
var allKeys = { ...props[propName], ...shapeTypes }; const allKeys = { ...props[propName], ...shapeTypes };
for (var key in allKeys) { for (const key in allKeys) {
var checker = shapeTypes[key]; const checker = shapeTypes[key];
if (!checker) { if (!checker) {
invariant( invariant(
false, false,
`Invalid props.${propName} key \`${key}\` supplied to \`${componentName}\`.` + `Invalid props.${propName} key \`${key}\` supplied to \`${componentName}\`.` +
`\nBad object: ` + '\nBad object: ' +
JSON.stringify(props[propName], null, ' ') + JSON.stringify(props[propName], null, ' ') +
`\nValid keys: ` + '\nValid keys: ' +
JSON.stringify(Object.keys(shapeTypes), null, ' ') JSON.stringify(Object.keys(shapeTypes), null, ' ')
); );
} }
var error = checker(propValue, key, componentName, location, null, ReactPropTypesSecret); const error = checker(propValue, key, componentName, location, ...rest);
if (error) { if (error) {
invariant( invariant(
false, false,
error.message + `\nBad object: ` + JSON.stringify(props[propName], null, ' ') error.message + '\nBad object: ' + JSON.stringify(props[propName], null, ' ')
); );
} }
} }
@@ -66,9 +63,10 @@ function createStrictShapeTypeChecker(shapeTypes: {
props: { [key: string]: any }, props: { [key: string]: any },
propName: string, propName: string,
componentName: string, componentName: string,
location?: string location?: string,
...rest
): ?Error { ): ?Error {
return checkType(false, props, propName, componentName, location); return checkType(false, props, propName, componentName, location, ...rest);
} }
chainedCheckType.isRequired = checkType.bind(null, true); chainedCheckType.isRequired = checkType.bind(null, true);
return chainedCheckType; return chainedCheckType;
+3 -3
View File
@@ -2756,9 +2756,9 @@ flatten@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
flow-bin@^0.46.0: flow-bin@^0.47.0:
version "0.46.0" version "0.47.0"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.46.0.tgz#06ad7fe19dddb1042264438064a2a32fee12b872" resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.47.0.tgz#a2a08ab3e0d1f1cb57d17e27b30b118b62fda367"
flow-parser@0.45.0: flow-parser@0.45.0:
version "0.45.0" version "0.45.0"