Deprecate YellowBox and unstable_enableLogBox

Summary:
This diff replaces YellowBox with YellowBoxDeprecated, adding warnings for using the module directly instead of YellowBox. Also adds a no-op message for unstable_enableLogBox.

Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D19949700

fbshipit-source-id: 269c341a2cedcdb2f7a80947d3239db078238201
This commit is contained in:
Rick Hanlon
2020-02-19 18:01:43 -08:00
committed by Facebook Github Bot
parent a83ea6ab8c
commit d66169b4fc
6 changed files with 134 additions and 513 deletions
-234
View File
@@ -1,234 +0,0 @@
/**
* 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 {Registry, IgnorePattern} from './Data/YellowBoxRegistry';
import YellowBoxWarning from './Data/YellowBoxWarning';
import * as LogBoxData from '../LogBox/Data/LogBoxData';
import NativeLogBox from '../NativeModules/specs/NativeLogBox';
type Props = $ReadOnly<{||}>;
type State = {|
registry: ?Registry,
|};
let YellowBox;
/**
* YellowBox displays warnings at the bottom of the screen.
*
* Warnings help guard against subtle yet significant issues that can impact the
* quality of the app. This "in your face" style of warning allows developers to
* notice and correct these issues as quickly as possible.
*
* YellowBox is only enabled in `__DEV__`. Set the following flag to disable it:
*
* console.disableYellowBox = true;
*
* Ignore specific warnings by calling:
*
* LogBox.ignoreLogs(['Warning: ...']);
*
* Strings supplied to `LogBox.ignoreLogs` only need to be a substring of
* the ignored warning messages.
*/
if (__DEV__) {
const Platform = require('../Utilities/Platform');
const RCTLog = require('../Utilities/RCTLog');
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);
};
(console: any).warn = function(...args) {
warnImpl(...args);
};
// eslint-disable-next-line no-shadow
YellowBox = class YellowBox extends React.Component<Props, State> {
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
LogBoxData.addIgnorePatterns(patterns);
YellowBoxRegistry.addIgnorePatterns(patterns);
}
static install(): void {
if (_isLogBoxEnabled) {
LogBox.install();
return;
}
_isInstalled = true;
errorImpl = function(...args) {
registerError(...args);
};
warnImpl = function(...args) {
registerWarning(...args);
};
if ((console: any).disableYellowBox === true) {
YellowBoxRegistry.setDisabled(true);
}
(Object.defineProperty: any)(console, 'disableYellowBox', {
configurable: true,
get: () => YellowBoxRegistry.isDisabled(),
set: value => YellowBoxRegistry.setDisabled(value),
});
if (Platform.isTesting) {
(console: any).disableYellowBox = true;
}
RCTLog.setWarningHandler((...args) => {
registerWarning(...args);
});
}
static uninstall(): void {
if (_isLogBoxEnabled) {
LogBox.uninstall();
return;
}
_isInstalled = false;
errorImpl = error;
warnImpl = warn;
delete (console: any).disableYellowBox;
}
static __unstable_enableLogBox(): void {
if (NativeLogBox == null) {
// The native module is required to enable LogBox.
return;
}
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;
// TODO: Temporary hack to prevent cycles with the ExceptionManager.
global.__unstable_isLogBoxEnabled = true;
}
static __unstable_isLogBoxEnabled(): boolean {
return !!_isLogBoxEnabled;
}
render(): React.Node {
if (_isLogBoxEnabled) {
return <LogBoxNotificationContainer />;
}
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
return <YellowBoxContainer />;
}
};
const registerWarning = (...args): void => {
if (typeof args[0] === 'string' && args[0].startsWith('(ADVICE)')) {
return;
}
const {category, message, stack} = YellowBoxWarning.parse({
args,
});
if (!YellowBoxRegistry.isWarningIgnored(message)) {
YellowBoxRegistry.add({category, message, stack});
warn.call(console, ...args);
}
};
const registerError = (...args): void => {
// Only show YellowBox for the `warning` module, otherwise pass through and skip.
if (typeof args[0] !== 'string' || !args[0].startsWith('Warning: ')) {
error.call(console, ...args);
return;
}
const format = args[0].replace('Warning: ', '');
const filterResult = LogBoxData.checkWarningFilter(format);
if (filterResult.suppressCompletely) {
return;
}
args[0] = filterResult.finalFormat;
const {category, message, stack} = YellowBoxWarning.parse({
args,
});
if (YellowBoxRegistry.isWarningIgnored(message)) {
return;
}
if (filterResult.forceDialogImmediately === true) {
// This will pop a redbox. Do not downgrade. These are real bugs with same severity as throws.
error.call(console, message.content);
} else {
// Unfortunately, we need to add the Warning: prefix back so we don't show a redbox later.
args[0] = `Warning: ${filterResult.finalFormat}`;
// Note: YellowBox has no concept of "soft errors" so we're showing YellowBox for those.
YellowBoxRegistry.add({category, message, stack});
error.call(console, ...args);
}
};
} else {
YellowBox = class extends React.Component<Props, State> {
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
// Do nothing.
}
static install(): void {
// Do nothing.
}
static uninstall(): void {
// Do nothing.
}
static __unstable_enableLogBox(): void {
// Do nothing.
}
static __unstable_isLogBoxEnabled(): boolean {
return false;
}
render(): React.Node {
return null;
}
};
}
module.exports = (YellowBox: Class<React.Component<Props, State>> & {
ignoreWarnings($ReadOnlyArray<IgnorePattern>): void,
install(): void,
uninstall(): void,
__unstable_enableLogBox(): void,
__unstable_isLogBoxEnabled(): boolean,
...
});
@@ -0,0 +1,75 @@
/**
* 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');
const LogBox = require('../LogBox/LogBox');
import type {IgnorePattern} from '../LogBox/Data/LogBoxData';
type Props = $ReadOnly<{||}>;
let YellowBox;
if (__DEV__) {
YellowBox = class extends React.Component<Props> {
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
console.warn(
'YellowBox has been replaced with LogBox. Please call LogBox.ignoreLogs() instead.',
);
LogBox.ignoreLogs(patterns);
}
static install(): void {
console.warn(
'YellowBox has been replaced with LogBox. Please call LogBox.install() instead.',
);
LogBox.install();
}
static uninstall(): void {
console.warn(
'YellowBox has been replaced with LogBox. Please call LogBox.uninstall() instead.',
);
LogBox.uninstall();
}
render(): React.Node {
return null;
}
};
} else {
YellowBox = class extends React.Component<Props> {
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
// Do nothing.
}
static install(): void {
// Do nothing.
}
static uninstall(): void {
// Do nothing.
}
render(): React.Node {
return null;
}
};
}
module.exports = (YellowBox: Class<React.Component<Props>> & {
ignoreWarnings($ReadOnlyArray<IgnorePattern>): void,
install(): void,
uninstall(): void,
...
});
@@ -1,270 +0,0 @@
/**
* 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.
*
* @emails oncall+react_native
* @format
* @flow
*/
'use strict';
import * as React from 'react';
const YellowBoxRegistry = require('../Data/YellowBoxRegistry');
const LogBoxData = require('../../LogBox/Data/LogBoxData');
const render = require('../../../jest/renderer');
jest.mock('../../NativeModules/specs/NativeLogBox', () => true);
jest.mock('../../LogBox/LogBoxNotificationContainer', () => ({
__esModule: true,
default: 'LogBoxNotificationContainer',
}));
type Overrides = {|
forceDialogImmediately?: boolean,
suppressDialog_LEGACY?: boolean,
suppressCompletely?: boolean,
|};
const setFilter = (options?: Overrides) => {
LogBoxData.setWarningFilter(format => ({
finalFormat: format,
forceDialogImmediately: false,
suppressDialog_LEGACY: false,
suppressCompletely: false,
monitorEvent: null,
monitorListVersion: 0,
monitorSampleRate: 0,
...options,
}));
};
const install = () => {
const YellowBox = require('../YellowBox');
YellowBox.install();
};
const uninstall = () => {
const YellowBox = require('../YellowBox');
YellowBox.uninstall();
};
describe('YellowBox', () => {
const {error, warn} = console;
const mockError = jest.fn();
const mockWarn = jest.fn();
beforeEach(() => {
jest.resetModules();
mockError.mockClear();
mockWarn.mockClear();
(console: any).error = mockError;
(console: any).warn = mockWarn;
});
afterEach(() => {
uninstall();
(console: any).error = error;
(console: any).warn = warn;
});
it('can set `disableYellowBox` after installing', () => {
expect((console: any).disableYellowBox).toBe(undefined);
install();
expect((console: any).disableYellowBox).toBe(false);
expect(YellowBoxRegistry.isDisabled()).toBe(false);
(console: any).disableYellowBox = true;
expect((console: any).disableYellowBox).toBe(true);
expect(YellowBoxRegistry.isDisabled()).toBe(true);
});
it('can set `disableYellowBox` before installing', () => {
expect((console: any).disableYellowBox).toBe(undefined);
(console: any).disableYellowBox = true;
install();
expect((console: any).disableYellowBox).toBe(true);
expect(YellowBoxRegistry.isDisabled()).toBe(true);
});
it('registers warnings', () => {
jest.mock('../Data/YellowBoxRegistry');
install();
expect(YellowBoxRegistry.add).not.toBeCalled();
(console: any).warn('...');
expect(YellowBoxRegistry.add).toBeCalled();
expect(mockWarn).toBeCalledTimes(1);
expect(mockWarn).toBeCalledWith('...');
});
it('registers errors', () => {
jest.mock('../Data/YellowBoxRegistry');
install();
(console: any).error('...');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(mockError).toBeCalledTimes(1);
expect(mockError).toBeCalledWith('...');
});
it('skips ADVICE warnings', () => {
jest.mock('../Data/YellowBoxRegistry');
install();
(console: any).warn('(ADVICE) Ignore me');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(mockWarn).not.toBeCalled();
});
it('skips ignored warnings', () => {
jest.mock('../Data/YellowBoxRegistry');
install();
(YellowBoxRegistry: any).isWarningIgnored.mockReturnValue(true);
(console: any).warn('Ignore me');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(mockWarn).not.toBeCalled();
});
it('registers Warning module errors with default options to YellowBox', () => {
jest.mock('../Data/YellowBoxRegistry');
setFilter();
install();
(console: any).error('Warning: ...');
expect(YellowBoxRegistry.add).toBeCalled();
expect(mockError).toBeCalled();
expect(mockError).toBeCalledTimes(1);
expect(mockWarn).not.toBeCalled();
});
it('skips Warning module errors with forceDialogImmediately', () => {
jest.mock('../Data/YellowBoxRegistry');
setFilter({
suppressCompletely: true,
});
install();
(console: any).error('Warning: ...');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(mockError).not.toBeCalled();
expect(mockWarn).not.toBeCalled();
});
it('registers Warning errors with forceDialogImmediately as console.error (with interpolation)', () => {
jest.mock('../Data/YellowBoxRegistry');
setFilter({
forceDialogImmediately: true,
});
install();
(console: any).error('Warning: %s', 'Something');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(mockWarn).not.toBeCalled();
expect(mockError).toBeCalledTimes(1);
// We expect this to be the interpolated value because we don't do interpolation downstream.
// We also strip the "Warning" prefix, otherwise the redbox would be skipped downstream.
expect(mockError).toBeCalledWith('Something');
});
it('registers Warning errors with suppressDialog_LEGACY to YellowBox', () => {
jest.mock('../Data/YellowBoxRegistry');
setFilter({
suppressDialog_LEGACY: true,
});
install();
(console: any).error('Warning: Something');
expect(YellowBoxRegistry.add).toBeCalledTimes(1);
expect(mockWarn).not.toBeCalled();
expect(mockError).toBeCalledTimes(1);
// We cannot strip the "Warning" prefix or it would pop a redbox.
expect(mockError).toBeCalledWith('Warning: Something');
});
it('skips Warning errors sent to YellowBox but ignored by patterns', () => {
jest.mock('../Data/YellowBoxRegistry');
setFilter({
suppressDialog_LEGACY: true,
});
install();
(YellowBoxRegistry: any).isWarningIgnored.mockReturnValue(true);
(console: any).error('Warning: ...');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(mockWarn).not.toBeCalled();
expect(mockError).not.toBeCalled();
});
it('if LogBox is enabled, installs and uninstalls LogBox', () => {
jest.mock('../../LogBox/Data/LogBoxData');
jest.mock('../Data/YellowBoxRegistry');
const YellowBox = require('../YellowBox');
YellowBox.__unstable_enableLogBox();
install();
(console: any).warn('Some warning');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(LogBoxData.addLog).toBeCalled();
expect(require('../YellowBox').__unstable_isLogBoxEnabled()).toBe(true);
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');
const YellowBox = require('../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 YellowBox = require('../YellowBox');
const output = render.shallowRender(<YellowBox />);
expect(output).toMatchSnapshot();
});
it('should render LogBoxNotificationContainer when LogBox is enabled', () => {
const YellowBox = require('../YellowBox');
YellowBox.__unstable_enableLogBox();
const output = render.shallowRender(<YellowBox />);
expect(output).toMatchSnapshot();
});
});
@@ -0,0 +1,53 @@
/**
* 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.
*
* @emails oncall+react_native
* @format
* @flow
*/
'use strict';
const LogBox = require('../../LogBox/LogBox');
const YellowBox = require('../YellowBoxDeprecated');
describe('YellowBox', () => {
beforeEach(() => {
jest.restoreAllMocks();
});
it('calling ignoreWarnings proxies to LogBox.ignoreLogs', () => {
jest.spyOn(LogBox, 'ignoreLogs');
jest.spyOn(console, 'warn').mockImplementation(() => {});
YellowBox.ignoreWarnings(['foo']);
expect(LogBox.ignoreLogs).toBeCalledWith(['foo']);
expect(console.warn).toBeCalledWith(
'YellowBox has been replaced with LogBox. Please call LogBox.ignoreLogs() instead.',
);
});
it('calling install proxies to LogBox.install', () => {
jest.spyOn(LogBox, 'install');
jest.spyOn(console, 'warn').mockImplementation(() => {});
YellowBox.install();
expect(LogBox.install).toBeCalled();
expect(console.warn).toBeCalledWith(
'YellowBox has been replaced with LogBox. Please call LogBox.install() instead.',
);
});
it('calling uninstall proxies to LogBox.uninstall', () => {
jest.spyOn(LogBox, 'uninstall');
jest.spyOn(console, 'warn').mockImplementation(() => {});
YellowBox.uninstall();
expect(LogBox.uninstall).toBeCalled();
expect(console.warn).toBeCalledWith(
'YellowBox has been replaced with LogBox. Please call LogBox.uninstall() instead.',
);
});
});
@@ -1,5 +0,0 @@
// 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 />`;
+6 -4
View File
@@ -86,7 +86,7 @@ import typeof useColorScheme from './Libraries/Utilities/useColorScheme';
import typeof useWindowDimensions from './Libraries/Utilities/useWindowDimensions';
import typeof UTFSequence from './Libraries/UTFSequence';
import typeof Vibration from './Libraries/Vibration/Vibration';
import typeof YellowBox from './Libraries/YellowBox/YellowBox';
import typeof YellowBox from './Libraries/YellowBox/YellowBoxDeprecated';
import typeof LogBox from './Libraries/LogBox/LogBox';
import typeof RCTDeviceEventEmitter from './Libraries/EventEmitter/RCTDeviceEventEmitter';
import typeof RCTNativeAppEventEmitter from './Libraries/EventEmitter/RCTNativeAppEventEmitter';
@@ -439,7 +439,7 @@ module.exports = {
return require('./Libraries/Vibration/Vibration');
},
get YellowBox(): YellowBox {
return require('./Libraries/YellowBox/YellowBox');
return require('./Libraries/YellowBox/YellowBoxDeprecated');
},
// Plugins
@@ -467,9 +467,11 @@ module.exports = {
return require('./Libraries/ReactNative/RootTagContext');
},
get unstable_enableLogBox(): () => void {
return require('./Libraries/YellowBox/YellowBox').__unstable_enableLogBox;
return () =>
console.warn(
'LogBox is enabled by default so there is no need to call unstable_enableLogBox() anymore. This is a no op and will be removed in the next version.',
);
},
// Prop Types
get ColorPropType(): DeprecatedColorPropType {
return require('./Libraries/DeprecatedPropTypes/DeprecatedColorPropType');