diff --git a/Libraries/YellowBox/YellowBox.js b/Libraries/YellowBox/YellowBox.js deleted file mode 100644 index a43a849ed4a..00000000000 --- a/Libraries/YellowBox/YellowBox.js +++ /dev/null @@ -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 { - static ignoreWarnings(patterns: $ReadOnlyArray): 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 ; - } - - // TODO: Ignore warnings that fire when rendering `YellowBox` itself. - return ; - } - }; - - 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 { - static ignoreWarnings(patterns: $ReadOnlyArray): 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> & { - ignoreWarnings($ReadOnlyArray): void, - install(): void, - uninstall(): void, - __unstable_enableLogBox(): void, - __unstable_isLogBoxEnabled(): boolean, - ... -}); diff --git a/Libraries/YellowBox/YellowBoxDeprecated.js b/Libraries/YellowBox/YellowBoxDeprecated.js new file mode 100644 index 00000000000..46ba16eeeda --- /dev/null +++ b/Libraries/YellowBox/YellowBoxDeprecated.js @@ -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 { + static ignoreWarnings(patterns: $ReadOnlyArray): 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 { + static ignoreWarnings(patterns: $ReadOnlyArray): void { + // Do nothing. + } + + static install(): void { + // Do nothing. + } + + static uninstall(): void { + // Do nothing. + } + + render(): React.Node { + return null; + } + }; +} + +module.exports = (YellowBox: Class> & { + ignoreWarnings($ReadOnlyArray): void, + install(): void, + uninstall(): void, + ... +}); diff --git a/Libraries/YellowBox/__tests__/YellowBox-test.js b/Libraries/YellowBox/__tests__/YellowBox-test.js deleted file mode 100644 index e9a89f3fcdb..00000000000 --- a/Libraries/YellowBox/__tests__/YellowBox-test.js +++ /dev/null @@ -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(); - - expect(output).toMatchSnapshot(); - }); - - it('should render LogBoxNotificationContainer when LogBox is enabled', () => { - const YellowBox = require('../YellowBox'); - - YellowBox.__unstable_enableLogBox(); - - const output = render.shallowRender(); - - expect(output).toMatchSnapshot(); - }); -}); diff --git a/Libraries/YellowBox/__tests__/YellowBoxDeprecated-test.js b/Libraries/YellowBox/__tests__/YellowBoxDeprecated-test.js new file mode 100644 index 00000000000..4adadb6e87f --- /dev/null +++ b/Libraries/YellowBox/__tests__/YellowBoxDeprecated-test.js @@ -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.', + ); + }); +}); diff --git a/Libraries/YellowBox/__tests__/__snapshots__/YellowBox-test.js.snap b/Libraries/YellowBox/__tests__/__snapshots__/YellowBox-test.js.snap deleted file mode 100644 index 974f032a963..00000000000 --- a/Libraries/YellowBox/__tests__/__snapshots__/YellowBox-test.js.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`YellowBox should render LogBoxNotificationContainer when LogBox is enabled 1`] = ``; - -exports[`YellowBox should render YellowBoxContainer by default 1`] = ``; diff --git a/index.js b/index.js index d0660e6de5d..a8cdf3a5dc0 100644 --- a/index.js +++ b/index.js @@ -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');