LogBox - Rework how fatals and syntax errors are handled

Summary:
This diff makes a few changes to how errors and warnings are handled in LogBox:

- Soft errors continue to notify the user collapsed.
- Fatal errors pop up full screen over the content, but are dismissible.
- Syntax errors pop up full screen, and are **not** dismissible.
- Removed the "Reload" button on fatals, and added back the dismiss/close.
- Removed all buttons from syntax errors so users are encouraged to fix it and safe without reloading.

To do this we needed to:
- Move the selectedLogIndex state out of the component and up to the rest of the log state.
- Change the way popping a log works, it's now done by setting selectedLogIndex at log time instead of deriving it at render time, that means `selectedLogIndex` is now the sole state value for deciding if the log inspector is open or not
- Whenever the state is updated, find a syntax error if it's there, instead of doing this at render time

Changelog: [Internal]

Reviewed By: cpojer

Differential Revision: D18421089

fbshipit-source-id: d2c4937f666f1302ed1a7b1b9c6679b0509136c5
This commit is contained in:
Rick Hanlon authored and Facebook Github Bot committed 2019-11-11 16:12:43 -08:00
1 parent 54bbe1b6ea
commit fe82f402a9
16 files changed
+547 -876

No files matched your search

+44 -9
View File
@@ -27,7 +27,11 @@ export type LogData = $ReadOnly<{|
|}>;
export type Observer = (
$ReadOnly<{|logs: LogBoxLogs, isDisabled: boolean|}>,
$ReadOnly<{|
logs: LogBoxLogs,
isDisabled: boolean,
selectedLogIndex: number,
|}>,
) => void;
export type IgnorePattern = string | RegExp;
@@ -41,10 +45,33 @@ const ignorePatterns: Set<IgnorePattern> = new Set();
let logs: LogBoxLogs = new Set();
let updateTimeout = null;
let _isDisabled = false;
let _selectedIndex = -1;
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,
selectedLogIndex: _selectedIndex,
};
}
export function reportLogBoxError(
error: ExtendedError,
componentStack?: string,
@@ -79,9 +106,7 @@ function handleUpdate(): void {
if (updateTimeout == null) {
updateTimeout = setImmediate(() => {
updateTimeout = null;
observers.forEach(({observer}) =>
observer({logs, isDisabled: _isDisabled}),
);
observers.forEach(({observer}) => observer(getNextState()));
});
}
}
@@ -172,10 +197,12 @@ export function addException(error: ExceptionData): void {
);
// Start symbolicating now so it's warm when it renders.
logs.add(newLog);
if (level === 'fatal') {
_selectedIndex = logs.size - 1;
symbolicateLogLazy(newLog);
}
logs.add(newLog);
}
handleUpdate();
@@ -186,12 +213,18 @@ export function addException(error: ExceptionData): void {
}
export function clear(): void {
if (logs.size > 0) {
logs.clear();
const newLogs = Array.from(logs).filter(log => log.level === 'syntax');
if (newLogs.length !== logs.size) {
logs = new Set(newLogs);
handleUpdate();
}
}
export function setSelectedLog(index: number): void {
_selectedIndex = index;
handleUpdate();
}
export function clearWarnings(): void {
const newLogs = Array.from(logs).filter(log => log.level !== 'warn');
if (newLogs.length !== logs.size) {
@@ -201,7 +234,9 @@ export function clearWarnings(): void {
}
export function clearErrors(): void {
const newLogs = Array.from(logs).filter(log => log.level !== 'error');
const newLogs = Array.from(logs).filter(
log => log.level !== 'error' && log.level !== 'fatal',
);
if (newLogs.length !== logs.size) {
logs = new Set(newLogs);
handleUpdate();
@@ -276,7 +311,7 @@ export function observe(observer: Observer): Subscription {
const subscription = {observer};
observers.add(subscription);
observer({logs, isDisabled: _isDisabled});
observer(getNextState());
return {
unsubscribe(): void {
@@ -37,6 +37,12 @@ const disabledState = () => {
return observer.mock.calls[0][0].isDisabled;
};
const selectedLogIndex = () => {
const observer = jest.fn();
LogBoxData.observe(observer).unsubscribe();
return observer.mock.calls[0][0].selectedLogIndex;
};
const observe = () => {
const observer = jest.fn();
return {
@@ -59,7 +65,7 @@ const addLogs = logs => {
});
};
const addExceptions = errors => {
const addSoftErrors = errors => {
errors.forEach(error => {
LogBoxData.addException(
Object.assign(
@@ -81,8 +87,30 @@ const addExceptions = errors => {
});
};
const addFatalErrors = errors => {
errors.forEach(error => {
LogBoxData.addException(
Object.assign(
{},
{
message: '',
originalMessage: '',
name: 'console.error',
componentStack: '',
stack: [],
id: 0,
isFatal: true,
},
typeof error === 'string'
? {message: error, originalMessage: error}
: error,
),
);
});
};
const addSyntaxError = () => {
addExceptions([
addFatalErrors([
{
message: `
@@ -109,13 +137,19 @@ beforeEach(() => {
describe('LogBoxData', () => {
it('adds and dismisses logs', () => {
addLogs(['A']);
addExceptions(['B']);
addSoftErrors(['B']);
addFatalErrors(['C']);
addSyntaxError();
jest.runAllImmediates();
expect(registry().length).toBe(2);
expect(registry().length).toBe(4);
expect(registry()[0]).toBeDefined();
expect(registry()[1]).toBeDefined();
LogBoxData.dismiss(registry()[0]);
expect(registry().length).toBe(3);
LogBoxData.dismiss(registry()[0]);
expect(registry().length).toBe(2);
LogBoxData.dismiss(registry()[0]);
expect(registry().length).toBe(1);
LogBoxData.dismiss(registry()[0]);
@@ -123,20 +157,22 @@ describe('LogBoxData', () => {
expect(registry()[0]).toBeUndefined();
});
it('clears all logs', () => {
it('clears all logs except syntax errors', () => {
addLogs(['A', 'B']);
addExceptions(['C', 'D']);
addSoftErrors(['C', 'D']);
addFatalErrors(['E', 'F']);
addSyntaxError();
jest.runAllImmediates();
expect(registry().length).toBe(4);
expect(registry().length).toBe(7);
LogBoxData.clear();
expect(registry().length).toBe(0);
expect(registry().length).toBe(1);
});
it('clears only warnings', () => {
addLogs(['A', 'B']);
addExceptions(['C', 'D', 'E']);
addSoftErrors(['C', 'D', 'E']);
addSyntaxError();
jest.runAllImmediates();
@@ -146,13 +182,14 @@ describe('LogBoxData', () => {
expect(registry().length).toBe(4);
});
it('clears only errors', () => {
it('clears errors and fatals, but not syntax errors.', () => {
addLogs(['A', 'B']);
addExceptions(['C', 'D', 'E']);
addSoftErrors(['C', 'D', 'E']);
addFatalErrors(['F']);
addSyntaxError();
jest.runAllImmediates();
expect(registry().length).toBe(6);
expect(registry().length).toBe(7);
LogBoxData.clearErrors();
expect(registry().length).toBe(3);
@@ -160,23 +197,25 @@ describe('LogBoxData', () => {
it('clears only syntax errors', () => {
addLogs(['A', 'B']);
addExceptions(['C', 'D', 'E']);
addSoftErrors(['C', 'D', 'E']);
addFatalErrors(['F']);
addSyntaxError();
jest.runAllImmediates();
expect(registry().length).toBe(6);
expect(registry().length).toBe(7);
LogBoxData.clearSyntaxErrors();
expect(registry().length).toBe(5);
expect(registry().length).toBe(6);
});
it('clears all types', () => {
addLogs(['A', 'B']);
addExceptions(['C', 'D', 'E']);
addSoftErrors(['C', 'D', 'E']);
addFatalErrors(['F']);
addSyntaxError();
jest.runAllImmediates();
expect(registry().length).toBe(6);
expect(registry().length).toBe(7);
LogBoxData.clearErrors();
LogBoxData.clearWarnings();
@@ -186,26 +225,103 @@ describe('LogBoxData', () => {
it('keeps logs in chronological order', () => {
addLogs(['A']);
addExceptions(['B']);
addLogs(['C']);
addSoftErrors(['B']);
addFatalErrors(['C']);
addLogs(['D']);
jest.runAllImmediates();
let logs = registry();
expect(logs.length).toBe(3);
expect(logs.length).toBe(4);
expect(logs[0].category).toEqual('A');
expect(logs[1].category).toEqual('B');
expect(logs[2].category).toEqual('C');
expect(logs[3].category).toEqual('D');
addLogs(['A']);
jest.runAllImmediates();
// Expect `A` to be added to the end of the registry.
logs = registry();
expect(logs.length).toBe(4);
expect(logs.length).toBe(5);
expect(logs[0].category).toEqual('A');
expect(logs[1].category).toEqual('B');
expect(logs[2].category).toEqual('C');
expect(logs[3].category).toEqual('A');
expect(logs[3].category).toEqual('D');
expect(logs[4].category).toEqual('A');
});
it('sets the selectedLogIndex', () => {
expect(selectedLogIndex()).toBe(-1);
LogBoxData.setSelectedLog(1);
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(1);
});
it('does not set the selectedLogIndex for warnings', () => {
addLogs(['A']);
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(-1);
});
it('does not set the selectedLogIndex for soft errors', () => {
addSoftErrors(['A']);
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(-1);
});
it('sets the selectedLogIndex to the last fatal error', () => {
addFatalErrors(['A']);
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(0);
addLogs(['B']);
addFatalErrors(['C']);
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(2);
});
it('sets the selectedLogIndex to the last syntax error', () => {
addSyntaxError();
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(0);
addLogs(['B']);
addSyntaxError();
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(2);
});
it('keeps selectedLogIndex set to the syntax error even when a new fatal is added', () => {
addSyntaxError();
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(0);
addLogs(['B']);
addFatalErrors(['C']);
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(0);
});
it('keeps selectedLogIndex set to the syntax error even when explicitly changed', () => {
addSyntaxError();
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(0);
LogBoxData.setSelectedLog(1);
jest.runAllImmediates();
expect(selectedLogIndex()).toBe(0);
});
it('increments the count of previous log with matching category (logs)', () => {
@@ -231,8 +347,8 @@ describe('LogBoxData', () => {
expect(logs[1].count).toBe(2);
});
it('increments the count of previous log with matching category (exceptions)', () => {
addExceptions(['A', 'B']);
it('increments the count of previous log with matching category (errors)', () => {
addFatalErrors(['A', 'B']);
jest.runAllImmediates();
let logs = registry();
@@ -242,7 +358,7 @@ describe('LogBoxData', () => {
expect(logs[1].category).toEqual('B');
expect(logs[1].count).toBe(1);
addExceptions(['B']);
addSoftErrors(['B']);
jest.runAllImmediates();
// Expect `B` to be rolled into the last log.
@@ -283,8 +399,9 @@ describe('LogBoxData', () => {
expect(filteredRegistry().length).toBe(0);
});
it('ignores logs matching patterns (exceptions)', () => {
addExceptions(['A!', 'B?', 'C!']);
it('ignores logs matching patterns (errors)', () => {
addSoftErrors(['A!', 'B?']);
addFatalErrors(['C!']);
jest.runAllImmediates();
expect(filteredRegistry().length).toBe(3);
@@ -312,8 +429,9 @@ describe('LogBoxData', () => {
expect(filteredRegistry().length).toBe(0);
});
it('ignores matching regexs or pattern (exceptions)', () => {
addExceptions(['There are 4 dogs', 'There are 3 cats', 'There are H cats']);
it('ignores matching regexs or pattern (errors)', () => {
addSoftErrors(['There are 4 dogs', 'There are 3 cats']);
addFatalErrors(['There are H cats']);
jest.runAllImmediates();
expect(filteredRegistry().length).toBe(3);
@@ -329,8 +447,9 @@ describe('LogBoxData', () => {
});
it('ignores all logs except fatals when disabled', () => {
addLogs(['A!', 'B?']);
addExceptions(['C!']);
addLogs(['A!']);
addSoftErrors(['B?']);
addFatalErrors(['C!']);
addSyntaxError();
jest.runAllImmediates();
@@ -424,7 +543,9 @@ describe('LogBoxData', () => {
expect(observer.mock.calls.length).toBe(1);
addLogs(['A']);
addExceptions(['B']);
addSoftErrors(['B']);
addFatalErrors(['C']);
addSyntaxError();
jest.runAllImmediates();
expect(observer.mock.calls.length).toBe(2);
@@ -443,7 +564,9 @@ describe('LogBoxData', () => {
expect(observer.mock.calls.length).toBe(1);
addLogs(['A']);
addExceptions(['B']);
addSoftErrors(['B']);
addFatalErrors(['C']);
addSyntaxError();
jest.runAllImmediates();
expect(observer.mock.calls.length).toBe(2);
@@ -462,6 +585,8 @@ describe('LogBoxData', () => {
expect(observer.mock.calls.length).toBe(1);
addLogs(['A']);
addSoftErrors(['B']);
addFatalErrors(['C']);
addSyntaxError();
jest.runAllImmediates();
expect(observer.mock.calls.length).toBe(2);
+8
View File
@@ -26,6 +26,7 @@ type State = {|
logs: LogBoxLogs,
isDisabled: boolean,
hasError: boolean,
selectedLogIndex: number,
|};
let LogBoxComponent;
@@ -109,6 +110,7 @@ if (__DEV__) {
logs: new Set(),
isDisabled: false,
hasError: false,
selectedLogIndex: -1,
};
render(): React.Node {
@@ -125,6 +127,8 @@ if (__DEV__) {
onDismissErrors={LogBoxData.clearErrors}
logs={this.state.logs}
isDisabled={this.state.isDisabled}
selectedLogIndex={this.state.selectedLogIndex}
setSelectedLog={this._handleSetSelectedLog}
/>
);
}
@@ -144,6 +148,10 @@ if (__DEV__) {
_handleDismiss(log: LogBoxLog): void {
LogBoxData.dismiss(log);
}
_handleSetSelectedLog(index: number): void {
LogBoxData.setSelectedLog(index);
}
};
const registerWarning = (...args): void => {
+9 -36
View File
@@ -23,12 +23,14 @@ type Props = $ReadOnly<{|
onDismiss: (log: LogBoxLog) => void,
onDismissWarns: () => void,
onDismissErrors: () => void,
setSelectedLog: number => void,
logs: LogBoxLogs,
selectedLogIndex: number,
isDisabled?: ?boolean,
|}>;
function LogBoxContainer(props: Props): React.Node {
const [selectedLogIndex, setSelectedLog] = React.useState(null);
const {selectedLogIndex, setSelectedLog} = props;
const logs = Array.from(props.logs);
@@ -38,7 +40,7 @@ function LogBoxContainer(props: Props): React.Node {
// is now outside the bounds of the log array.
if (selectedLogIndex != null) {
if (logs.length - 1 <= 0) {
setSelectedLog(null);
setSelectedLog(-1);
} else if (selectedLogIndex >= logs.length - 1) {
setSelectedLog(selectedLogIndex - 1);
}
@@ -47,7 +49,7 @@ function LogBoxContainer(props: Props): React.Node {
}
function handleInspectorMinimize() {
setSelectedLog(null);
setSelectedLog(-1);
}
function openLog(log: LogBoxLog) {
@@ -60,16 +62,7 @@ function LogBoxContainer(props: Props): React.Node {
setSelectedLog(index);
}
let fatalIndex = logs.length - 1;
while (fatalIndex >= 0) {
const level = logs[fatalIndex].level;
if (level === 'fatal' || level === 'syntax') {
break;
}
fatalIndex -= 1;
}
if (selectedLogIndex == null && fatalIndex >= 0) {
if (selectedLogIndex > -1) {
return (
<View style={StyleSheet.absoluteFill}>
<LogBoxInspector
@@ -77,22 +70,6 @@ function LogBoxContainer(props: Props): React.Node {
onMinimize={handleInspectorMinimize}
onChangeSelectedIndex={setSelectedLog}
logs={logs}
fatalType={logs[fatalIndex].level}
selectedIndex={fatalIndex}
/>
</View>
);
}
if (selectedLogIndex != null) {
return (
<View style={StyleSheet.absoluteFill}>
<LogBoxInspector
onDismiss={handleInspectorDismiss}
onMinimize={handleInspectorMinimize}
onChangeSelectedIndex={setSelectedLog}
logs={logs}
fatalType={fatalIndex >= 0 ? logs[fatalIndex].level : null}
selectedIndex={selectedLogIndex}
/>
</View>
@@ -104,7 +81,9 @@ function LogBoxContainer(props: Props): React.Node {
}
const warnings = logs.filter(log => log.level === 'warn');
const errors = logs.filter(log => log.level === 'error');
const errors = logs.filter(
log => log.level === 'error' || log.level === 'fatal',
);
return (
<View style={styles.list}>
{warnings.length > 0 && (
@@ -114,9 +93,6 @@ function LogBoxContainer(props: Props): React.Node {
level="warn"
totalLogCount={warnings.length}
onPressOpen={() => openLog(warnings[warnings.length - 1])}
onPressList={() => {
/* TODO: open log list */
}}
onPressDismiss={props.onDismissWarns}
/>
</View>
@@ -128,9 +104,6 @@ function LogBoxContainer(props: Props): React.Node {
level="error"
totalLogCount={errors.length}
onPressOpen={() => openLog(errors[errors.length - 1])}
onPressList={() => {
/* TODO: open log list */
}}
onPressDismiss={props.onDismissErrors}
/>
</View>
+1 -1
View File
@@ -79,7 +79,7 @@ function LogBoxInspector(props: Props): React.Node {
<LogBoxInspectorFooter
onDismiss={props.onDismiss}
onMinimize={props.onMinimize}
fatalType={props.fatalType}
level={log.level}
/>
</View>
);
+15 -51
View File
@@ -23,31 +23,16 @@ import * as LogBoxStyle from './LogBoxStyle';
type Props = $ReadOnly<{|
onDismiss: () => void,
onMinimize: () => void,
fatalType?: ?LogLevel,
level?: ?LogLevel,
|}>;
function LogBoxInspectorFooter(props: Props): React.Node {
if (props.fatalType === 'fatal' || props.fatalType === 'syntax') {
if (props.level === 'syntax') {
return (
<View style={[styles.root, fatalStyles.root]}>
<LogBoxButton
backgroundColor={{
default: LogBoxStyle.getFatalColor(),
pressed: LogBoxStyle.getFatalDarkColor(),
}}
onPress={() =>
require('../../Utilities/DevSettings').reload('LogBox fatal')
}
style={fatalStyles.button}>
<View style={[fatalStyles.content]}>
<Text style={fatalStyles.label}>Reload</Text>
<Text style={fatalStyles.subtextLabel}>
{{fatal: 'Fatal', syntax: 'Syntax'}[props.fatalType]} errors
require a full reload
</Text>
</View>
<SafeAreaView />
</LogBoxButton>
<View style={styles.root}>
<Text style={styles.syntaxErrorText}>
This error cannot be dismissed.
</Text>
</View>
);
}
@@ -82,36 +67,6 @@ function FooterButton(props: ButtonProps): React.Node {
);
}
const fatalStyles = StyleSheet.create({
root: {
shadowColor: LogBoxStyle.getBackgroundDarkColor(1),
},
button: {
flex: 1,
},
content: {
alignItems: 'center',
height: 60,
justifyContent: 'center',
},
subtext: {
height: 60,
},
label: {
color: LogBoxStyle.getTextColor(1),
fontSize: 14,
fontWeight: '600',
includeFontPadding: false,
lineHeight: 20,
},
subtextLabel: {
color: LogBoxStyle.getTextColor(0.8),
fontSize: 11,
includeFontPadding: false,
lineHeight: 12,
},
});
const buttonStyles = StyleSheet.create({
button: {
flex: 1,
@@ -139,6 +94,15 @@ const styles = StyleSheet.create({
elevation: 1,
flexDirection: 'row',
},
syntaxErrorText: {
textAlign: 'center',
width: '100%',
fontSize: 14,
paddingTop: 15,
paddingBottom: 15,
fontStyle: 'italic',
color: LogBoxStyle.getTextColor(0.6),
},
});
export default LogBoxInspectorFooter;
@@ -29,6 +29,18 @@ type Props = $ReadOnly<{|
|}>;
function LogBoxInspectorHeader(props: Props): React.Node {
if (props.level === 'syntax') {
return (
<SafeAreaView style={styles[props.level]}>
<View style={styles.header}>
<View style={styles.title}>
<Text style={styles.titleText}>Failed to compile</Text>
</View>
</View>
</SafeAreaView>
);
}
const prevIndex =
props.selectedIndex - 1 < 0 ? props.total - 1 : props.selectedIndex - 1;
const nextIndex =
@@ -27,7 +27,6 @@ type Props = $ReadOnly<{|
totalLogCount: number,
level: 'warn' | 'error',
onPressOpen: () => void,
onPressList: () => void,
onPressDismiss: () => void,
|}>;
@@ -23,6 +23,8 @@ describe('LogBoxContainer', () => {
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
setSelectedLog={() => {}}
selectedLogIndex={-1}
logs={new Set()}
/>,
);
@@ -30,121 +32,119 @@ describe('LogBoxContainer', () => {
expect(output).toMatchSnapshot();
});
it('should render the latest warning', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
logs={
new Set([
new LogBoxLog(
'warn',
{
content: 'Some kind of message',
substitutions: [],
},
[],
'Some kind of message',
[],
),
new LogBoxLog(
'warn',
{
content: 'Some kind of message (latest)',
substitutions: [],
},
[],
'Some kind of message (latest)',
[],
),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render the latest error', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
logs={
new Set([
new LogBoxLog(
'error',
{
content: 'Some kind of message',
substitutions: [],
},
[],
'Some kind of message',
[],
),
new LogBoxLog(
'error',
{
content: 'Some kind of message (latest)',
substitutions: [],
},
[],
'Some kind of message (latest)',
[],
),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render both an error and warning', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
logs={
new Set([
new LogBoxLog(
'warn',
{
content: 'Some kind of message',
substitutions: [],
},
[],
'Some kind of message',
[],
),
new LogBoxLog(
'error',
{
content: 'Some kind of message (latest)',
substitutions: [],
},
[],
'Some kind of message (latest)',
[],
),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render null when disabled with non-fatal error and warning', () => {
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(
'warn',
{
content: 'Some kind of message',
substitutions: [],
},
[],
'Some kind of message',
[],
),
])
}
/>,
);
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(
'warn',
{
content: 'Some kind of message',
substitutions: [],
},
[],
'Some kind of message',
[],
),
new LogBoxLog(
'warn',
{
content: 'Some kind of message (latest)',
substitutions: [],
},
[],
'Some kind of message (latest)',
[],
),
])
}
/>,
);
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(
'error',
{
content: 'Some kind of message',
substitutions: [],
},
[],
'Some kind of message',
[],
),
new LogBoxLog(
'error',
{
content: 'Some kind of message (latest)',
substitutions: [],
},
[],
'Some kind of message (latest)',
[],
),
])
}
/>,
);
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(
@@ -175,95 +175,15 @@ describe('LogBoxContainer', () => {
expect(output).toMatchSnapshot();
});
it('should render fatal before warn and error', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
logs={
new Set([
new LogBoxLog(
'fatal',
{
content: 'Some kind of fatal message',
substitutions: [],
},
[],
'Some kind of fatal message',
[],
),
new LogBoxLog(
'warn',
{
content: 'Some kind of message',
substitutions: [],
},
[],
'Some kind of message',
[],
),
new LogBoxLog(
'error',
{
content: 'Some kind of message (latest)',
substitutions: [],
},
[],
'Some kind of message (latest)',
[],
),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render most recent fatal', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
logs={
new Set([
new LogBoxLog(
'fatal',
{
content: 'Should not be selected',
substitutions: [],
},
[],
'Some kind of fatal message',
[],
),
new LogBoxLog(
'fatal',
{
content: 'Should be selected',
substitutions: [],
},
[],
'Some kind of message',
[],
),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render fatal error even when disabled', () => {
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(
@@ -284,69 +204,15 @@ describe('LogBoxContainer', () => {
expect(output).toMatchSnapshot();
});
it('should render most recent syntax error', () => {
const output = render.shallowRender(
<LogBoxContainer
onDismiss={() => {}}
onDismissWarns={() => {}}
onDismissErrors={() => {}}
logs={
new Set([
new LogBoxLog(
'syntax',
{
content: 'Should not be selected',
substitutions: [],
},
[],
'Some kind of syntax error message',
[],
{
fileName:
'/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
location: {row: 199, column: 0},
content: ` 197 | });
198 |
> 199 | export default CrashReactApp;
| ^
200 |`,
},
),
new LogBoxLog(
'syntax',
{
content: 'Should be selected',
substitutions: [],
},
[],
'Some kind of syntax error message',
[],
{
fileName:
'/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
location: {row: 199, column: 0},
content: ` 197 | });
198 |
> 199 | export default CrashReactApp;
| ^
200 |`,
},
),
])
}
/>,
);
expect(output).toMatchSnapshot();
});
it('should render syntax error even when disabled', () => {
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(
@@ -16,44 +16,48 @@ const LogBoxInspectorFooter = require('../LogBoxInspectorFooter').default;
const render = require('../../../../jest/renderer');
describe('LogBoxInspectorFooter', () => {
it('should render two buttons for warning with no fatal', () => {
const output = render.shallowRender(
<LogBoxInspectorFooter onMinimize={() => {}} onDismiss={() => {}} />,
);
expect(output).toMatchSnapshot();
});
it('should render fatal for warning with a fatal', () => {
it('should render two buttons for warning', () => {
const output = render.shallowRender(
<LogBoxInspectorFooter
onMinimize={() => {}}
onDismiss={() => {}}
fatalType="syntax"
level="warn"
/>,
);
expect(output).toMatchSnapshot();
});
it('should render fatal button', () => {
it('should render two buttons for error', () => {
const output = render.shallowRender(
<LogBoxInspectorFooter
onMinimize={() => {}}
onDismiss={() => {}}
fatalType="fatal"
level="error"
/>,
);
expect(output).toMatchSnapshot();
});
it('should render fatal button for syntax erorr', () => {
it('should render two buttons for fatal', () => {
const output = render.shallowRender(
<LogBoxInspectorFooter
onMinimize={() => {}}
onDismiss={() => {}}
fatalType="syntax"
level="fatal"
/>,
);
expect(output).toMatchSnapshot();
});
it('should render no buttons and a message for syntax error', () => {
const output = render.shallowRender(
<LogBoxInspectorFooter
onMinimize={() => {}}
onDismiss={() => {}}
level="syntax"
/>,
);
@@ -54,4 +54,17 @@ describe('LogBoxInspectorHeader', () => {
expect(output).toMatchSnapshot();
});
it('should render syntax error header', () => {
const output = render.shallowRender(
<LogBoxInspectorHeader
onSelectIndex={() => {}}
selectedIndex={0}
total={1}
level="syntax"
/>,
);
expect(output).toMatchSnapshot();
});
});
@@ -35,7 +35,6 @@ describe('LogBoxLogNotification', () => {
totalLogCount={1}
level="warn"
onPressOpen={() => {}}
onPressList={() => {}}
onPressDismiss={() => {}}
/>,
);
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxContainer should render both an error and warning 1`] = `
exports[`LogBoxContainer should render both an error and warning notification 1`] = `
<View
style={
Object {
@@ -42,7 +42,6 @@ exports[`LogBoxContainer should render both an error and warning 1`] = `
}
}
onPressDismiss={[Function]}
onPressList={[Function]}
onPressOpen={[Function]}
totalLogCount={1}
/>
@@ -78,7 +77,6 @@ exports[`LogBoxContainer should render both an error and warning 1`] = `
}
}
onPressDismiss={[Function]}
onPressList={[Function]}
onPressOpen={[Function]}
totalLogCount={1}
/>
@@ -93,273 +91,53 @@ exports[`LogBoxContainer should render both an error and warning 1`] = `
</View>
`;
exports[`LogBoxContainer should render fatal before warn and error 1`] = `
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<LogBoxInspector
fatalType="fatal"
logs={
Array [
LogBoxLog {
"category": "Some kind of fatal message",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"level": "fatal",
"message": Object {
"content": "Some kind of fatal message",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
LogBoxLog {
"category": "Some kind of message",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"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,
"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={0}
/>
</View>
`;
exports[`LogBoxContainer should render fatal error even when disabled 1`] = `
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<LogBoxInspector
fatalType="fatal"
logs={
Array [
LogBoxLog {
"category": "Some kind of message",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"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[`LogBoxContainer should render most recent fatal 1`] = `
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<LogBoxInspector
fatalType="fatal"
logs={
Array [
LogBoxLog {
"category": "Some kind of fatal message",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"level": "fatal",
"message": Object {
"content": "Should not be selected",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
LogBoxLog {
"category": "Some kind of message",
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"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={1}
/>
</View>
`;
exports[`LogBoxContainer should render most recent syntax error 1`] = `
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<LogBoxInspector
fatalType="syntax"
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,
"level": "syntax",
"message": Object {
"content": "Should not be selected",
"substitutions": Array [],
},
"stack": Array [],
"symbolicated": Object {
"error": null,
"stack": null,
"status": "NONE",
},
},
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,
"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={1}
/>
</View>
`;
exports[`LogBoxContainer should render null when disabled with non-fatal error and warning 1`] = `null`;
exports[`LogBoxContainer should render null with no logs 1`] = `null`;
exports[`LogBoxContainer should render syntax error even when disabled 1`] = `
exports[`LogBoxContainer 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,
"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[`LogBoxContainer should render selected syntax error even when disabled 1`] = `
<View
style={
Object {
@@ -372,7 +150,6 @@ exports[`LogBoxContainer should render syntax error even when disabled 1`] = `
}
>
<LogBoxInspector
fatalType="syntax"
logs={
Array [
LogBoxLog {
@@ -413,7 +190,7 @@ exports[`LogBoxContainer should render syntax error even when disabled 1`] = `
</View>
`;
exports[`LogBoxContainer should render the latest error 1`] = `
exports[`LogBoxContainer should render the latest error notification 1`] = `
<View
style={
Object {
@@ -455,7 +232,6 @@ exports[`LogBoxContainer should render the latest error 1`] = `
}
}
onPressDismiss={[Function]}
onPressList={[Function]}
onPressOpen={[Function]}
totalLogCount={2}
/>
@@ -470,7 +246,7 @@ exports[`LogBoxContainer should render the latest error 1`] = `
</View>
`;
exports[`LogBoxContainer should render the latest warning 1`] = `
exports[`LogBoxContainer should render the latest warning notification 1`] = `
<View
style={
Object {
@@ -512,7 +288,6 @@ exports[`LogBoxContainer should render the latest warning 1`] = `
}
}
onPressDismiss={[Function]}
onPressList={[Function]}
onPressOpen={[Function]}
totalLogCount={2}
/>
@@ -39,7 +39,7 @@ exports[`LogBoxContainer should render fatal with selectedIndex 2 1`] = `
onRetry={[Function]}
/>
<LogBoxInspectorFooter
fatalType="fatal"
level="fatal"
onDismiss={[Function]}
onMinimize={[Function]}
/>
@@ -87,6 +87,7 @@ exports[`LogBoxContainer should render warning with selectedIndex 0 1`] = `
onRetry={[Function]}
/>
<LogBoxInspectorFooter
level="warn"
onDismiss={[Function]}
onMinimize={[Function]}
/>
@@ -1,243 +1,97 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxInspectorFooter should render fatal button 1`] = `
exports[`LogBoxInspectorFooter should render no buttons and a message for syntax error 1`] = `
<View
style={
Array [
Object {
"backgroundColor": "rgba(51, 51, 51, 1)",
"elevation": 1,
"flexDirection": "row",
"shadowColor": "#000",
"shadowOffset": Object {
"height": -2,
"width": 0,
},
"shadowOpacity": 0.5,
"shadowRadius": 2,
Object {
"backgroundColor": "rgba(51, 51, 51, 1)",
"elevation": 1,
"flexDirection": "row",
"shadowColor": "#000",
"shadowOffset": Object {
"height": -2,
"width": 0,
},
Object {
"shadowColor": "rgba(34, 34, 34, 1)",
},
]
"shadowOpacity": 0.5,
"shadowRadius": 2,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "rgba(243, 83, 105, 1)",
"pressed": "rgba(208, 75, 95, 1)",
}
}
onPress={[Function]}
<Text
style={
Object {
"flex": 1,
"color": "rgba(255, 255, 255, 0.6)",
"fontSize": 14,
"fontStyle": "italic",
"paddingBottom": 15,
"paddingTop": 15,
"textAlign": "center",
"width": "100%",
}
}
>
<View
style={
Array [
Object {
"alignItems": "center",
"height": 60,
"justifyContent": "center",
},
]
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontSize": 14,
"fontWeight": "600",
"includeFontPadding": false,
"lineHeight": 20,
}
}
>
Reload
</Text>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.8)",
"fontSize": 11,
"includeFontPadding": false,
"lineHeight": 12,
}
}
>
Fatal
errors require a full reload
</Text>
</View>
<ForwardRef(SafeAreaView) />
</LogBoxButton>
This error cannot be dismissed.
</Text>
</View>
`;
exports[`LogBoxInspectorFooter should render fatal button for syntax erorr 1`] = `
exports[`LogBoxInspectorFooter should render two buttons for error 1`] = `
<View
style={
Array [
Object {
"backgroundColor": "rgba(51, 51, 51, 1)",
"elevation": 1,
"flexDirection": "row",
"shadowColor": "#000",
"shadowOffset": Object {
"height": -2,
"width": 0,
},
"shadowOpacity": 0.5,
"shadowRadius": 2,
Object {
"backgroundColor": "rgba(51, 51, 51, 1)",
"elevation": 1,
"flexDirection": "row",
"shadowColor": "#000",
"shadowOffset": Object {
"height": -2,
"width": 0,
},
Object {
"shadowColor": "rgba(34, 34, 34, 1)",
},
]
"shadowOpacity": 0.5,
"shadowRadius": 2,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "rgba(243, 83, 105, 1)",
"pressed": "rgba(208, 75, 95, 1)",
}
}
<FooterButton
onPress={[Function]}
text="Dismiss"
/>
<FooterButton
onPress={[Function]}
style={
Object {
"flex": 1,
}
}
>
<View
style={
Array [
Object {
"alignItems": "center",
"height": 60,
"justifyContent": "center",
},
]
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontSize": 14,
"fontWeight": "600",
"includeFontPadding": false,
"lineHeight": 20,
}
}
>
Reload
</Text>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.8)",
"fontSize": 11,
"includeFontPadding": false,
"lineHeight": 12,
}
}
>
Syntax
errors require a full reload
</Text>
</View>
<ForwardRef(SafeAreaView) />
</LogBoxButton>
text="Close"
/>
</View>
`;
exports[`LogBoxInspectorFooter should render fatal for warning with a fatal 1`] = `
exports[`LogBoxInspectorFooter should render two buttons for fatal 1`] = `
<View
style={
Array [
Object {
"backgroundColor": "rgba(51, 51, 51, 1)",
"elevation": 1,
"flexDirection": "row",
"shadowColor": "#000",
"shadowOffset": Object {
"height": -2,
"width": 0,
},
"shadowOpacity": 0.5,
"shadowRadius": 2,
Object {
"backgroundColor": "rgba(51, 51, 51, 1)",
"elevation": 1,
"flexDirection": "row",
"shadowColor": "#000",
"shadowOffset": Object {
"height": -2,
"width": 0,
},
Object {
"shadowColor": "rgba(34, 34, 34, 1)",
},
]
"shadowOpacity": 0.5,
"shadowRadius": 2,
}
}
>
<LogBoxButton
backgroundColor={
Object {
"default": "rgba(243, 83, 105, 1)",
"pressed": "rgba(208, 75, 95, 1)",
}
}
<FooterButton
onPress={[Function]}
text="Dismiss"
/>
<FooterButton
onPress={[Function]}
style={
Object {
"flex": 1,
}
}
>
<View
style={
Array [
Object {
"alignItems": "center",
"height": 60,
"justifyContent": "center",
},
]
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontSize": 14,
"fontWeight": "600",
"includeFontPadding": false,
"lineHeight": 20,
}
}
>
Reload
</Text>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.8)",
"fontSize": 11,
"includeFontPadding": false,
"lineHeight": 12,
}
}
>
Syntax
errors require a full reload
</Text>
</View>
<ForwardRef(SafeAreaView) />
</LogBoxButton>
text="Close"
/>
</View>
`;
exports[`LogBoxInspectorFooter should render two buttons for warning with no fatal 1`] = `
exports[`LogBoxInspectorFooter should render two buttons for warning 1`] = `
<View
style={
Object {
@@ -110,6 +110,49 @@ exports[`LogBoxInspectorHeader should render no buttons for one total 1`] = `
</ForwardRef(SafeAreaView)>
`;
exports[`LogBoxInspectorHeader should render syntax error header 1`] = `
<ForwardRef(SafeAreaView)
style={
Object {
"backgroundColor": "rgba(243, 83, 105, 1)",
}
}
>
<View
style={
Object {
"flexDirection": "row",
"height": 44,
}
}
>
<View
style={
Object {
"alignItems": "center",
"flex": 1,
"justifyContent": "center",
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontSize": 16,
"fontWeight": "600",
"includeFontPadding": false,
"lineHeight": 20,
}
}
>
Failed to compile
</Text>
</View>
</View>
</ForwardRef(SafeAreaView)>
`;
exports[`LogBoxInspectorHeader should render two buttons for three or more total 1`] = `
<ForwardRef(SafeAreaView)
style={