2016-05-16 14:13:22 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*
|
|
|
|
* @providesModule dumpReactTree
|
|
|
|
* @flow
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
2016-11-04 12:40:26 +00:00
|
|
|
const ReactNativeMount = require('ReactNativeMount');
|
2016-05-16 14:13:22 +00:00
|
|
|
const getReactData = require('getReactData');
|
|
|
|
|
|
|
|
const INDENTATION_SIZE = 2;
|
|
|
|
const MAX_DEPTH = 2;
|
|
|
|
const MAX_STRING_LENGTH = 50;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Dump all React Native root views and their content. This function tries
|
|
|
|
* it best to get the content but ultimately relies on implementation details
|
|
|
|
* of React and will fail in future versions.
|
|
|
|
*/
|
|
|
|
function dumpReactTree() {
|
|
|
|
try {
|
|
|
|
return getReactTree();
|
|
|
|
} catch (e) {
|
|
|
|
return 'Failed to dump react tree: ' + e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getReactTree() {
|
|
|
|
let output = '';
|
|
|
|
const rootIds = Object.getOwnPropertyNames(ReactNativeMount._instancesByContainerID);
|
|
|
|
for (const rootId of rootIds) {
|
|
|
|
const instance = ReactNativeMount._instancesByContainerID[rootId];
|
|
|
|
output += `============ Root ID: ${rootId} ============\n`;
|
|
|
|
output += dumpNode(instance, 0);
|
|
|
|
output += `============ End root ID: ${rootId} ============\n`;
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
function dumpNode(node: Object, identation: number) {
|
|
|
|
const data = getReactData(node);
|
2016-05-17 11:59:37 +00:00
|
|
|
if (data.nodeType === 'Text') {
|
2016-05-16 14:13:22 +00:00
|
|
|
return indent(identation) + data.text + '\n';
|
2016-05-17 11:59:37 +00:00
|
|
|
} else if (data.nodeType === 'Empty') {
|
2016-05-16 14:13:22 +00:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
let output = indent(identation) + `<${data.name}`;
|
2016-05-17 11:59:37 +00:00
|
|
|
if (data.nodeType === 'Composite') {
|
2016-05-16 14:13:22 +00:00
|
|
|
for (const propName of Object.getOwnPropertyNames(data.props || {})) {
|
|
|
|
if (isNormalProp(propName)) {
|
2016-05-17 11:59:37 +00:00
|
|
|
try {
|
|
|
|
const value = convertValue(data.props[propName]);
|
|
|
|
if (value) {
|
|
|
|
output += ` ${propName}=${value}`;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
const message = `[Failed to get property: ${e}]`;
|
|
|
|
output += ` ${propName}=${message}`;
|
2016-05-16 14:13:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let childOutput = '';
|
|
|
|
for (const child of data.children || []) {
|
|
|
|
childOutput += dumpNode(child, identation + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (childOutput) {
|
2016-05-17 11:59:37 +00:00
|
|
|
output += '>\n' + childOutput + indent(identation) + `</${data.name}>\n`;
|
2016-05-16 14:13:22 +00:00
|
|
|
} else {
|
|
|
|
output += ' />\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isNormalProp(name: string): boolean {
|
|
|
|
switch (name) {
|
|
|
|
case 'children':
|
|
|
|
case 'key':
|
|
|
|
case 'ref':
|
|
|
|
return false;
|
|
|
|
default:
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function convertObject(object: Object, depth: number) {
|
|
|
|
if (depth >= MAX_DEPTH) {
|
|
|
|
return '[...omitted]';
|
|
|
|
}
|
2016-05-17 11:59:37 +00:00
|
|
|
let output = '{';
|
2016-05-16 14:13:22 +00:00
|
|
|
let first = true;
|
|
|
|
for (const key of Object.getOwnPropertyNames(object)) {
|
|
|
|
if (!first) {
|
2016-05-17 11:59:37 +00:00
|
|
|
output += ', ';
|
2016-05-16 14:13:22 +00:00
|
|
|
}
|
2016-07-06 19:49:08 +00:00
|
|
|
// $FlowFixMe(>=0.28.0)
|
2016-05-16 14:13:22 +00:00
|
|
|
output += `${key}: ${convertValue(object[key], depth + 1)}`;
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
return output + '}';
|
|
|
|
}
|
|
|
|
|
|
|
|
function convertValue(value, depth = 0): ?string {
|
|
|
|
if (!value) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (typeof value) {
|
|
|
|
case 'string':
|
|
|
|
return JSON.stringify(possiblyEllipsis(value).replace('\n', '\\n'));
|
|
|
|
case 'boolean':
|
|
|
|
case 'number':
|
|
|
|
return JSON.stringify(value);
|
|
|
|
case 'function':
|
|
|
|
return '[function]';
|
|
|
|
case 'object':
|
|
|
|
return convertObject(value, depth);
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function possiblyEllipsis(value: string) {
|
|
|
|
if (value.length > MAX_STRING_LENGTH) {
|
|
|
|
return value.slice(0, MAX_STRING_LENGTH) + '...';
|
|
|
|
} else {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function indent(size: number) {
|
|
|
|
return ' '.repeat(size * INDENTATION_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = dumpReactTree;
|