Add unstable_enableLogBox

Summary:
This diff adds a new `unstable_enableLogBox` function to opt-into the new LogBox experience. If LogBox is not enabled early enough, we show an error with instructions.

With this, LogBox can be enabled with:

```
require('react-native').unstable_enableLogBox();
```

Changelog: [General] [Adds] unstable_enableLogBox

Reviewed By: zackargyle, rubennorte

Differential Revision: D18808940

fbshipit-source-id: 4b0234ddc4d1646515bf63110d5b02133780512e
This commit is contained in:
Rick Hanlon
2019-12-10 02:28:06 -08:00
parent 93bbab3878
commit c194ed2dbb
8 changed files with 191 additions and 64 deletions
+1 -1
View File
@@ -83,7 +83,7 @@ function reportException(e: ExtendedError, isFatal: boolean) {
e.jsEngine == null ? message : `${message}, js engine: ${e.jsEngine}`;
const isHandledByLogBox =
e.forceRedbox !== true && global.__reactExperimentalLogBox;
e.forceRedbox !== true && global.__unstable_isLogBoxEnabled === true;
const data = preprocessException({
message,
+11 -19
View File
@@ -94,17 +94,14 @@ class AppContainer extends React.Component<Props, State> {
}
render(): React.Node {
let logBox = null;
if (__DEV__ && !this.props.internal_excludeLogBox) {
if (!global.__RCTProfileIsProfiling) {
if (global.__reactExperimentalLogBox) {
const LogBoxNotificationContainer = require('../LogBox/LogBoxNotificationContainer')
.default;
logBox = <LogBoxNotificationContainer />;
} else {
const YellowBox = require('../YellowBox/YellowBox');
logBox = <YellowBox />;
}
let yellowBox = null;
if (__DEV__) {
if (
!global.__RCTProfileIsProfiling &&
!this.props.internal_excludeLogBox
) {
const YellowBox = require('../YellowBox/YellowBox');
yellowBox = <YellowBox />;
}
}
@@ -138,7 +135,7 @@ class AppContainer extends React.Component<Props, State> {
<View style={styles.appContainer} pointerEvents="box-none">
{!this.state.hasError && innerView}
{this.state.inspector}
{logBox}
{yellowBox}
</View>
</RootTagContext.Provider>
);
@@ -153,13 +150,8 @@ const styles = StyleSheet.create({
if (__DEV__) {
if (!global.__RCTProfileIsProfiling) {
if (global.__reactExperimentalLogBox) {
const LogBox = require('../LogBox/LogBox');
LogBox.install();
} else {
const YellowBox = require('../YellowBox/YellowBox');
YellowBox.install();
}
const YellowBox = require('../YellowBox/YellowBox');
YellowBox.install();
}
}
+9 -7
View File
@@ -181,13 +181,15 @@ const AppRegistry = {
* See http://facebook.github.io/react-native/docs/appregistry.html#runapplication
*/
runApplication(appKey: string, appParameters: any): void {
const msg =
'Running "' + appKey + '" with ' + JSON.stringify(appParameters);
infoLog(msg);
BugReporting.addSource(
'AppRegistry.runApplication' + runCount++,
() => msg,
);
if (appKey !== 'LogBox') {
const msg =
'Running "' + appKey + '" with ' + JSON.stringify(appParameters);
infoLog(msg);
BugReporting.addSource(
'AppRegistry.runApplication' + runCount++,
() => msg,
);
}
invariant(
runnables[appKey] && runnables[appKey].run,
`"${appKey}" has not been registered. This can happen if:\n` +
+44 -37
View File
@@ -12,12 +12,7 @@
const React = require('react');
import type {Category} from './Data/YellowBoxCategory';
import type {
Registry,
Subscription,
IgnorePattern,
} from './Data/YellowBoxRegistry';
import type {Registry, IgnorePattern} from './Data/YellowBoxRegistry';
import * as LogBoxData from '../LogBox/Data/LogBoxData';
type Props = $ReadOnly<{||}>;
type State = {|
@@ -47,14 +42,19 @@ let YellowBox;
if (__DEV__) {
const Platform = require('../Utilities/Platform');
const RCTLog = require('../Utilities/RCTLog');
const YellowBoxList = require('./UI/YellowBoxList');
const YellowBoxContainer = require('./YellowBoxContainer').default;
const LogBox = require('../LogBox/LogBox');
const YellowBoxRegistry = require('./Data/YellowBoxRegistry');
const LogBoxNotificationContainer = require('../LogBox/LogBoxNotificationContainer')
.default;
// YellowBox needs to insert itself early,
// in order to access the component stacks appended by React DevTools.
const {error, warn} = console;
let errorImpl = error;
let warnImpl = warn;
let _isLogBoxEnabled = false;
let _isInstalled = false;
(console: any).error = function(...args) {
errorImpl(...args);
};
@@ -70,6 +70,11 @@ if (__DEV__) {
}
static install(): void {
_isInstalled = true;
if (_isLogBoxEnabled) {
LogBox.install();
return;
}
errorImpl = function(...args) {
error.call(console, ...args);
// Show YellowBox for the `warning` module.
@@ -102,46 +107,39 @@ if (__DEV__) {
}
static uninstall(): void {
if (_isLogBoxEnabled) {
LogBox.uninstall();
return;
}
_isInstalled = false;
errorImpl = error;
warnImpl = warn;
delete (console: any).disableYellowBox;
}
_subscription: ?Subscription;
static __unstable_enableLogBox(): void {
if (_isInstalled) {
throw new Error(
'LogBox must be enabled before AppContainer is required so that it can properly wrap the console methods.\n\nPlease enable LogBox earlier in your app.\n\n',
);
}
_isLogBoxEnabled = true;
state = {
registry: null,
};
// TODO: Temporary hack to prevent cycles with the ExceptionManager.
global.__unstable_isLogBoxEnabled = true;
}
static __unstable_isLogBoxEnabled(): boolean {
return !!_isLogBoxEnabled;
}
render(): React.Node {
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
return this.state.registry == null ? null : (
<YellowBoxList
onDismiss={this._handleDismiss}
onDismissAll={this._handleDismissAll}
registry={this.state.registry}
/>
);
}
componentDidMount(): void {
this._subscription = YellowBoxRegistry.observe(registry => {
this.setState({registry});
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
if (_isLogBoxEnabled) {
return <LogBoxNotificationContainer />;
}
}
_handleDismiss = (category: Category): void => {
YellowBoxRegistry.delete(category);
};
_handleDismissAll(): void {
YellowBoxRegistry.clear();
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
return <YellowBoxContainer />;
}
};
@@ -162,6 +160,13 @@ if (__DEV__) {
// Do nothing.
}
static __unstable_enableLogBox(): void {
// Do nothing.
}
static __unstable_isLogBoxEnabled(): boolean {
return false;
}
render(): React.Node {
return null;
}
@@ -172,5 +177,7 @@ module.exports = (YellowBox: Class<React.Component<Props, State>> & {
ignoreWarnings($ReadOnlyArray<IgnorePattern>): void,
install(): void,
uninstall(): void,
__unstable_enableLogBox(): void,
__unstable_isLogBoxEnabled(): boolean,
...
});
+65
View File
@@ -0,0 +1,65 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
const React = require('react');
import type {Category} from './Data/YellowBoxCategory';
import type {Registry, Subscription} from './Data/YellowBoxRegistry';
type Props = $ReadOnly<{||}>;
type State = $ReadOnly<{|
registry: ?Registry,
|}>;
const YellowBoxList = require('./UI/YellowBoxList');
const YellowBoxRegistry = require('./Data/YellowBoxRegistry');
class YellowBoxContainer extends React.Component<Props, State> {
_subscription: ?Subscription;
state: State = {
registry: null,
};
render(): React.Node {
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
return this.state.registry == null ? null : (
<YellowBoxList
onDismiss={this._handleDismiss}
onDismissAll={this._handleDismissAll}
registry={this.state.registry}
/>
);
}
componentDidMount(): void {
this._subscription = YellowBoxRegistry.observe(registry => {
this.setState({registry});
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
}
}
_handleDismiss = (category: Category): void => {
YellowBoxRegistry.delete(category);
};
_handleDismissAll(): void {
YellowBoxRegistry.clear();
}
}
export default YellowBoxContainer;
@@ -11,8 +11,16 @@
'use strict';
import * as React from 'react';
const YellowBox = require('../YellowBox');
const YellowBoxRegistry = require('../Data/YellowBoxRegistry');
const LogBoxData = require('../../LogBox/Data/LogBoxData');
const render = require('../../../jest/renderer');
jest.mock('../../LogBox/LogBoxNotificationContainer', () => ({
__esModule: true,
default: 'LogBoxNotificationContainer',
}));
describe('YellowBox', () => {
const {error, warn} = console;
@@ -74,4 +82,49 @@ describe('YellowBox', () => {
(console: any).error('Warning: ...');
expect(YellowBoxRegistry.add).toBeCalled();
});
it('if LogBox is enabled, installs and uninstalls LogBox', () => {
jest.mock('../../LogBox/Data/LogBoxData');
jest.mock('../Data/YellowBoxRegistry');
YellowBox.__unstable_enableLogBox();
YellowBox.install();
(console: any).warn('Some warning');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(LogBoxData.addLog).toBeCalled();
expect(YellowBox.__unstable_isLogBoxEnabled()).toBe(true);
YellowBox.uninstall();
(LogBoxData.addLog: any).mockClear();
(console: any).warn('Some warning');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(LogBoxData.addLog).not.toBeCalled();
expect(YellowBox.__unstable_isLogBoxEnabled()).toBe(true);
});
it('throws if LogBox is enabled after YellowBox is installed', () => {
jest.mock('../Data/YellowBoxRegistry');
YellowBox.install();
expect(() => YellowBox.__unstable_enableLogBox()).toThrow(
'LogBox must be enabled before AppContainer is required so that it can properly wrap the console methods.\n\nPlease enable LogBox earlier in your app.\n\n',
);
});
it('should render YellowBoxContainer by default', () => {
const output = render.shallowRender(<YellowBox />);
expect(output).toMatchSnapshot();
});
it('should render LogBoxNotificationContainer when LogBox is enabled', () => {
YellowBox.__unstable_enableLogBox();
const output = render.shallowRender(<YellowBox />);
expect(output).toMatchSnapshot();
});
});
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`YellowBox should render LogBoxNotificationContainer when LogBox is enabled 1`] = `<LogBoxNotificationContainer />`;
exports[`YellowBox should render YellowBoxContainer by default 1`] = `<YellowBoxContainer />`;
+3
View File
@@ -438,6 +438,9 @@ module.exports = {
get unstable_RootTagContext(): RootTagContext {
return require('./Libraries/ReactNative/RootTagContext');
},
get unstable_enableLogBox(): () => void {
return require('./Libraries/YellowBox/YellowBox').__unstable_enableLogBox;
},
// Prop Types
get ColorPropType(): DeprecatedColorPropType {