Compare commits

...
18 Commits
Author SHA1 Message Date
Lorenzo Sciandra 2bf7f1046d [0.62.0-rc.1] Bump version numbers 2020-01-21 08:27:36 +00:00
Rick Hanlon 5b4816741b LogBox - Double sync rendering timeout
Summary:
On slower devices , 0.5s is not fast enough and we frequently render a black screen. Let's bump the sync rendering timeout to 1s.

Changelog: [Internal]

Reviewed By: sammy-SC

Differential Revision: D19282619

fbshipit-source-id: 0d9ae60f4869b0de7c4523c2cb33fbbf320c6438
2020-01-07 06:55:57 -05:00
sunnylqm 39e5a83062 Fix android modal not disappear when reload (#27542)
Summary:
Fixes https://github.com/facebook/react-native/issues/17986

See above issue

After apply this change:
![ezgif-4-45d9add85b74](https://user-images.githubusercontent.com/615282/70987576-2520ad00-20fb-11ea-9b90-c9a7839824a5.gif)

## Changelog

[Android] [Fixed] - Fix android modal not disappear when reload
Pull Request resolved: https://github.com/facebook/react-native/pull/27542

Test Plan: Open a modal and do a refresh to see whether it disappears

Differential Revision: D19178803

Pulled By: mdvacca

fbshipit-source-id: 61894927fc481650804b2196df06a80c16b64e6c
2019-12-20 09:47:45 -05:00
Rick Hanlon fe348ab53d LogBox fixes for OSS
Summary:
This diff fixes two issues with LogBox in OSS.

Changelog: [Internal]

Reviewed By: mmmulani

Differential Revision: D19165696

fbshipit-source-id: 4dd9c480ea3d2b205f8e65cb32203949b1e32e8a
2019-12-19 07:32:26 -05:00
Rick Hanlon 0fd83e4786 LogBox - lazily initialize on iOS, use sync APIs
Summary:
Update LogBox on iOS to lazily initialize, using a synchronous RCTSurface, behind RCTSharedApplication checks.

This results in faster of LogBox, without keeping around a long lived window in the background, and only used when LogBox is used.

On Android, we still start the react app in the background but we create a dialog when it's shown and then destroy it when it's hidden. Once we have the sync APIs on android we can update it to use the same strategy.

Changelog: [Internal]

Reviewed By: fkgozali

Differential Revision: D18925538

fbshipit-source-id: 1a72c39aa0fc26c8ba657d36c7fa7bc0ae777eb9
2019-12-19 07:30:59 -05:00
Kevin Gozali e97baa6066 LogBox iOS: don't initialize logbox window if redbox flag is disabled
Summary:
If redbox flag is disabled, don't initialize LogBox window even if the native module exists. This makes the module noop.

Changelog: [Internal]

Reviewed By: shergin

Differential Revision: D18927309

fbshipit-source-id: 3dc6c08ed537925594cabf77d1142568d47132e9
2019-12-19 07:30:59 -05:00
Rick Hanlon 76557f00d7 Add WarningFilter handling for YellowBox
Summary:
This diff backports warning filter handling to yellow box. I also backported the skipped warning handling so that ignored patterns do not log to the console, same as LogBox.

Changelog: [Internal]

Reviewed By: gaearon

Differential Revision: D18573288

fbshipit-source-id: 5bf8e86f754adc808313d7ed02f98daaf65de98c
2019-12-19 07:30:59 -05:00
Rick Hanlon 4693170d63 Hotfix - register null logbox outside of dev
Summary:
Outside of __DEV__ the app may still try to use LogBox. We're going to fix that but until we do, if it tries to use LogBox outside of dev render null.

Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D18932666

fbshipit-source-id: ef0f2542a295dc9332cae8b77faed78f8be350fe
2019-12-19 07:30:59 -05:00
Rick Hanlon 51dcb0195b Disable LogBox if native module is not available
Summary: Changelog: [Internal]

Reviewed By: mmmulani

Differential Revision: D18920345

fbshipit-source-id: e5213dc9c3a1aa775138e71a8040a3472e9bbd67
2019-12-19 07:30:59 -05:00
Lorenzo Sciandra 203f3e3399 [0.62.0-rc.0] Bump version numbers 2019-12-18 15:36:07 +00:00
Spencer Ahrens e7c289b56e emit Dimensions change enent when app goes split screen
Summary:
Fixes https://github.com/facebook/react-native/issues/26830 by removing version gating around `RCTUserInterfaceStyleDidChangeNotification` sent by `RCTRootView` and observing that notif for `Dimensions` changes.

Also centralizes `RCTUserInterfaceStyleDidChangeNotification` constant definition in new `RCTConstants` file.

Changelog:
[iOS] [Fixed] - `Dimensions` module now updates on initial split screen

Reviewed By: sammy-SC

Differential Revision: D18931098

fbshipit-source-id: e9784be3f544f3b10360fbc2d6ad0324273b1a8f
2019-12-11 14:05:45 -08:00
Rick Hanlon 3cfbae6540 Fix logs stuck in pending symbolocated state
Summary:
This diff fixes an issue where symbolicated logs were getting stuck in a "pending" state if the timeout occurred before symbolication finished.

Changelog: [Internal]

Reviewed By: sahrens

Differential Revision: D18894154

fbshipit-source-id: ed225962468f67aef40e430aa798f8d426d31027
2019-12-10 19:53:38 +00:00
Rick Hanlon 3fd98d95ef RN DevX] LogBox - Handle tapping component stacks if possible
Summary:
This diff adds handling for tapping to open components in component stacks, whenever they're available.

Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D18882338

fbshipit-source-id: 574bff1c41c952c33e647cb142d278ace60b4631
2019-12-10 19:53:29 +00:00
Rick Hanlon c194ed2dbb 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
2019-12-10 19:53:18 +00:00
Rick Hanlon 93bbab3878 LogBox - Update to use it's own root for the inspector
Summary:
Apologies for the large diff, it was difficult to break down.

This diff is a major refactor of LogBox that:
- Separates LogBoxNotification and LogBoxInspector
- Moves them each to their own container
- Updates AppContainer to only render the notification
- Updates the native logbox root to render the inspector
- Adds withSubscription HOC to manage subscribing to LogBoxData
- Simplifies LogBox to export an object instead of a component

Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D18750011

fbshipit-source-id: 639885d29e55e125892d1c2b6bbf2826f27d78db
2019-12-10 19:53:09 +00:00
Rick Hanlon 1a305fd79b Add NativeLogBox module on Android
Summary:
This diff adds a NativeLogBox module implementation on Android to manage rendering LogBox the way we render RedBox, except rendering a React Native component instead of a native view.

The strategy here is:
- initialize: will create a React rootview and render it.
- show: will add the rootview to a dialog and display the dialog.
- hide: will remove the rootview from it's parent, dismiss the dialog, and release the reference to the activity to prevent leaks.

Most of this is copied from the way RedBox works, the difference here is that we eagerly initialize the rootview with the `initialize` function so that it's warm by the time the dialog needs to render.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D18768517

fbshipit-source-id: 2510d6c186ccf73153ef9372c736c9e0c71bbc7d
2019-12-10 19:52:58 +00:00
Rick Hanlon 45a43241cd Add NativeLogBox module on iOS
Summary:
This diff adds a NativeLogBox module implementation on iOS to manage rendering LogBox the way we render RedBox, except rendering a React Native component instead of a native view.

The strategy here is:
- initialize: will create a hidden window (the way redbox does) and render the LogBox to it
- show: will show the window
- hide: will hide the window

Most of this is copied from the way RedBox works, the difference here is that we eagerly initialize the window with the `initialize` function so that it's warm by the time LogBox needs to render.

Changelog: [Internal]

Reviewed By: RSNara

Differential Revision: D18750008

fbshipit-source-id: 013e55ded55c8572bb08e0219ff4cd0060ebe0da
2019-12-10 19:52:44 +00:00
Rick Hanlon 041c392ec9 Register LogBox by default in dev
Summary:
This diff stubs out registering LogBox as a renderable component. In later diffs, we'll render this component on the native side.

Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D18750005

fbshipit-source-id: 4db082ca2104731641d2d10de1ba83a318ab44fb
2019-12-10 19:52:23 +00:00
69 changed files with 2563 additions and 851 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,
+4 -4
View File
@@ -1,17 +1,17 @@
/**
* @generated by scripts/bump-oss-version.js
*
* 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.
*
* @format
* @generated by scripts/bump-oss-version.js
* @flow
*/
exports.version = {
major: 0,
minor: 0,
minor: 62,
patch: 0,
prerelease: null,
prerelease: 'rc.1',
};
@@ -1671,6 +1671,33 @@ namespace facebook {
}
} // namespace react
} // namespace facebook
namespace facebook {
namespace react {
static facebook::jsi::Value __hostFunction_NativeLogBoxSpecJSI_show(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "show", @selector(show), args, count);
}
static facebook::jsi::Value __hostFunction_NativeLogBoxSpecJSI_hide(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "hide", @selector(hide), args, count);
}
NativeLogBoxSpecJSI::NativeLogBoxSpecJSI(id<RCTTurboModule> instance, std::shared_ptr<CallInvoker> jsInvoker)
: ObjCTurboModule("LogBox", instance, jsInvoker) {
methodMap_["show"] = MethodMetadata {0, __hostFunction_NativeLogBoxSpecJSI_show};
methodMap_["hide"] = MethodMetadata {0, __hostFunction_NativeLogBoxSpecJSI_hide};
}
} // namespace react
@@ -1771,6 +1771,25 @@ namespace facebook {
};
} // namespace react
} // namespace facebook
@protocol NativeLogBoxSpec <RCTBridgeModule, RCTTurboModule>
- (void)show;
- (void)hide;
@end
namespace facebook {
namespace react {
/**
* ObjC++ class for module 'LogBox'
*/
class JSI_EXPORT NativeLogBoxSpecJSI : public ObjCTurboModule {
public:
NativeLogBoxSpecJSI(id<RCTTurboModule> instance, std::shared_ptr<CallInvoker> jsInvoker);
};
} // namespace react
} // namespace facebook
@protocol NativeModalManagerSpec <RCTBridgeModule, RCTTurboModule>
- (void)addListener:(NSString *)eventName;
+133 -25
View File
@@ -10,6 +10,7 @@
('use strict');
import * as React from 'react';
import LogBoxLog from './LogBoxLog';
import {parseLogBoxException} from './parseLogBoxLog';
import type {LogLevel} from './LogBoxLog';
@@ -21,7 +22,7 @@ import type {
} from './parseLogBoxLog';
import parseErrorStack from '../../Core/Devtools/parseErrorStack';
import type {ExtendedError} from '../../Core/Devtools/parseErrorStack';
import NativeLogBox from '../../NativeModules/specs/NativeLogBox';
export type LogBoxLogs = Set<LogBoxLog>;
export type LogData = $ReadOnly<{|
level: LogLevel,
@@ -73,7 +74,7 @@ let warningFilter: WarningFilter = function(format) {
return {
finalFormat: format,
forceDialogImmediately: false,
suppressDialog_LEGACY: false,
suppressDialog_LEGACY: true,
suppressCompletely: false,
monitorEvent: 'unknown',
monitorListVersion: 0,
@@ -85,20 +86,6 @@ const LOGBOX_ERROR_MESSAGE =
'An error was thrown when attempting to render log messages via LogBox.';
function getNextState() {
const logArray = Array.from(logs);
let index = logArray.length - 1;
while (index >= 0) {
// The latest syntax error is selected and displayed before all other logs.
if (logArray[index].level === 'syntax') {
return {
logs,
isDisabled: _isDisabled,
selectedLogIndex: index,
};
}
index -= 1;
}
return {
logs,
isDisabled: _isDisabled,
@@ -174,9 +161,10 @@ function appendNewLog(newLog) {
let addPendingLog = () => {
logs.add(newLog);
if (_selectedIndex <= 0) {
_selectedIndex = logs.size - 1;
setSelectedLog(logs.size - 1);
} else {
handleUpdate();
}
handleUpdate();
addPendingLog = null;
};
@@ -190,8 +178,14 @@ function appendNewLog(newLog) {
if (addPendingLog && status !== 'PENDING') {
addPendingLog();
clearTimeout(optimisticTimeout);
} else if (status !== 'PENDING') {
// The log has already been added but we need to trigger a render.
handleUpdate();
}
});
} else if (newLog.level === 'syntax') {
logs.add(newLog);
setSelectedLog(logs.size - 1);
} else {
logs.add(newLog);
handleUpdate();
@@ -255,21 +249,42 @@ export function symbolicateLogLazy(log: LogBoxLog) {
export function clear(): void {
if (logs.size > 0) {
logs = new Set();
_selectedIndex = -1;
handleUpdate();
setSelectedLog(-1);
}
}
export function setSelectedLog(index: number): void {
_selectedIndex = index;
export function setSelectedLog(proposedNewIndex: number): void {
const oldIndex = _selectedIndex;
let newIndex = proposedNewIndex;
const logArray = Array.from(logs);
let index = logArray.length - 1;
while (index >= 0) {
// The latest syntax error is selected and displayed before all other logs.
if (logArray[index].level === 'syntax') {
newIndex = index;
break;
}
index -= 1;
}
_selectedIndex = newIndex;
handleUpdate();
if (NativeLogBox) {
setTimeout(() => {
if (oldIndex < 0 && newIndex >= 0) {
NativeLogBox.show();
} else if (oldIndex >= 0 && newIndex < 0) {
NativeLogBox.hide();
}
}, 0);
}
}
export function clearWarnings(): void {
const newLogs = Array.from(logs).filter(log => log.level !== 'warn');
if (newLogs.length !== logs.size) {
logs = new Set(newLogs);
_selectedIndex = -1;
setSelectedLog(-1);
handleUpdate();
}
}
@@ -280,8 +295,7 @@ export function clearErrors(): void {
);
if (newLogs.length !== logs.size) {
logs = new Set(newLogs);
_selectedIndex = -1;
handleUpdate();
setSelectedLog(-1);
}
}
@@ -369,3 +383,97 @@ export function observe(observer: Observer): Subscription {
},
};
}
type Props = $ReadOnly<{||}>;
type State = $ReadOnly<{|
logs: LogBoxLogs,
isDisabled: boolean,
hasError: boolean,
selectedLogIndex: number,
|}>;
type SubscribedComponent = React.AbstractComponent<
$ReadOnly<{|
logs: $ReadOnlyArray<LogBoxLog>,
isDisabled: boolean,
selectedLogIndex: number,
|}>,
>;
export function withSubscription(
WrappedComponent: SubscribedComponent,
): React.AbstractComponent<{||}> {
class LogBoxStateSubscription extends React.Component<Props, State> {
static getDerivedStateFromError() {
return {hasError: true};
}
componentDidCatch(err: Error, errorInfo: {componentStack: string, ...}) {
reportLogBoxError(err, errorInfo.componentStack);
}
_subscription: ?Subscription;
state = {
logs: new Set(),
isDisabled: false,
hasError: false,
selectedLogIndex: -1,
};
render(): React.Node {
if (this.state.hasError) {
// This happens when the component failed to render, in which case we delegate to the native redbox.
// We can't show anyback fallback UI here, because the error may be with <View> or <Text>.
return null;
}
return (
<WrappedComponent
logs={Array.from(this.state.logs)}
isDisabled={this.state.isDisabled}
selectedLogIndex={this.state.selectedLogIndex}
/>
);
}
componentDidMount(): void {
this._subscription = observe(data => {
this.setState(data);
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
}
}
_handleDismiss = (): void => {
// Here we handle the cases when the log is dismissed and it
// was either the last log, or when the current index
// is now outside the bounds of the log array.
const {selectedLogIndex, logs: stateLogs} = this.state;
const logsArray = Array.from(stateLogs);
if (selectedLogIndex != null) {
if (logsArray.length - 1 <= 0) {
setSelectedLog(-1);
} else if (selectedLogIndex >= logsArray.length - 1) {
setSelectedLog(selectedLogIndex - 1);
}
dismiss(logsArray[selectedLogIndex]);
}
};
_handleMinimize = (): void => {
setSelectedLog(-1);
};
_handleSetSelectedLog = (index: number): void => {
setSelectedLog(index);
};
}
return LogBoxStateSubscription;
}
@@ -25,7 +25,13 @@ function getLogBoxLog() {
message: {content: '...', substitutions: []},
stack: createStack(['A', 'B', 'C']),
category: 'Message category...',
componentStack: [{component: 'LogBoxLog', location: 'LogBoxLog.js:1'}],
componentStack: [
{
content: 'LogBoxLog',
fileName: 'LogBoxLog.js',
location: {column: -1, row: 1},
},
],
codeFrame: {
fileName: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
location: {row: 199, column: 0},
@@ -69,7 +75,11 @@ describe('LogBoxLog', () => {
expect(log.stack).toEqual(createStack(['A', 'B', 'C']));
expect(log.category).toEqual('Message category...');
expect(log.componentStack).toEqual([
{component: 'LogBoxLog', location: 'LogBoxLog.js:1'},
{
content: 'LogBoxLog',
fileName: 'LogBoxLog.js',
location: {column: -1, row: 1},
},
]);
expect(log.codeFrame).toEqual({
fileName: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
@@ -117,8 +117,16 @@ describe('parseLogBoxLog', () => {
]),
).toEqual({
componentStack: [
{component: 'MyComponent', location: 'filename.js:1'},
{component: 'MyOtherComponent', location: 'filename2.js:1'},
{
content: 'MyComponent',
fileName: 'filename.js',
location: {column: -1, row: 1},
},
{
content: 'MyOtherComponent',
fileName: 'filename2.js',
location: {column: -1, row: 1},
},
],
category:
'Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?%s',
@@ -143,8 +151,16 @@ describe('parseLogBoxLog', () => {
]),
).toEqual({
componentStack: [
{component: 'MyComponent', location: 'filename.js:1'},
{component: 'MyOtherComponent', location: 'filename2.js:1'},
{
content: 'MyComponent',
fileName: 'filename.js',
location: {column: -1, row: 1},
},
{
content: 'MyOtherComponent',
fileName: 'filename2.js',
location: {column: -1, row: 1},
},
],
category: 'Some kind of message',
message: {
@@ -164,8 +180,16 @@ describe('parseLogBoxLog', () => {
]),
).toEqual({
componentStack: [
{component: 'MyComponent', location: 'filename.js:1'},
{component: 'MyOtherComponent', location: 'filename2.js:1'},
{
content: 'MyComponent',
fileName: 'filename.js',
location: {column: -1, row: 1},
},
{
content: 'MyOtherComponent',
fileName: 'filename2.js',
location: {column: -1, row: 1},
},
],
category:
'Some kind of message Some other kind of message Some third kind of message',
@@ -418,12 +442,14 @@ Please follow the instructions at: fburl.com/rn-remote-assets`,
},
componentStack: [
{
component: 'MyComponent',
location: 'filename.js:1',
content: 'MyComponent',
fileName: 'filename.js',
location: {column: -1, row: 1},
},
{
component: 'MyOtherComponent',
location: 'filename2.js:1',
content: 'MyOtherComponent',
fileName: 'filename2.js',
location: {column: -1, row: 1},
},
],
stack: [
+12 -10
View File
@@ -42,12 +42,7 @@ export type Message = $ReadOnly<{|
>,
|}>;
export type ComponentStack = $ReadOnlyArray<
$ReadOnly<{|
component: string,
location: string,
|}>,
>;
export type ComponentStack = $ReadOnlyArray<CodeFrame>;
const SUBSTITUTION = UTFSequence.BOM + '%s';
@@ -127,6 +122,7 @@ export function parseCategory(
},
};
}
export function parseComponentStack(message: string): ComponentStack {
return message
.split(/\n {4}in /g)
@@ -134,11 +130,17 @@ export function parseComponentStack(message: string): ComponentStack {
if (!s) {
return null;
}
let [component, location] = s.split(/ \(at /);
if (!location) {
[component, location] = s.split(/ \(/);
const match = s.match(/(.*) \(at (.*\.js):([\d]+)\)/);
if (!match) {
return null;
}
return {component, location: location && location.replace(')', '')};
let [content, fileName, row] = match.slice(1);
return {
content,
fileName,
location: {column: -1, row: parseInt(row, 10)},
};
})
.filter(Boolean);
}
+28 -98
View File
@@ -10,26 +10,14 @@
'use strict';
import * as React from 'react';
import Platform from '../Utilities/Platform';
import RCTLog from '../Utilities/RCTLog';
import LogBoxContainer from './UI/LogBoxContainer';
import * as LogBoxData from './Data/LogBoxData';
import {parseLogBoxLog} from './Data/parseLogBoxLog';
import type {LogBoxLogs, Subscription, IgnorePattern} from './Data/LogBoxData';
import type {IgnorePattern} from './Data/LogBoxData';
import LogBoxLog from './Data/LogBoxLog';
type Props = $ReadOnly<{||}>;
type State = {|
logs: LogBoxLogs,
isDisabled: boolean,
hasError: boolean,
selectedLogIndex: number,
|};
let LogBoxComponent;
let LogBox;
/**
* LogBox displays logs in the app.
@@ -48,25 +36,26 @@ if (__DEV__) {
warnImpl(...args);
};
LogBoxComponent = class LogBox extends React.Component<Props, State> {
static getDerivedStateFromError() {
return {hasError: true};
}
componentDidCatch(err, errorInfo) {
LogBoxData.reportLogBoxError(err, errorInfo.componentStack);
}
LogBox = {
// TODO: deprecated, replace with ignoreLogs
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
ignoreWarnings: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
LogBox.ignoreLogs(patterns);
}
},
static ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
ignoreLogs: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
LogBoxData.addIgnorePatterns(patterns);
}
},
uninstall: (): void => {
errorImpl = error;
warnImpl = warn;
delete (console: any).disableLogBox;
},
install: (): void => {
// Trigger lazy initialization of module.
require('../NativeModules/specs/NativeLogBox');
static install(): void {
errorImpl = function(...args) {
registerError(...args);
};
@@ -92,62 +81,7 @@ if (__DEV__) {
RCTLog.setWarningHandler((...args) => {
registerWarning(...args);
});
}
static uninstall(): void {
errorImpl = error;
warnImpl = warn;
delete (console: any).disableLogBox;
}
_subscription: ?Subscription;
state = {
logs: new Set(),
isDisabled: false,
hasError: false,
selectedLogIndex: -1,
};
render(): React.Node {
if (this.state.hasError) {
// This happens when the component failed to render, in which case we delegate to the native redbox.
// We can't show anyback fallback UI here, because the error may be with <View> or <Text>.
return null;
}
return this.state.logs == null ? null : (
<LogBoxContainer
onDismiss={this._handleDismiss}
onDismissWarns={LogBoxData.clearWarnings}
onDismissErrors={LogBoxData.clearErrors}
logs={this.state.logs}
isDisabled={this.state.isDisabled}
selectedLogIndex={this.state.selectedLogIndex}
setSelectedLog={this._handleSetSelectedLog}
/>
);
}
componentDidMount(): void {
this._subscription = LogBoxData.observe(data => {
this.setState(data);
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
}
}
_handleDismiss(log: LogBoxLog): void {
LogBoxData.dismiss(log);
}
_handleSetSelectedLog(index: number): void {
LogBoxData.setSelectedLog(index);
}
},
};
const isRCTLogAdviceWarning = (...args) => {
@@ -227,31 +161,27 @@ if (__DEV__) {
}
};
} else {
LogBoxComponent = class extends React.Component<Props, State> {
LogBox = {
// TODO: deprecated, replace with ignoreLogs
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
ignoreWarnings: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
// Do nothing.
}
},
static ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
ignoreLogs: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
// Do nothing.
}
},
static install(): void {
install: (): void => {
// Do nothing.
}
},
static uninstall(): void {
uninstall: (): void => {
// Do nothing.
}
render(): React.Node {
return null;
}
},
};
}
module.exports = (LogBoxComponent: Class<React.Component<Props, State>> & {
module.exports = (LogBox: {
// TODO: deprecated, replace with ignoreLogs
ignoreWarnings($ReadOnlyArray<IgnorePattern>): void,
ignoreLogs($ReadOnlyArray<IgnorePattern>): void,
@@ -0,0 +1,68 @@
/**
* 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';
import * as React from 'react';
import {View, StyleSheet} from 'react-native';
import * as LogBoxData from './Data/LogBoxData';
import LogBoxInspector from './UI/LogBoxInspector';
import type LogBoxLog from './Data/LogBoxLog';
type Props = $ReadOnly<{|
logs: $ReadOnlyArray<LogBoxLog>,
selectedLogIndex: number,
isDisabled?: ?boolean,
|}>;
export class _LogBoxInspectorContainer extends React.Component<Props> {
render(): React.Node {
return (
<View style={StyleSheet.absoluteFill}>
<LogBoxInspector
onDismiss={this._handleDismiss}
onMinimize={this._handleMinimize}
onChangeSelectedIndex={this._handleSetSelectedLog}
logs={this.props.logs}
selectedIndex={this.props.selectedLogIndex}
/>
</View>
);
}
_handleDismiss = (): void => {
// Here we handle the cases when the log is dismissed and it
// was either the last log, or when the current index
// is now outside the bounds of the log array.
const {selectedLogIndex, logs} = this.props;
const logsArray = Array.from(logs);
if (selectedLogIndex != null) {
if (logsArray.length - 1 <= 0) {
LogBoxData.setSelectedLog(-1);
} else if (selectedLogIndex >= logsArray.length - 1) {
LogBoxData.setSelectedLog(selectedLogIndex - 1);
}
LogBoxData.dismiss(logsArray[selectedLogIndex]);
}
};
_handleMinimize = (): void => {
LogBoxData.setSelectedLog(-1);
};
_handleSetSelectedLog = (index: number): void => {
LogBoxData.setSelectedLog(index);
};
}
export default (LogBoxData.withSubscription(
_LogBoxInspectorContainer,
): React.AbstractComponent<{||}>);
@@ -11,46 +11,31 @@
'use strict';
import * as React from 'react';
import SafeAreaView from '../../Components/SafeAreaView/SafeAreaView';
import StyleSheet from '../../StyleSheet/StyleSheet';
import View from '../../Components/View/View';
import LogBoxInspector from './LogBoxInspector';
import LogBoxLog from '../Data/LogBoxLog';
import LogBoxLogNotification from './LogBoxLogNotification';
import type {LogBoxLogs} from '../Data/LogBoxData';
import StyleSheet from '../StyleSheet/StyleSheet';
import View from '../Components/View/View';
import * as LogBoxData from './Data/LogBoxData';
import LogBoxLog from './Data/LogBoxLog';
import LogBoxLogNotification from './UI/LogBoxNotification';
type Props = $ReadOnly<{|
onDismiss: (log: LogBoxLog) => void,
onDismissWarns: () => void,
onDismissErrors: () => void,
setSelectedLog: number => void,
logs: LogBoxLogs,
logs: $ReadOnlyArray<LogBoxLog>,
selectedLogIndex: number,
isDisabled?: ?boolean,
|}>;
function LogBoxContainer(props: Props): React.Node {
const {selectedLogIndex, setSelectedLog} = props;
export function _LogBoxNotificationContainer(props: Props): React.Node {
const {logs} = props;
const logs = Array.from(props.logs);
const onDismissWarns = () => {
LogBoxData.clearWarnings();
};
const onDismissErrors = () => {
LogBoxData.clearErrors();
};
function handleInspectorDismiss() {
// Here we handle the cases when the log is dismissed and it
// was either the last log, or when the current index
// is now outside the bounds of the log array.
if (selectedLogIndex != null) {
if (logs.length - 1 <= 0) {
setSelectedLog(-1);
} else if (selectedLogIndex >= logs.length - 1) {
setSelectedLog(selectedLogIndex - 1);
}
props.onDismiss(logs[selectedLogIndex]);
}
}
function handleInspectorMinimize() {
setSelectedLog(-1);
}
const setSelectedLog = (index: number): void => {
LogBoxData.setSelectedLog(index);
};
function openLog(log: LogBoxLog) {
let index = logs.length - 1;
@@ -62,20 +47,6 @@ function LogBoxContainer(props: Props): React.Node {
setSelectedLog(index);
}
if (selectedLogIndex > -1) {
return (
<View style={StyleSheet.absoluteFill}>
<LogBoxInspector
onDismiss={handleInspectorDismiss}
onMinimize={handleInspectorMinimize}
onChangeSelectedIndex={setSelectedLog}
logs={logs}
selectedIndex={selectedLogIndex}
/>
</View>
);
}
if (logs.length === 0 || props.isDisabled === true) {
return null;
}
@@ -93,7 +64,7 @@ function LogBoxContainer(props: Props): React.Node {
level="warn"
totalLogCount={warnings.length}
onPressOpen={() => openLog(warnings[warnings.length - 1])}
onPressDismiss={props.onDismissWarns}
onPressDismiss={onDismissWarns}
/>
</View>
)}
@@ -104,7 +75,7 @@ function LogBoxContainer(props: Props): React.Node {
level="error"
totalLogCount={errors.length}
onPressOpen={() => openLog(errors[errors.length - 1])}
onPressDismiss={props.onDismissErrors}
onPressDismiss={onDismissErrors}
/>
</View>
)}
@@ -126,4 +97,6 @@ const styles = StyleSheet.create({
},
});
export default LogBoxContainer;
export default (LogBoxData.withSubscription(
_LogBoxNotificationContainer,
): React.AbstractComponent<{||}>);
+12 -12
View File
@@ -14,7 +14,7 @@ import LogBoxInspectorCodeFrame from './LogBoxInspectorCodeFrame';
import * as React from 'react';
import ScrollView from '../../Components/ScrollView/ScrollView';
import StyleSheet from '../../StyleSheet/StyleSheet';
import Modal from '../../Modal/Modal';
import View from '../../Components/View/View';
import * as LogBoxData from '../Data/LogBoxData';
import Keyboard from '../../Components/Keyboard/Keyboard';
import LogBoxInspectorFooter from './LogBoxInspectorFooter';
@@ -23,8 +23,7 @@ import LogBoxInspectorReactFrames from './LogBoxInspectorReactFrames';
import LogBoxInspectorStackFrames from './LogBoxInspectorStackFrames';
import LogBoxInspectorHeader from './LogBoxInspectorHeader';
import * as LogBoxStyle from './LogBoxStyle';
import type LogBoxLog, {LogLevel} from '../Data/LogBoxLog';
import LogBoxLog, {type LogLevel} from '../Data/LogBoxLog';
type Props = $ReadOnly<{|
onDismiss: () => void,
@@ -37,10 +36,12 @@ type Props = $ReadOnly<{|
function LogBoxInspector(props: Props): React.Node {
const {logs, selectedIndex} = props;
let log = logs[selectedIndex];
const log = logs[selectedIndex];
React.useEffect(() => {
LogBoxData.symbolicateLogNow(log);
if (log) {
LogBoxData.symbolicateLogNow(log);
}
}, [log]);
React.useEffect(() => {
@@ -68,12 +69,7 @@ function LogBoxInspector(props: Props): React.Node {
}
return (
<Modal
animationType="none"
visible
statusBarTranslucent
supportedOrientations={['portrait']}
presentationStyle="overFullScreen">
<View style={styles.root}>
<LogBoxInspectorHeader
onSelectIndex={props.onChangeSelectedIndex}
selectedIndex={selectedIndex}
@@ -86,7 +82,7 @@ function LogBoxInspector(props: Props): React.Node {
onMinimize={props.onMinimize}
level={log.level}
/>
</Modal>
</View>
);
}
@@ -143,6 +139,10 @@ function LogBoxInspectorBody(props) {
}
const styles = StyleSheet.create({
root: {
flex: 1,
backgroundColor: LogBoxStyle.getTextColor(),
},
scrollBody: {
backgroundColor: LogBoxStyle.getBackgroundColor(0.9),
flex: 1,
+4 -2
View File
@@ -101,9 +101,11 @@ const styles = StyleSheet.create({
syntaxErrorText: {
textAlign: 'center',
width: '100%',
height: 48,
fontSize: 14,
paddingTop: 15,
paddingBottom: 15,
lineHeight: 20,
paddingTop: 20,
paddingBottom: 50,
fontStyle: 'italic',
color: LogBoxStyle.getTextColor(0.6),
},
+2 -2
View File
@@ -126,7 +126,7 @@ const headerStyles = StyleSheet.create({
borderRadius: 3,
},
buttonImage: {
tintColor: LogBoxStyle.getBackgroundColor(1),
tintColor: LogBoxStyle.getTextColor(),
},
});
@@ -156,7 +156,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
titleText: {
color: LogBoxStyle.getBackgroundColor(1),
color: LogBoxStyle.getTextColor(),
fontSize: 16,
fontWeight: '600',
includeFontPadding: false,
@@ -18,12 +18,37 @@ import View from '../../Components/View/View';
import LogBoxButton from './LogBoxButton';
import * as LogBoxStyle from './LogBoxStyle';
import LogBoxInspectorSection from './LogBoxInspectorSection';
import openFileInEditor from '../../Core/Devtools/openFileInEditor';
import type LogBoxLog from '../Data/LogBoxLog';
type Props = $ReadOnly<{|
log: LogBoxLog,
|}>;
const BEFORE_SLASH_RE = /^(.*)[\\/]/;
// Taken from React https://github.com/facebook/react/blob/206d61f72214e8ae5b935f0bf8628491cb7f0797/packages/react-devtools-shared/src/backend/describeComponentFrame.js#L27-L41
function getPrettyFileName(path) {
let fileName = path.replace(BEFORE_SLASH_RE, '');
// In DEV, include code for a common special case:
// prefer "folder/index.js" instead of just "index.js".
if (/^index\./.test(fileName)) {
const match = path.match(BEFORE_SLASH_RE);
if (match) {
const pathBeforeSlash = match[1];
if (pathBeforeSlash) {
const folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, '');
// Note the below string contains a zero width space after the "/" character.
// This is to prevent browsers like Chrome from formatting the file name as a link.
// (Since this is a source link, it would not work to open the source file anyway.)
fileName = folderName + '/' + fileName;
}
}
}
return fileName;
}
function LogBoxInspectorReactFrames(props: Props): React.Node {
const [collapsed, setCollapsed] = React.useState(true);
if (props.log.componentStack == null || props.log.componentStack.length < 1) {
@@ -39,6 +64,10 @@ function LogBoxInspectorReactFrames(props: Props): React.Node {
}
function getCollapseMessage() {
if (props.log.componentStack.length <= 3) {
return;
}
const count = props.log.componentStack.length - 3;
if (collapsed) {
return `See ${count} more components`;
@@ -53,15 +82,34 @@ function LogBoxInspectorReactFrames(props: Props): React.Node {
<View
// Unfortunately we don't have a unique identifier for stack traces.
key={index}
style={componentStyles.frame}>
<View style={componentStyles.component}>
<Text style={componentStyles.frameName}>
<Text style={componentStyles.bracket}>{'<'}</Text>
{frame.component}
<Text style={componentStyles.bracket}>{' />'}</Text>
style={componentStyles.frameContainer}>
<LogBoxButton
backgroundColor={{
default: 'transparent',
pressed: LogBoxStyle.getBackgroundColor(1),
}}
onPress={
// Older versions of DevTools do not provide full path.
// This will not work on Windows, remove check once the
// DevTools return the full file path.
frame.fileName.startsWith('/')
? () =>
openFileInEditor(frame.fileName, frame.location?.row ?? 1)
: null
}
style={componentStyles.frame}>
<View style={componentStyles.component}>
<Text style={componentStyles.frameName}>
<Text style={componentStyles.bracket}>{'<'}</Text>
{frame.content}
<Text style={componentStyles.bracket}>{' />'}</Text>
</Text>
</View>
<Text style={componentStyles.frameLocation}>
{getPrettyFileName(frame.fileName)}
{frame.location ? `:${frame.location.row}` : ''}
</Text>
</View>
<Text style={componentStyles.frameLocation}>{frame.location}</Text>
</LogBoxButton>
</View>
))}
<View style={componentStyles.collapseContainer}>
@@ -96,9 +144,15 @@ const componentStyles = StyleSheet.create({
paddingVertical: 5,
paddingHorizontal: 10,
},
frameContainer: {
flexDirection: 'row',
paddingHorizontal: 15,
},
frame: {
paddingHorizontal: 25,
flex: 1,
paddingVertical: 4,
paddingHorizontal: 10,
borderRadius: 5,
},
component: {
flexDirection: 'row',
@@ -1,254 +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.
*
* @format
* @emails oncall+react_native
* @flow strict-local
*/
'use strict';
const React = require('react');
const LogBoxContainer = require('../LogBoxContainer').default;
const LogBoxLog = require('../../Data/LogBoxLog').default;
const render = require('../../../../jest/renderer');
describe('LogBoxContainer', () => {
it('should render null with no logs', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={new Set()}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render null with no selected log and disabled', () => {
const output = render.shallowRender(
<LogBoxContainer
isDisabled
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={
new Set([
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render the latest warning notification', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={
new Set([
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render the latest error notification', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={
new Set([
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render both an error and warning notification', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={
new Set([
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render selected fatal error even when disabled', () => {
const output = render.shallowRender(
<LogBoxContainer
isDisabled
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={0}
logs={
new Set([
new LogBoxLog({
level: 'fatal',
isComponentError: false,
message: {
content: 'Should be selected',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render selected syntax error even when disabled', () => {
const output = render.shallowRender(
<LogBoxContainer
isDisabled
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={0}
logs={
new Set([
new LogBoxLog({
level: 'syntax',
isComponentError: false,
message: {
content: 'Should be selected',
substitutions: [],
},
stack: [],
category: 'Some kind of syntax error message',
componentStack: [],
codeFrame: {
fileName:
'/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
location: {row: 199, column: 0},
content: ` 197 | });
198 |
> 199 | export default CrashReactApp;
| ^
200 |`,
},
}),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
});
@@ -40,7 +40,7 @@ describe('LogBoxInspectorReactFrames', () => {
expect(output).toMatchSnapshot();
});
it('should render componentStack frames', () => {
it('should render componentStack frames without full path pressable', () => {
const output = render.shallowRender(
<LogBoxInspectorReactFrames
log={
@@ -55,8 +55,129 @@ describe('LogBoxInspectorReactFrames', () => {
category: 'Some kind of message',
componentStack: [
{
component: 'MyComponent',
location: 'MyComponentFile.js:1',
content: 'MyComponent',
fileName: 'MyComponentFile.js',
location: {
row: 1,
column: -1,
},
},
],
})
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render componentStack frames with full path pressable', () => {
const output = render.shallowRender(
<LogBoxInspectorReactFrames
log={
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [
{
content: 'MyComponent',
fileName: '/path/to/MyComponentFile.js',
location: {
row: 1,
column: -1,
},
},
],
})
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render componentStack frames with parent folder of index.js', () => {
const output = render.shallowRender(
<LogBoxInspectorReactFrames
log={
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [
{
content: 'MyComponent',
fileName: '/path/to/index.js',
location: {
row: 1,
column: -1,
},
},
],
})
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render componentStack frames with more than 3 stacks', () => {
const output = render.shallowRender(
<LogBoxInspectorReactFrames
log={
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [
{
content: 'MyComponent',
fileName: '/path/to/index.js',
location: {
row: 1,
column: -1,
},
},
{
content: 'MyComponent2',
fileName: '/path/to/index2.js',
location: {
row: 1,
column: -1,
},
},
{
content: 'MyComponent3',
fileName: '/path/to/index3.js',
location: {
row: 1,
column: -1,
},
},
{
content: 'MyComponent4',
fileName: '/path/to/index4.js',
location: {
row: 1,
column: -1,
},
},
],
})
@@ -12,7 +12,7 @@
'use strict';
const React = require('react');
const LogBoxLogNotification = require('../LogBoxLogNotification').default;
const LogBoxNotification = require('../LogBoxNotification').default;
const LogBoxLog = require('../../Data/LogBoxLog').default;
const render = require('../../../../jest/renderer');
@@ -28,10 +28,10 @@ const log = new LogBoxLog({
componentStack: [],
});
describe('LogBoxLogNotification', () => {
describe('LogBoxNotification', () => {
it('should render log', () => {
const output = render.shallowRender(
<LogBoxLogNotification
<LogBoxNotification
log={log}
totalLogCount={1}
level="warn"
@@ -1,17 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxContainer should render fatal with selectedIndex 2 1`] = `
<Component
animationType="none"
hardwareAccelerated={false}
presentationStyle="overFullScreen"
statusBarTranslucent={true}
supportedOrientations={
Array [
"portrait",
]
<View
style={
Object {
"backgroundColor": "rgba(255, 255, 255, 1)",
"flex": 1,
}
}
visible={true}
>
<LogBoxInspectorHeader
level="fatal"
@@ -47,23 +43,19 @@ exports[`LogBoxContainer should render fatal with selectedIndex 2 1`] = `
onDismiss={[Function]}
onMinimize={[Function]}
/>
</Component>
</View>
`;
exports[`LogBoxContainer should render null with no logs 1`] = `null`;
exports[`LogBoxContainer should render warning with selectedIndex 0 1`] = `
<Component
animationType="none"
hardwareAccelerated={false}
presentationStyle="overFullScreen"
statusBarTranslucent={true}
supportedOrientations={
Array [
"portrait",
]
<View
style={
Object {
"backgroundColor": "rgba(255, 255, 255, 1)",
"flex": 1,
}
}
visible={true}
>
<LogBoxInspectorHeader
level="warn"
@@ -99,5 +91,5 @@ exports[`LogBoxContainer should render warning with selectedIndex 0 1`] = `
onDismiss={[Function]}
onMinimize={[Function]}
/>
</Component>
</View>
`;
@@ -29,8 +29,10 @@ exports[`LogBoxInspectorFooter should render no buttons and a message for syntax
"color": "rgba(255, 255, 255, 0.6)",
"fontSize": 14,
"fontStyle": "italic",
"paddingBottom": 15,
"paddingTop": 15,
"height": 48,
"lineHeight": 20,
"paddingBottom": 50,
"paddingTop": 20,
"textAlign": "center",
"width": "100%",
}
@@ -39,7 +39,7 @@ exports[`LogBoxInspectorHeader should render both buttons for two total 1`] = `
<Text
style={
Object {
"color": "rgba(51, 51, 51, 1)",
"color": "rgba(255, 255, 255, 1)",
"fontSize": 16,
"fontWeight": "600",
"includeFontPadding": false,
@@ -99,7 +99,7 @@ exports[`LogBoxInspectorHeader should render no buttons for one total 1`] = `
<Text
style={
Object {
"color": "rgba(51, 51, 51, 1)",
"color": "rgba(255, 255, 255, 1)",
"fontSize": 16,
"fontWeight": "600",
"includeFontPadding": false,
@@ -153,7 +153,7 @@ exports[`LogBoxInspectorHeader should render syntax error header 1`] = `
<Text
style={
Object {
"color": "rgba(51, 51, 51, 1)",
"color": "rgba(255, 255, 255, 1)",
"fontSize": 16,
"fontWeight": "600",
"includeFontPadding": false,
@@ -207,7 +207,7 @@ exports[`LogBoxInspectorHeader should render two buttons for three or more total
<Text
style={
Object {
"color": "rgba(51, 51, 51, 1)",
"color": "rgba(255, 255, 255, 1)",
"fontSize": 16,
"fontWeight": "600",
"includeFontPadding": false,
@@ -1,81 +1,420 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxInspectorReactFrames should render componentStack frames 1`] = `
exports[`LogBoxInspectorReactFrames should render componentStack frames with full path pressable 1`] = `
<LogBoxInspectorSection
heading="Component Stack"
>
<View
style={
Object {
"paddingHorizontal": 25,
"paddingVertical": 4,
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<View
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<Text
<View
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
}
>
/&gt;
>
&lt;
</Text>
MyComponent
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
MyComponentFile.js
:1
</Text>
</View>
<Text
</LogBoxButton>
</View>
<View
style={
Object {
"flexDirection": "row",
"marginLeft": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
"borderRadius": 5,
}
}
>
MyComponentFile.js:1
</Text>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"lineHeight": 20,
"marginTop": 0,
"paddingHorizontal": 10,
"paddingVertical": 5,
}
}
/>
</LogBoxButton>
</View>
</LogBoxInspectorSection>
`;
exports[`LogBoxInspectorReactFrames should render componentStack frames with more than 3 stacks 1`] = `
<LogBoxInspectorSection
heading="Component Stack"
>
<View
style={
Object {
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
to/index.js
:1
</Text>
</LogBoxButton>
</View>
<View
style={
Object {
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent2
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
index2.js
:1
</Text>
</LogBoxButton>
</View>
<View
style={
Object {
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent3
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
index3.js
:1
</Text>
</LogBoxButton>
</View>
<View
style={
@@ -112,11 +451,283 @@ exports[`LogBoxInspectorReactFrames should render componentStack frames 1`] = `
}
}
>
See -2 more components
See 1 more components
</Text>
</LogBoxButton>
</View>
</LogBoxInspectorSection>
`;
exports[`LogBoxInspectorReactFrames should render componentStack frames with parent folder of index.js 1`] = `
<LogBoxInspectorSection
heading="Component Stack"
>
<View
style={
Object {
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
to/index.js
:1
</Text>
</LogBoxButton>
</View>
<View
style={
Object {
"flexDirection": "row",
"marginLeft": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"lineHeight": 20,
"marginTop": 0,
"paddingHorizontal": 10,
"paddingVertical": 5,
}
}
/>
</LogBoxButton>
</View>
</LogBoxInspectorSection>
`;
exports[`LogBoxInspectorReactFrames should render componentStack frames without full path pressable 1`] = `
<LogBoxInspectorSection
heading="Component Stack"
>
<View
style={
Object {
"flexDirection": "row",
"paddingHorizontal": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={null}
style={
Object {
"borderRadius": 5,
"flex": 1,
"paddingHorizontal": 10,
"paddingVertical": 4,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"paddingRight": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 14,
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
&lt;
</Text>
MyComponent
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.4)",
"fontFamily": "Menlo",
"fontSize": 14,
"fontWeight": "500",
"includeFontPadding": false,
"lineHeight": 18,
}
}
>
/&gt;
</Text>
</Text>
</View>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"includeFontPadding": false,
"lineHeight": 16,
"paddingLeft": 10,
}
}
>
MyComponentFile.js
:1
</Text>
</LogBoxButton>
</View>
<View
style={
Object {
"flexDirection": "row",
"marginLeft": 15,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(51, 51, 51, 1)",
}
}
onPress={[Function]}
style={
Object {
"borderRadius": 5,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.7)",
"fontSize": 12,
"fontWeight": "300",
"lineHeight": 20,
"marginTop": 0,
"paddingHorizontal": 10,
"paddingVertical": 5,
}
}
/>
</LogBoxButton>
</View>
</LogBoxInspectorSection>
`;
exports[`LogBoxInspectorReactFrames should render null for no componentStack frames 1`] = `null`;
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxLogNotification should render log 1`] = `
exports[`LogBoxNotification should render log 1`] = `
<View
style={
Object {
@@ -0,0 +1,212 @@
/**
* 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.
*
* @format
* @emails oncall+react_native
* @flow
*/
'use strict';
const React = require('react');
const {
_LogBoxNotificationContainer: LogBoxNotificationContainer,
} = require('../LogBoxNotificationContainer');
const LogBoxLog = require('../Data/LogBoxLog').default;
const render = require('../../../jest/renderer');
describe('LogBoxNotificationContainer', () => {
it('should render null with no logs', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer selectedLogIndex={-1} logs={[]} />,
);
expect(output).toMatchSnapshot();
});
it('should render null with no selected log and disabled', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
isDisabled
selectedLogIndex={-1}
logs={[
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render the latest warning notification', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
selectedLogIndex={-1}
logs={[
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render the latest error notification', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
selectedLogIndex={-1}
logs={[
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render both an error and warning notification', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
selectedLogIndex={-1}
logs={[
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render selected fatal error even when disabled', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
isDisabled
selectedLogIndex={0}
logs={[
new LogBoxLog({
level: 'fatal',
isComponentError: false,
message: {
content: 'Should be selected',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render selected syntax error even when disabled', () => {
const output = render.shallowRender(
<LogBoxNotificationContainer
isDisabled
selectedLogIndex={0}
logs={[
new LogBoxLog({
level: 'syntax',
isComponentError: false,
message: {
content: 'Should be selected',
substitutions: [],
},
stack: [],
category: 'Some kind of syntax error message',
componentStack: [],
codeFrame: {
fileName: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
location: {row: 199, column: 0},
content: ` 197 | });
198 |
> 199 | export default CrashReactApp;
| ^
200 |`,
},
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
});
@@ -0,0 +1,56 @@
/**
* 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.
*
* @format
* @emails oncall+react_native
* @flow strict-local
*/
'use strict';
const React = require('react');
const {
_LogBoxInspectorContainer: LogBoxInspectorContainer,
} = require('../LogBoxInspectorContainer');
const LogBoxLog = require('../Data/LogBoxLog').default;
const render = require('../../../jest/renderer');
describe('LogBoxNotificationContainer', () => {
it('should render inspector with logs, even when disabled', () => {
const output = render.shallowRender(
<LogBoxInspectorContainer
isDisabled
selectedLogIndex={-1}
logs={[
new LogBoxLog({
level: 'warn',
isComponentError: false,
message: {
content: 'Some kind of message',
substitutions: [],
},
stack: [],
category: 'Some kind of message',
componentStack: [],
}),
new LogBoxLog({
level: 'error',
isComponentError: false,
message: {
content: 'Some kind of message (latest)',
substitutions: [],
},
stack: [],
category: 'Some kind of message (latest)',
componentStack: [],
}),
]}
/>,
);
expect(output).toMatchSnapshot();
});
});
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxContainer should render both an error and warning notification 1`] = `
exports[`LogBoxNotificationContainer should render both an error and warning notification 1`] = `
<View
style={
Object {
@@ -86,108 +86,15 @@ exports[`LogBoxContainer should render both an error and warning notification 1`
</View>
`;
exports[`LogBoxContainer should render null with no logs 1`] = `null`;
exports[`LogBoxNotificationContainer should render null with no logs 1`] = `null`;
exports[`LogBoxContainer should render null with no selected log and disabled 1`] = `null`;
exports[`LogBoxNotificationContainer should render null with no selected log and disabled 1`] = `null`;
exports[`LogBoxContainer should render selected fatal error even when disabled 1`] = `
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<LogBoxInspector
logs={
Array [
LogBoxLog {
"category": "Some kind of message",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"isComponentError": false,
"level": "fatal",
"message": Object {
"content": "Should be selected",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
]
}
onChangeSelectedIndex={[Function]}
onDismiss={[Function]}
onMinimize={[Function]}
selectedIndex={0}
/>
</View>
`;
exports[`LogBoxNotificationContainer should render selected fatal error even when disabled 1`] = `null`;
exports[`LogBoxContainer should render selected syntax error even when disabled 1`] = `
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<LogBoxInspector
logs={
Array [
LogBoxLog {
"category": "Some kind of syntax error message",
"codeFrame": Object {
"content": " 197 | });
198 |
> 199 | export default CrashReactApp;
| ^
200 |",
"fileName": "/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js",
"location": Object {
"column": 0,
"row": 199,
},
},
"componentStack": Array [],
"count": 1,
"isComponentError": false,
"level": "syntax",
"message": Object {
"content": "Should be selected",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
]
}
onChangeSelectedIndex={[Function]}
onDismiss={[Function]}
onMinimize={[Function]}
selectedIndex={0}
/>
</View>
`;
exports[`LogBoxNotificationContainer should render selected syntax error even when disabled 1`] = `null`;
exports[`LogBoxContainer should render the latest error notification 1`] = `
exports[`LogBoxNotificationContainer should render the latest error notification 1`] = `
<View
style={
Object {
@@ -237,7 +144,7 @@ exports[`LogBoxContainer should render the latest error notification 1`] = `
</View>
`;
exports[`LogBoxContainer should render the latest warning notification 1`] = `
exports[`LogBoxNotificationContainer should render the latest warning notification 1`] = `
<View
style={
Object {
@@ -0,0 +1,62 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxNotificationContainer should render inspector with logs, even when disabled 1`] = `
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<LogBoxInspector
logs={
Array [
LogBoxLog {
"category": "Some kind of message",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"isComponentError": false,
"level": "warn",
"message": Object {
"content": "Some kind of message",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
LogBoxLog {
"category": "Some kind of message (latest)",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"isComponentError": false,
"level": "error",
"message": Object {
"content": "Some kind of message (latest)",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
]
}
onChangeSelectedIndex={[Function]}
onDismiss={[Function]}
onMinimize={[Function]}
selectedIndex={-1}
/>
</View>
`;
+1 -1
View File
@@ -229,7 +229,7 @@ class Modal extends React.Component<Props> {
}
const innerChildren = __DEV__ ? (
<AppContainer rootTag={this.context.rootTag} internal_excludeLogBox>
<AppContainer rootTag={this.context.rootTag}>
{this.props.children}
</AppContainer>
) : (
@@ -0,0 +1,21 @@
/**
* 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';
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+show: () => void;
+hide: () => void;
}
export default (TurboModuleRegistry.get<Spec>('LogBox'): ?Spec);
+11 -27
View File
@@ -94,16 +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 LogBox = require('../LogBox/LogBox');
logBox = <LogBox />;
} 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 />;
}
}
@@ -137,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>
);
@@ -152,22 +150,8 @@ const styles = StyleSheet.create({
if (__DEV__) {
if (!global.__RCTProfileIsProfiling) {
if (global.__reactExperimentalLogBox) {
const LogBox = require('../LogBox/LogBox');
LogBox.install();
// TODO: (rickhanlonii) T57484314 Temporary hack to fix LogBox experiment but we need to
// either decide to provide an error boundary by default or move this to a separate root.
AppContainer.getDerivedStateFromError = function getDerivedStateFromError(
error,
state,
) {
return {...state, hasError: true};
};
} else {
const YellowBox = require('../YellowBox/YellowBox');
YellowBox.install();
}
const YellowBox = require('../YellowBox/YellowBox');
YellowBox.install();
}
}
+23 -7
View File
@@ -125,6 +125,7 @@ const AppRegistry = {
appParameters.fabric,
showArchitectureIndicator,
scopedPerformanceLogger,
appKey === 'LogBox',
);
},
};
@@ -180,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` +
@@ -292,4 +295,17 @@ const AppRegistry = {
BatchedBridge.registerCallableModule('AppRegistry', AppRegistry);
if (__DEV__) {
const LogBoxInspector = require('../LogBox/LogBoxInspectorContainer').default;
AppRegistry.registerComponent('LogBox', () => LogBoxInspector);
} else {
AppRegistry.registerComponent(
'LogBox',
() =>
function NoOp() {
return null;
},
);
}
module.exports = AppRegistry;
+3 -1
View File
@@ -29,6 +29,7 @@ function renderApplication<Props: Object>(
fabric?: boolean,
showArchitectureIndicator?: boolean,
scopedPerformanceLogger?: IPerformanceLogger,
isLogBox?: boolean,
) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
@@ -39,7 +40,8 @@ function renderApplication<Props: Object>(
rootTag={rootTag}
fabric={fabric}
showArchitectureIndicator={showArchitectureIndicator}
WrapperComponent={WrapperComponent}>
WrapperComponent={WrapperComponent}
internal_excludeLogBox={isLogBox}>
<RootComponent {...initialProps} rootTag={rootTag} />
</AppContainer>
</PerformanceLoggerContext.Provider>
+26 -28
View File
@@ -12,8 +12,8 @@
const YellowBoxWarning = require('./YellowBoxWarning');
import type {Category} from './YellowBoxCategory';
import type {Category, Message} from './YellowBoxCategory';
import type {Stack} from './YellowBoxSymbolication';
export type Registry = Map<Category, $ReadOnlyArray<YellowBoxWarning>>;
export type Observer = (registry: Registry) => void;
@@ -32,25 +32,13 @@ let disabled = false;
let projection = new Map();
let updateTimeout = null;
function isWarningIgnored(warning: YellowBoxWarning): boolean {
for (const pattern of ignorePatterns) {
if (pattern instanceof RegExp && pattern.test(warning.message.content)) {
return true;
} else if (
typeof pattern === 'string' &&
warning.message.content.includes(pattern)
) {
return true;
}
}
return false;
}
function handleUpdate(): void {
projection = new Map();
if (!disabled) {
for (const [category, warnings] of registry) {
const filtered = warnings.filter(warning => !isWarningIgnored(warning));
const filtered = warnings.filter(
warning => !YellowBoxRegistry.isWarningIgnored(warning.message),
);
if (filtered.length > 0) {
projection.set(category, filtered);
}
@@ -67,18 +55,28 @@ function handleUpdate(): void {
}
const YellowBoxRegistry = {
add({
args,
}: $ReadOnly<{|
args: $ReadOnlyArray<mixed>,
|}>): void {
if (typeof args[0] === 'string' && args[0].startsWith('(ADVICE)')) {
return;
isWarningIgnored(message: Message): boolean {
for (const pattern of ignorePatterns) {
if (pattern instanceof RegExp && pattern.test(message.content)) {
return true;
} else if (
typeof pattern === 'string' &&
message.content.includes(pattern)
) {
return true;
}
}
const {category, message, stack} = YellowBoxWarning.parse({
args,
});
return false;
},
add({
category,
message,
stack,
}: $ReadOnly<{|
category: Category,
message: Message,
stack: Stack,
|}>): void {
let warnings = registry.get(category);
if (warnings == null) {
warnings = [];
@@ -11,6 +11,7 @@
'use strict';
const YellowBoxWarning = require('../YellowBoxWarning');
const YellowBoxCategory = require('../YellowBoxCategory');
const YellowBoxRegistry = require('../YellowBoxRegistry');
@@ -34,7 +35,7 @@ describe('YellowBoxRegistry', () => {
});
it('adds and deletes warnings', () => {
YellowBoxRegistry.add({args: ['A']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A']}));
const {category: categoryA} = YellowBoxCategory.parse(['A']);
expect(registry().size).toBe(1);
@@ -46,9 +47,9 @@ describe('YellowBoxRegistry', () => {
});
it('clears all warnings', () => {
YellowBoxRegistry.add({args: ['A']});
YellowBoxRegistry.add({args: ['B']});
YellowBoxRegistry.add({args: ['C']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['B']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['C']}));
expect(registry().size).toBe(3);
@@ -57,9 +58,9 @@ describe('YellowBoxRegistry', () => {
});
it('sorts warnings in chronological order', () => {
YellowBoxRegistry.add({args: ['A']});
YellowBoxRegistry.add({args: ['B']});
YellowBoxRegistry.add({args: ['C']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['B']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['C']}));
const {category: categoryA} = YellowBoxCategory.parse(['A']);
const {category: categoryB} = YellowBoxCategory.parse(['B']);
@@ -71,7 +72,7 @@ describe('YellowBoxRegistry', () => {
categoryC,
]);
YellowBoxRegistry.add({args: ['A']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A']}));
// Expect `A` to be hoisted to the end of the registry.
expect(Array.from(registry().keys())).toEqual([
@@ -82,9 +83,9 @@ describe('YellowBoxRegistry', () => {
});
it('ignores warnings matching patterns', () => {
YellowBoxRegistry.add({args: ['A!']});
YellowBoxRegistry.add({args: ['B?']});
YellowBoxRegistry.add({args: ['C!']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A!']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['B?']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['C!']}));
expect(registry().size).toBe(3);
YellowBoxRegistry.addIgnorePatterns(['!']);
@@ -95,9 +96,9 @@ describe('YellowBoxRegistry', () => {
});
it('ignores warnings matching regexs or pattern', () => {
YellowBoxRegistry.add({args: ['There are 4 dogs']});
YellowBoxRegistry.add({args: ['There are 3 cats']});
YellowBoxRegistry.add({args: ['There are H cats']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['There are 4 dogs']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['There are 3 cats']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['There are H cats']}));
expect(registry().size).toBe(3);
YellowBoxRegistry.addIgnorePatterns(['dogs']);
@@ -111,9 +112,9 @@ describe('YellowBoxRegistry', () => {
});
it('ignores all warnings when disabled', () => {
YellowBoxRegistry.add({args: ['A!']});
YellowBoxRegistry.add({args: ['B?']});
YellowBoxRegistry.add({args: ['C!']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A!']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['B?']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['C!']}));
expect(registry().size).toBe(3);
YellowBoxRegistry.setDisabled(true);
@@ -124,57 +125,54 @@ describe('YellowBoxRegistry', () => {
});
it('groups warnings by simple categories', () => {
YellowBoxRegistry.add({args: ['A']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A']}));
expect(registry().size).toBe(1);
YellowBoxRegistry.add({args: ['A']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A']}));
expect(registry().size).toBe(1);
YellowBoxRegistry.add({args: ['B']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['B']}));
expect(registry().size).toBe(2);
});
it('groups warnings by format string categories', () => {
YellowBoxRegistry.add({args: ['%s', 'A']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['%s', 'A']}));
expect(registry().size).toBe(1);
YellowBoxRegistry.add({args: ['%s', 'B']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['%s', 'B']}));
expect(registry().size).toBe(1);
YellowBoxRegistry.add({args: ['A']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A']}));
expect(registry().size).toBe(2);
YellowBoxRegistry.add({args: ['B']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['B']}));
expect(registry().size).toBe(3);
});
it('groups warnings with consideration for arguments', () => {
YellowBoxRegistry.add({args: ['A', 'B']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A', 'B']}));
expect(registry().size).toBe(1);
YellowBoxRegistry.add({args: ['A', 'B']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A', 'B']}));
expect(registry().size).toBe(1);
YellowBoxRegistry.add({args: ['A', 'C']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A', 'C']}));
expect(registry().size).toBe(2);
YellowBoxRegistry.add({args: ['%s', 'A', 'A']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['%s', 'A', 'A']}));
expect(registry().size).toBe(3);
YellowBoxRegistry.add({args: ['%s', 'B', 'A']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['%s', 'B', 'A']}));
expect(registry().size).toBe(3);
YellowBoxRegistry.add({args: ['%s', 'B', 'B']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['%s', 'B', 'B']}));
expect(registry().size).toBe(4);
});
it('ignores warnings starting with "(ADVICE)"', () => {
YellowBoxRegistry.add({args: ['(ADVICE) ...']});
expect(registry().size).toBe(0);
});
it('does not ignore warnings formatted to start with "(ADVICE)"', () => {
YellowBoxRegistry.add({args: ['%s ...', '(ADVICE)']});
YellowBoxRegistry.add(
YellowBoxWarning.parse({args: ['%s ...', '(ADVICE)']}),
);
expect(registry().size).toBe(1);
});
@@ -189,8 +187,8 @@ describe('YellowBoxRegistry', () => {
const {observer} = observe();
expect(observer.mock.calls.length).toBe(1);
YellowBoxRegistry.add({args: ['A']});
YellowBoxRegistry.add({args: ['B']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A']}));
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['B']}));
jest.runAllImmediates();
expect(observer.mock.calls.length).toBe(2);
});
@@ -207,7 +205,7 @@ describe('YellowBoxRegistry', () => {
const {observer} = observe();
expect(observer.mock.calls.length).toBe(1);
YellowBoxRegistry.add({args: ['A']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A']}));
jest.runAllImmediates();
expect(observer.mock.calls.length).toBe(2);
@@ -226,7 +224,7 @@ describe('YellowBoxRegistry', () => {
const {observer} = observe();
expect(observer.mock.calls.length).toBe(1);
YellowBoxRegistry.add({args: ['A']});
YellowBoxRegistry.add(YellowBoxWarning.parse({args: ['A']}));
jest.runAllImmediates();
expect(observer.mock.calls.length).toBe(2);
+102 -44
View File
@@ -12,13 +12,12 @@
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 YellowBoxWarning from './Data/YellowBoxWarning';
import * as LogBoxData from '../LogBox/Data/LogBoxData';
import NativeLogBox from '../NativeModules/specs/NativeLogBox';
type Props = $ReadOnly<{||}>;
type State = {|
registry: ?Registry,
@@ -47,14 +46,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,16 +74,17 @@ if (__DEV__) {
}
static install(): void {
if (_isLogBoxEnabled) {
LogBox.install();
return;
}
_isInstalled = true;
errorImpl = function(...args) {
error.call(console, ...args);
// Show YellowBox for the `warning` module.
if (typeof args[0] === 'string' && args[0].startsWith('Warning: ')) {
registerWarning(...args);
}
registerError(...args);
};
warnImpl = function(...args) {
warn.call(console, ...args);
registerWarning(...args);
};
@@ -102,51 +107,95 @@ 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 (NativeLogBox == null) {
// The native module is required to enable LogBox.
return;
}
state = {
registry: null,
};
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 {
// 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 />;
}
};
const registerWarning = (...args): void => {
YellowBoxRegistry.add({args});
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> {
@@ -162,6 +211,13 @@ if (__DEV__) {
// Do nothing.
}
static __unstable_enableLogBox(): void {
// Do nothing.
}
static __unstable_isLogBoxEnabled(): boolean {
return false;
}
render(): React.Node {
return null;
}
@@ -172,5 +228,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;
+202 -9
View File
@@ -11,20 +11,62 @@
'use strict';
const YellowBox = require('../YellowBox');
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();
(console: any).error = jest.fn();
(console: any).warn = jest.fn();
mockError.mockClear();
mockWarn.mockClear();
(console: any).error = mockError;
(console: any).warn = mockWarn;
});
afterEach(() => {
YellowBox.uninstall();
uninstall();
(console: any).error = error;
(console: any).warn = warn;
});
@@ -32,7 +74,7 @@ describe('YellowBox', () => {
it('can set `disableYellowBox` after installing', () => {
expect((console: any).disableYellowBox).toBe(undefined);
YellowBox.install();
install();
expect((console: any).disableYellowBox).toBe(false);
expect(YellowBoxRegistry.isDisabled()).toBe(false);
@@ -47,7 +89,7 @@ describe('YellowBox', () => {
expect((console: any).disableYellowBox).toBe(undefined);
(console: any).disableYellowBox = true;
YellowBox.install();
install();
expect((console: any).disableYellowBox).toBe(true);
expect(YellowBoxRegistry.isDisabled()).toBe(true);
@@ -56,22 +98,173 @@ describe('YellowBox', () => {
it('registers warnings', () => {
jest.mock('../Data/YellowBoxRegistry');
YellowBox.install();
install();
expect(YellowBoxRegistry.add).not.toBeCalled();
(console: any).warn('...');
expect(YellowBoxRegistry.add).toBeCalled();
expect(mockWarn).toBeCalledTimes(1);
expect(mockWarn).toBeCalledWith('...');
});
it('registers errors beginning with "Warning: "', () => {
it('registers errors', () => {
jest.mock('../Data/YellowBoxRegistry');
YellowBox.install();
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,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 />`;
+11
View File
@@ -0,0 +1,11 @@
/*
* 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.
*/
#import <React/RCTDefines.h>
RCT_EXTERN NSString *const RCTUserInterfaceStyleDidChangeNotification;
RCT_EXTERN NSString *const RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey;
+11
View File
@@ -0,0 +1,11 @@
/*
* 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.
*/
#import "RCTConstants.h"
NSString *const RCTUserInterfaceStyleDidChangeNotification = @"RCTUserInterfaceStyleDidChangeNotification";
NSString *const RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey = @"traitCollection";
+6 -11
View File
@@ -14,6 +14,7 @@
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTConstants.h"
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
@@ -33,7 +34,6 @@
#endif
NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification";
static NSString *const RCTUserInterfaceStyleDidChangeNotification = @"RCTUserInterfaceStyleDidChangeNotification";
@interface RCTUIManager (RCTRootView)
@@ -367,21 +367,16 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
[self showLoadingView];
}
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
if ([previousTraitCollection hasDifferentColorAppearanceComparedToTraitCollection:self.traitCollection]) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUserInterfaceStyleDidChangeNotification
object:self
userInfo:@{@"traitCollection": self.traitCollection}];
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUserInterfaceStyleDidChangeNotification
object:self
userInfo:@{
RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey: self.traitCollection,
}];
}
#endif
- (void)dealloc
{
+5 -5
View File
@@ -21,11 +21,11 @@ NSDictionary* RCTGetReactNativeVersion(void)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^(void){
__rnVersion = @{
RCTVersionMajor: @(0),
RCTVersionMinor: @(0),
RCTVersionPatch: @(0),
RCTVersionPrerelease: [NSNull null],
};
RCTVersionMajor: @(0),
RCTVersionMinor: @(62),
RCTVersionPatch: @(0),
RCTVersionPrerelease: @"rc.1",
};
});
return __rnVersion;
}
@@ -7,14 +7,13 @@
#import "RCTSurfaceHostingView.h"
#import "RCTConstants.h"
#import "RCTDefines.h"
#import "RCTSurface.h"
#import "RCTSurfaceDelegate.h"
#import "RCTSurfaceView.h"
#import "RCTUtils.h"
static NSString *const RCTUserInterfaceStyleDidChangeNotification = @"RCTUserInterfaceStyleDidChangeNotification";
@interface RCTSurfaceHostingView ()
@property (nonatomic, assign) BOOL isActivityIndicatorViewVisible;
@@ -208,21 +207,15 @@ RCT_NOT_IMPLEMENTED(- (nullable instancetype)initWithCoder:(NSCoder *)coder)
#pragma mark - UITraitCollection updates
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
if ([previousTraitCollection hasDifferentColorAppearanceComparedToTraitCollection:self.traitCollection]) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUserInterfaceStyleDidChangeNotification
object:self
userInfo:@{@"traitCollection": self.traitCollection}];
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUserInterfaceStyleDidChangeNotification
object:self
userInfo:@{
RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey: self.traitCollection,
}];
}
#endif
#pragma mark - Private stuff
+3
View File
@@ -96,6 +96,9 @@ rn_apple_library(
) + react_module_plugin_providers(
name = "RedBox",
native_class_func = "RCTRedBoxCls",
) + react_module_plugin_providers(
name = "LogBox",
native_class_func = "RCTLogBoxCls",
) + react_module_plugin_providers(
name = "TVNavigationEventEmitter",
native_class_func = "RCTTVNavigationEventEmitterCls",
+1
View File
@@ -48,6 +48,7 @@ Class RCTPerfMonitorCls(void) __attribute__((used));
Class RCTDevMenuCls(void) __attribute__((used));
Class RCTDevSettingsCls(void) __attribute__((used));
Class RCTRedBoxCls(void) __attribute__((used));
Class RCTLogBoxCls(void) __attribute__((used));
Class RCTTVNavigationEventEmitterCls(void) __attribute__((used));
Class RCTWebSocketExecutorCls(void) __attribute__((used));
Class RCTWebSocketModuleCls(void) __attribute__((used));
+1
View File
@@ -37,6 +37,7 @@ Class RCTCoreModulesClassProvider(const char *name) {
{"DevMenu", RCTDevMenuCls},
{"DevSettings", RCTDevSettingsCls},
{"RedBox", RCTRedBoxCls},
{"LogBox", RCTLogBoxCls},
{"TVNavigationEventEmitter", RCTTVNavigationEventEmitterCls},
{"WebSocketExecutor", RCTWebSocketExecutorCls},
{"WebSocketModule", RCTWebSocketModuleCls},
-2
View File
@@ -12,7 +12,5 @@
RCT_EXTERN void RCTEnableAppearancePreference(BOOL enabled);
NSString *const RCTUserInterfaceStyleDidChangeNotification = @"RCTUserInterfaceStyleDidChangeNotification";
@interface RCTAppearance : RCTEventEmitter <RCTBridgeModule>
@end
+2 -1
View File
@@ -8,6 +8,7 @@
#import "RCTAppearance.h"
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTConstants.h>
#import <React/RCTEventEmitter.h>
#import "CoreModulesPlugins.h"
@@ -86,7 +87,7 @@ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getColorScheme)
NSDictionary *userInfo = [notification userInfo];
UITraitCollection *traitCollection = nil;
if (userInfo) {
traitCollection = userInfo[@"traitCollection"];
traitCollection = userInfo[RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey];
}
NSString *newColorScheme = RCTColorSchemePreference(traitCollection);
if (![_currentColorScheme isEqualToString:newColorScheme]) {
+10 -3
View File
@@ -10,6 +10,7 @@
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTAccessibilityManager.h>
#import <React/RCTAssert.h>
#import <React/RCTConstants.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTUIUtils.h>
#import <React/RCTUtils.h>
@@ -61,9 +62,15 @@ RCT_EXPORT_MODULE()
_currentInterfaceDimensions = RCTExportedDimensions(_bridge);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(interfaceFrameDidChange)
name:UIApplicationDidBecomeActiveNotification
object:nil];
selector:@selector(interfaceFrameDidChange)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(interfaceFrameDidChange)
name:RCTUserInterfaceStyleDidChangeNotification
object:nil];
#endif
}
+21
View File
@@ -0,0 +1,21 @@
/*
* 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.
*/
#import <UIKit/UIKit.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTErrorCustomizer.h>
@class RCTJSStackFrame;
@interface RCTLogBox : NSObject <RCTBridgeModule>
- (void)show;
- (void)hide;
@end
+159
View File
@@ -0,0 +1,159 @@
/*
* 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.
*/
#import "RCTLogBox.h"
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTBridge.h>
#import <React/RCTRootView.h>
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>
#import <React/RCTErrorInfo.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTJSStackFrame.h>
#import <React/RCTRedBoxSetEnabled.h>
#import <React/RCTReloadCommand.h>
#import <React/RCTRedBoxSetEnabled.h>
#import <React/RCTSurface.h>
#import <React/RCTUtils.h>
#import <objc/runtime.h>
#import "CoreModulesPlugins.h"
#if RCT_DEV_MENU
@class RCTLogBoxView;
@interface RCTLogBoxView : UIView
@end
@implementation RCTLogBoxView
{
UIViewController *_rootViewController;
RCTSurface *_surface;
}
- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge
{
if ((self = [super initWithFrame:frame])) {
self.backgroundColor = [UIColor clearColor];
_surface = [[RCTSurface alloc] initWithBridge:bridge moduleName:@"LogBox" initialProperties:@{}];
[_surface start];
[_surface setSize:frame.size];
if (![_surface synchronouslyWaitForStage:RCTSurfaceStageSurfaceDidInitialMounting timeout:1]) {
RCTLogInfo(@"Failed to mount LogBox within 1s");
}
_rootViewController = [UIViewController new];
_rootViewController.view = (UIView *)_surface.view;
_rootViewController.view.backgroundColor = [UIColor clearColor];
_rootViewController.modalPresentationStyle = UIModalPresentationFullScreen;
}
return self;
}
- (void)dealloc
{
// Dismiss by deallocating the window.
// This will also handle JS reload, otherwise the LogBox view would be stuck on top.
[_rootViewController.view resignFirstResponder];
[_rootViewController dismissViewControllerAnimated:NO completion:NULL];
}
- (void)show
{
[RCTSharedApplication().delegate.window.rootViewController presentViewController:_rootViewController animated:NO completion:^{
[self->_rootViewController.view becomeFirstResponder];
}];
}
@end
@interface RCTLogBox () <NativeLogBoxSpec>
@end
@implementation RCTLogBox
{
RCTLogBoxView *_view;
}
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
RCT_EXPORT_METHOD(show)
{
if (RCTRedBoxGetEnabled()) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!self->_view) {
self->_view = [[RCTLogBoxView alloc] initWithFrame:[UIScreen mainScreen].bounds bridge: self->_bridge];
}
[self->_view show];
});
}
}
RCT_EXPORT_METHOD(hide)
{
if (RCTRedBoxGetEnabled()) {
dispatch_async(dispatch_get_main_queue(), ^{
self->_view = nil;
});
}
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
if (RCTRedBoxGetEnabled()) {
return std::make_shared<facebook::react::NativeLogBoxSpecJSI>(self, jsInvoker);
}
return nullptr;
}
@end
#else // Disabled
@interface RCTLogBox() <NativeLogBoxSpec>
@end
@implementation RCTLogBox
+ (NSString *)moduleName
{
return nil;
}
- (void)show {
// noop
}
- (void)hide {
// noop
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
return std::make_shared<facebook::react::NativeLogBoxSpecJSI>(self, jsInvoker);
}
@end
#endif
Class RCTLogBoxCls(void) {
return RCTLogBox.class;
}
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_NAME=1000.0.0-master
VERSION_NAME=0.62.0-rc.1
GROUP=com.facebook.react
POM_NAME=ReactNative
@@ -0,0 +1,31 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*
* <p>Generated by an internal genrule from Flow types.
*
* @generated
* @nolint
*/
package com.facebook.fbreact.specs;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactModuleWithSpec;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
public abstract class NativeLogBoxSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule {
public NativeLogBoxSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public abstract void hide();
@ReactMethod
public abstract void show();
}
@@ -16,6 +16,7 @@ import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.devsupport.LogBoxModule;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.module.annotations.ReactModuleList;
import com.facebook.react.module.model.ReactModuleInfo;
@@ -49,6 +50,7 @@ import java.util.Map;
DeviceInfoModule.class,
DevSettingsModule.class,
ExceptionsManagerModule.class,
LogBoxModule.class,
HeadlessJsTaskSupportModule.class,
SourceCodeModule.class,
TimingModule.class,
@@ -94,6 +96,7 @@ import java.util.Map;
DeviceInfoModule.class,
DevSettingsModule.class,
ExceptionsManagerModule.class,
LogBoxModule.class,
HeadlessJsTaskSupportModule.class,
SourceCodeModule.class,
TimingModule.class,
@@ -142,6 +145,8 @@ import java.util.Map;
return new DevSettingsModule(reactContext, mReactInstanceManager.getDevSupportManager());
case ExceptionsManagerModule.NAME:
return new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager());
case LogBoxModule.NAME:
return new LogBoxModule(reactContext, mReactInstanceManager.getDevSupportManager());
case HeadlessJsTaskSupportModule.NAME:
return new HeadlessJsTaskSupportModule(reactContext);
case SourceCodeModule.NAME:
@@ -304,6 +304,27 @@ public class ReactInstanceManager {
public JavaScriptExecutorFactory getJavaScriptExecutorFactory() {
return ReactInstanceManager.this.getJSExecutorFactory();
}
@Override
public @Nullable View createRootView(String appKey) {
Activity currentActivity = getCurrentActivity();
if (currentActivity != null) {
ReactRootView rootView = new ReactRootView(currentActivity);
rootView.startReactApplication(ReactInstanceManager.this, appKey, null);
return rootView;
}
return null;
}
@Override
public void destroyRootView(View rootView) {
if (rootView instanceof ReactRootView) {
((ReactRootView) rootView).unmountReactApplication();
}
}
};
}
@@ -19,6 +19,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.hardware.SensorManager;
import android.util.Pair;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -377,6 +378,14 @@ public class DevSupportManagerImpl
}
}
public @Nullable View createRootView(String appKey) {
return mReactInstanceManagerHelper.createRootView(appKey);
}
public void destroyRootView(View rootView) {
mReactInstanceManagerHelper.destroyRootView(rootView);
}
private void hideDevOptionsDialog() {
if (mDevOptionsDialog != null) {
mDevOptionsDialog.dismiss();
@@ -7,6 +7,7 @@
package com.facebook.react.devsupport;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactContext;
@@ -40,6 +41,14 @@ public class DisabledDevSupportManager implements DevSupportManager {
@Override
public void showNewJSError(String message, ReadableArray details, int errorCookie) {}
@Override
public @Nullable View createRootView(String appKey) {
return null;
}
@Override
public void destroyRootView(View rootView) {}
@Override
public void updateJSError(String message, ReadableArray details, int errorCookie) {}
@@ -0,0 +1,24 @@
/*
* 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.
*/
package com.facebook.react.devsupport;
import android.app.Activity;
import android.app.Dialog;
import android.view.View;
import android.view.Window;
import com.facebook.react.R;
/** Dialog for displaying JS errors in LogBox. */
public class LogBoxDialog extends Dialog {
public LogBoxDialog(Activity context, View reactRootView) {
super(context, R.style.Theme_Catalyst_LogBox);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(reactRootView);
}
}
@@ -0,0 +1,110 @@
/*
* 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.
*/
package com.facebook.react.devsupport;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = LogBoxModule.NAME)
public class LogBoxModule extends ReactContextBaseJavaModule {
public static final String NAME = "LogBox";
private final DevSupportManager mDevSupportManager;
private @Nullable View mReactRootView;
private @Nullable LogBoxDialog mLogBoxDialog;
public LogBoxModule(ReactApplicationContext reactContext, DevSupportManager devSupportManager) {
super(reactContext);
mDevSupportManager = devSupportManager;
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mReactRootView == null) {
mReactRootView = mDevSupportManager.createRootView("LogBox");
if (mReactRootView == null) {
FLog.e(
ReactConstants.TAG,
"Unable to launch logbox because react was unable to create the root view");
}
}
}
});
}
@Override
public String getName() {
return NAME;
}
@ReactMethod
public void show() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mLogBoxDialog == null) {
Activity context = getCurrentActivity();
if (context == null || context.isFinishing()) {
FLog.e(
ReactConstants.TAG,
"Unable to launch logbox because react activity "
+ "is not available, here is the error that logbox would've displayed: ");
return;
}
mLogBoxDialog = new LogBoxDialog(context, mReactRootView);
mLogBoxDialog.setCancelable(false);
mLogBoxDialog.show();
}
}
});
}
@ReactMethod
public void hide() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mLogBoxDialog != null) {
if (mReactRootView.getParent() != null) {
((ViewGroup) mReactRootView.getParent()).removeView(mReactRootView);
}
mLogBoxDialog.dismiss();
mLogBoxDialog = null;
}
}
});
}
@Override
public void onCatalystInstanceDestroy() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mReactRootView != null) {
mDevSupportManager.destroyRootView(mReactRootView);
mReactRootView = null;
}
}
});
}
}
@@ -8,6 +8,7 @@
package com.facebook.react.devsupport;
import android.app.Activity;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
@@ -32,4 +33,9 @@ public interface ReactInstanceManagerDevHelper {
Activity getCurrentActivity();
JavaScriptExecutorFactory getJavaScriptExecutorFactory();
@Nullable
View createRootView(String appKey);
void destroyRootView(View rootView);
}
@@ -7,6 +7,7 @@
package com.facebook.react.devsupport.interfaces;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactContext;
@@ -25,6 +26,11 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler {
void addCustomDevOption(String optionName, DevOptionHandler optionHandler);
@Nullable
View createRootView(String appKey);
void destroyRootView(View rootView);
void showNewJSError(String message, ReadableArray details, int errorCookie);
void updateJSError(final String message, final ReadableArray details, final int errorCookie);
@@ -16,7 +16,7 @@ import java.util.Map;
public class ReactNativeVersion {
public static final Map<String, Object> VERSION = MapBuilder.<String, Object>of(
"major", 0,
"minor", 0,
"minor", 62,
"patch", 0,
"prerelease", null);
"prerelease", "rc.1");
}
@@ -96,6 +96,12 @@ public class ReactModalHostView extends ViewGroup implements LifecycleEventListe
// Do nothing as we are laid out by UIManager
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
dismiss();
}
@Override
public void addView(View child, int index) {
UiThreadUtil.assertOnUiThread();
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="catalyst_redbox_background">#eecc0000</color>
<color name="catalyst_logbox_background">#ffffffff</color>
</resources>
@@ -9,10 +9,22 @@
<item name="android:outAnimation">@android:anim/fade_out</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="Theme.Catalyst.LogBox">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowAnimationStyle">@style/Animation.Catalyst.LogBox</item>
<item name="android:inAnimation">@android:anim/fade_in</item>
<item name="android:outAnimation">@android:anim/fade_out</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="Animation.Catalyst.RedBox" parent="@android:style/Animation">
<item name="android:windowEnterAnimation">@anim/catalyst_push_up_in</item>
<item name="android:windowExitAnimation">@anim/catalyst_push_up_out</item>
</style>
<style name="Animation.Catalyst.LogBox" parent="@android:style/Animation">
<item name="android:windowEnterAnimation">@anim/catalyst_push_up_in</item>
<item name="android:windowExitAnimation">@anim/catalyst_push_up_out</item>
</style>
<style name="redboxButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
+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 {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "react-native",
"version": "1000.0.0",
"version": "0.62.0-rc.1",
"bin": "./cli.js",
"description": "A framework for building native apps using React",
"license": "MIT",
@@ -174,4 +174,4 @@
}
}
}
}
}
+1 -1
View File
@@ -10,7 +10,7 @@
},
"dependencies": {
"react": "16.11.0",
"react-native": "1000.0.0"
"react-native": "0.62.0-rc.1"
},
"devDependencies": {
"@babel/core": "^7.6.2",