Merge inbound to mozilla-central. a=merge

This commit is contained in:
Csoregi Natalia 2019-01-07 23:46:59 +02:00
Родитель 4ea67edf08 6258bc84e7
Коммит 498c659ff8
621 изменённых файлов: 100102 добавлений и 412 удалений

Просмотреть файл

@ -1,5 +1,5 @@
This is the PDF.js project output, https://github.com/mozilla/pdf.js
Current extension version is: 2.1.153
Current extension version is: 2.1.176
Taken from upstream commit: 5a2bd9fc
Taken from upstream commit: e4d2a160

Просмотреть файл

@ -42,6 +42,7 @@ var PdfJsDefaultPreferences = Object.freeze({
"disableOpenActionDestination": true,
"disablePageMode": false,
"disablePageLabels": false,
"historyUpdateUrl": false,
"scrollModeOnLoad": 0,
"spreadModeOnLoad": 0
});

Просмотреть файл

@ -123,8 +123,8 @@ return /******/ (function(modules) { // webpackBootstrap
"use strict";
var pdfjsVersion = '2.1.153';
var pdfjsBuild = '5a2bd9fc';
var pdfjsVersion = '2.1.176';
var pdfjsBuild = 'e4d2a160';
var pdfjsSharedUtil = __w_pdfjs_require__(1);
@ -5154,7 +5154,7 @@ function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
return worker.messageHandler.sendWithPromise('GetDocRequest', {
docId,
apiVersion: '2.1.153',
apiVersion: '2.1.176',
source: {
data: source.data,
url: source.url,
@ -6885,9 +6885,9 @@ const InternalRenderTask = function InternalRenderTaskClosure() {
return InternalRenderTask;
}();
const version = '2.1.153';
const version = '2.1.176';
exports.version = version;
const build = '5a2bd9fc';
const build = 'e4d2a160';
exports.build = build;
/***/ }),
@ -10699,7 +10699,7 @@ function isWhitespaceString(s) {
class XMLParserBase {
_resolveEntities(s) {
return s.replace(/&([^;]+);/g, function (all, entity) {
return s.replace(/&([^;]+);/g, (all, entity) => {
if (entity.substring(0, 2) === '#x') {
return String.fromCharCode(parseInt(entity.substring(2), 16));
} else if (entity.substring(0, 1) === '#') {
@ -10989,6 +10989,11 @@ class SimpleDOMNode {
}
const index = childNodes.indexOf(this);
if (index === -1) {
return undefined;
}
return childNodes[index + 1];
}
@ -11078,9 +11083,13 @@ class SimpleXMLParser extends XMLParserBase {
}
onEndElement(name) {
this._currentFragment = this._stack.pop();
this._currentFragment = this._stack.pop() || [];
const lastElement = this._currentFragment[this._currentFragment.length - 1];
if (!lastElement) {
return;
}
for (let i = 0, ii = lastElement.childNodes.length; i < ii; i++) {
lastElement.childNodes[i].parentNode = lastElement;
}

Просмотреть файл

@ -123,8 +123,8 @@ return /******/ (function(modules) { // webpackBootstrap
"use strict";
var pdfjsVersion = '2.1.153';
var pdfjsBuild = '5a2bd9fc';
var pdfjsVersion = '2.1.176';
var pdfjsBuild = 'e4d2a160';
var pdfjsCoreWorker = __w_pdfjs_require__(1);
@ -375,7 +375,7 @@ var WorkerMessageHandler = {
var cancelXHRs = null;
var WorkerTasks = [];
let apiVersion = docParams.apiVersion;
let workerVersion = '2.1.153';
let workerVersion = '2.1.176';
if (apiVersion !== workerVersion) {
throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
@ -14942,246 +14942,245 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ArithmeticDecoder = void 0;
const QeTable = [{
qe: 0x5601,
nmps: 1,
nlps: 1,
switchFlag: 1
}, {
qe: 0x3401,
nmps: 2,
nlps: 6,
switchFlag: 0
}, {
qe: 0x1801,
nmps: 3,
nlps: 9,
switchFlag: 0
}, {
qe: 0x0AC1,
nmps: 4,
nlps: 12,
switchFlag: 0
}, {
qe: 0x0521,
nmps: 5,
nlps: 29,
switchFlag: 0
}, {
qe: 0x0221,
nmps: 38,
nlps: 33,
switchFlag: 0
}, {
qe: 0x5601,
nmps: 7,
nlps: 6,
switchFlag: 1
}, {
qe: 0x5401,
nmps: 8,
nlps: 14,
switchFlag: 0
}, {
qe: 0x4801,
nmps: 9,
nlps: 14,
switchFlag: 0
}, {
qe: 0x3801,
nmps: 10,
nlps: 14,
switchFlag: 0
}, {
qe: 0x3001,
nmps: 11,
nlps: 17,
switchFlag: 0
}, {
qe: 0x2401,
nmps: 12,
nlps: 18,
switchFlag: 0
}, {
qe: 0x1C01,
nmps: 13,
nlps: 20,
switchFlag: 0
}, {
qe: 0x1601,
nmps: 29,
nlps: 21,
switchFlag: 0
}, {
qe: 0x5601,
nmps: 15,
nlps: 14,
switchFlag: 1
}, {
qe: 0x5401,
nmps: 16,
nlps: 14,
switchFlag: 0
}, {
qe: 0x5101,
nmps: 17,
nlps: 15,
switchFlag: 0
}, {
qe: 0x4801,
nmps: 18,
nlps: 16,
switchFlag: 0
}, {
qe: 0x3801,
nmps: 19,
nlps: 17,
switchFlag: 0
}, {
qe: 0x3401,
nmps: 20,
nlps: 18,
switchFlag: 0
}, {
qe: 0x3001,
nmps: 21,
nlps: 19,
switchFlag: 0
}, {
qe: 0x2801,
nmps: 22,
nlps: 19,
switchFlag: 0
}, {
qe: 0x2401,
nmps: 23,
nlps: 20,
switchFlag: 0
}, {
qe: 0x2201,
nmps: 24,
nlps: 21,
switchFlag: 0
}, {
qe: 0x1C01,
nmps: 25,
nlps: 22,
switchFlag: 0
}, {
qe: 0x1801,
nmps: 26,
nlps: 23,
switchFlag: 0
}, {
qe: 0x1601,
nmps: 27,
nlps: 24,
switchFlag: 0
}, {
qe: 0x1401,
nmps: 28,
nlps: 25,
switchFlag: 0
}, {
qe: 0x1201,
nmps: 29,
nlps: 26,
switchFlag: 0
}, {
qe: 0x1101,
nmps: 30,
nlps: 27,
switchFlag: 0
}, {
qe: 0x0AC1,
nmps: 31,
nlps: 28,
switchFlag: 0
}, {
qe: 0x09C1,
nmps: 32,
nlps: 29,
switchFlag: 0
}, {
qe: 0x08A1,
nmps: 33,
nlps: 30,
switchFlag: 0
}, {
qe: 0x0521,
nmps: 34,
nlps: 31,
switchFlag: 0
}, {
qe: 0x0441,
nmps: 35,
nlps: 32,
switchFlag: 0
}, {
qe: 0x02A1,
nmps: 36,
nlps: 33,
switchFlag: 0
}, {
qe: 0x0221,
nmps: 37,
nlps: 34,
switchFlag: 0
}, {
qe: 0x0141,
nmps: 38,
nlps: 35,
switchFlag: 0
}, {
qe: 0x0111,
nmps: 39,
nlps: 36,
switchFlag: 0
}, {
qe: 0x0085,
nmps: 40,
nlps: 37,
switchFlag: 0
}, {
qe: 0x0049,
nmps: 41,
nlps: 38,
switchFlag: 0
}, {
qe: 0x0025,
nmps: 42,
nlps: 39,
switchFlag: 0
}, {
qe: 0x0015,
nmps: 43,
nlps: 40,
switchFlag: 0
}, {
qe: 0x0009,
nmps: 44,
nlps: 41,
switchFlag: 0
}, {
qe: 0x0005,
nmps: 45,
nlps: 42,
switchFlag: 0
}, {
qe: 0x0001,
nmps: 45,
nlps: 43,
switchFlag: 0
}, {
qe: 0x5601,
nmps: 46,
nlps: 46,
switchFlag: 0
}];
var ArithmeticDecoder = function ArithmeticDecoderClosure() {
var QeTable = [{
qe: 0x5601,
nmps: 1,
nlps: 1,
switchFlag: 1
}, {
qe: 0x3401,
nmps: 2,
nlps: 6,
switchFlag: 0
}, {
qe: 0x1801,
nmps: 3,
nlps: 9,
switchFlag: 0
}, {
qe: 0x0AC1,
nmps: 4,
nlps: 12,
switchFlag: 0
}, {
qe: 0x0521,
nmps: 5,
nlps: 29,
switchFlag: 0
}, {
qe: 0x0221,
nmps: 38,
nlps: 33,
switchFlag: 0
}, {
qe: 0x5601,
nmps: 7,
nlps: 6,
switchFlag: 1
}, {
qe: 0x5401,
nmps: 8,
nlps: 14,
switchFlag: 0
}, {
qe: 0x4801,
nmps: 9,
nlps: 14,
switchFlag: 0
}, {
qe: 0x3801,
nmps: 10,
nlps: 14,
switchFlag: 0
}, {
qe: 0x3001,
nmps: 11,
nlps: 17,
switchFlag: 0
}, {
qe: 0x2401,
nmps: 12,
nlps: 18,
switchFlag: 0
}, {
qe: 0x1C01,
nmps: 13,
nlps: 20,
switchFlag: 0
}, {
qe: 0x1601,
nmps: 29,
nlps: 21,
switchFlag: 0
}, {
qe: 0x5601,
nmps: 15,
nlps: 14,
switchFlag: 1
}, {
qe: 0x5401,
nmps: 16,
nlps: 14,
switchFlag: 0
}, {
qe: 0x5101,
nmps: 17,
nlps: 15,
switchFlag: 0
}, {
qe: 0x4801,
nmps: 18,
nlps: 16,
switchFlag: 0
}, {
qe: 0x3801,
nmps: 19,
nlps: 17,
switchFlag: 0
}, {
qe: 0x3401,
nmps: 20,
nlps: 18,
switchFlag: 0
}, {
qe: 0x3001,
nmps: 21,
nlps: 19,
switchFlag: 0
}, {
qe: 0x2801,
nmps: 22,
nlps: 19,
switchFlag: 0
}, {
qe: 0x2401,
nmps: 23,
nlps: 20,
switchFlag: 0
}, {
qe: 0x2201,
nmps: 24,
nlps: 21,
switchFlag: 0
}, {
qe: 0x1C01,
nmps: 25,
nlps: 22,
switchFlag: 0
}, {
qe: 0x1801,
nmps: 26,
nlps: 23,
switchFlag: 0
}, {
qe: 0x1601,
nmps: 27,
nlps: 24,
switchFlag: 0
}, {
qe: 0x1401,
nmps: 28,
nlps: 25,
switchFlag: 0
}, {
qe: 0x1201,
nmps: 29,
nlps: 26,
switchFlag: 0
}, {
qe: 0x1101,
nmps: 30,
nlps: 27,
switchFlag: 0
}, {
qe: 0x0AC1,
nmps: 31,
nlps: 28,
switchFlag: 0
}, {
qe: 0x09C1,
nmps: 32,
nlps: 29,
switchFlag: 0
}, {
qe: 0x08A1,
nmps: 33,
nlps: 30,
switchFlag: 0
}, {
qe: 0x0521,
nmps: 34,
nlps: 31,
switchFlag: 0
}, {
qe: 0x0441,
nmps: 35,
nlps: 32,
switchFlag: 0
}, {
qe: 0x02A1,
nmps: 36,
nlps: 33,
switchFlag: 0
}, {
qe: 0x0221,
nmps: 37,
nlps: 34,
switchFlag: 0
}, {
qe: 0x0141,
nmps: 38,
nlps: 35,
switchFlag: 0
}, {
qe: 0x0111,
nmps: 39,
nlps: 36,
switchFlag: 0
}, {
qe: 0x0085,
nmps: 40,
nlps: 37,
switchFlag: 0
}, {
qe: 0x0049,
nmps: 41,
nlps: 38,
switchFlag: 0
}, {
qe: 0x0025,
nmps: 42,
nlps: 39,
switchFlag: 0
}, {
qe: 0x0015,
nmps: 43,
nlps: 40,
switchFlag: 0
}, {
qe: 0x0009,
nmps: 44,
nlps: 41,
switchFlag: 0
}, {
qe: 0x0005,
nmps: 45,
nlps: 42,
switchFlag: 0
}, {
qe: 0x0001,
nmps: 45,
nlps: 43,
switchFlag: 0
}, {
qe: 0x5601,
nmps: 46,
nlps: 46,
switchFlag: 0
}];
function ArithmeticDecoder(data, start, end) {
class ArithmeticDecoder {
constructor(data, start, end) {
this.data = data;
this.bp = start;
this.dataEnd = end;
@ -15194,98 +15193,95 @@ var ArithmeticDecoder = function ArithmeticDecoderClosure() {
this.a = 0x8000;
}
ArithmeticDecoder.prototype = {
byteIn: function ArithmeticDecoder_byteIn() {
var data = this.data;
var bp = this.bp;
byteIn() {
const data = this.data;
let bp = this.bp;
if (data[bp] === 0xFF) {
var b1 = data[bp + 1];
if (b1 > 0x8F) {
this.clow += 0xFF00;
this.ct = 8;
} else {
bp++;
this.clow += data[bp] << 9;
this.ct = 7;
this.bp = bp;
}
if (data[bp] === 0xFF) {
if (data[bp + 1] > 0x8F) {
this.clow += 0xFF00;
this.ct = 8;
} else {
bp++;
this.clow += bp < this.dataEnd ? data[bp] << 8 : 0xFF00;
this.ct = 8;
this.clow += data[bp] << 9;
this.ct = 7;
this.bp = bp;
}
if (this.clow > 0xFFFF) {
this.chigh += this.clow >> 16;
this.clow &= 0xFFFF;
}
},
readBit: function ArithmeticDecoder_readBit(contexts, pos) {
var cx_index = contexts[pos] >> 1,
cx_mps = contexts[pos] & 1;
var qeTableIcx = QeTable[cx_index];
var qeIcx = qeTableIcx.qe;
var d;
var a = this.a - qeIcx;
if (this.chigh < qeIcx) {
if (a < qeIcx) {
a = qeIcx;
d = cx_mps;
cx_index = qeTableIcx.nmps;
} else {
a = qeIcx;
d = 1 ^ cx_mps;
if (qeTableIcx.switchFlag === 1) {
cx_mps = d;
}
cx_index = qeTableIcx.nlps;
}
} else {
this.chigh -= qeIcx;
if ((a & 0x8000) !== 0) {
this.a = a;
return cx_mps;
}
if (a < qeIcx) {
d = 1 ^ cx_mps;
if (qeTableIcx.switchFlag === 1) {
cx_mps = d;
}
cx_index = qeTableIcx.nlps;
} else {
d = cx_mps;
cx_index = qeTableIcx.nmps;
}
}
do {
if (this.ct === 0) {
this.byteIn();
}
a <<= 1;
this.chigh = this.chigh << 1 & 0xFFFF | this.clow >> 15 & 1;
this.clow = this.clow << 1 & 0xFFFF;
this.ct--;
} while ((a & 0x8000) === 0);
this.a = a;
contexts[pos] = cx_index << 1 | cx_mps;
return d;
} else {
bp++;
this.clow += bp < this.dataEnd ? data[bp] << 8 : 0xFF00;
this.ct = 8;
this.bp = bp;
}
};
return ArithmeticDecoder;
}();
if (this.clow > 0xFFFF) {
this.chigh += this.clow >> 16;
this.clow &= 0xFFFF;
}
}
readBit(contexts, pos) {
let cx_index = contexts[pos] >> 1,
cx_mps = contexts[pos] & 1;
const qeTableIcx = QeTable[cx_index];
const qeIcx = qeTableIcx.qe;
let d;
let a = this.a - qeIcx;
if (this.chigh < qeIcx) {
if (a < qeIcx) {
a = qeIcx;
d = cx_mps;
cx_index = qeTableIcx.nmps;
} else {
a = qeIcx;
d = 1 ^ cx_mps;
if (qeTableIcx.switchFlag === 1) {
cx_mps = d;
}
cx_index = qeTableIcx.nlps;
}
} else {
this.chigh -= qeIcx;
if ((a & 0x8000) !== 0) {
this.a = a;
return cx_mps;
}
if (a < qeIcx) {
d = 1 ^ cx_mps;
if (qeTableIcx.switchFlag === 1) {
cx_mps = d;
}
cx_index = qeTableIcx.nlps;
} else {
d = cx_mps;
cx_index = qeTableIcx.nmps;
}
}
do {
if (this.ct === 0) {
this.byteIn();
}
a <<= 1;
this.chigh = this.chigh << 1 & 0xFFFF | this.clow >> 15 & 1;
this.clow = this.clow << 1 & 0xFFFF;
this.ct--;
} while ((a & 0x8000) === 0);
this.a = a;
contexts[pos] = cx_index << 1 | cx_mps;
return d;
}
}
exports.ArithmeticDecoder = ArithmeticDecoder;
@ -21985,7 +21981,8 @@ class AnnotationBorderStyle {
setWidth(width) {
if ((0, _primitives.isName)(width)) {
width = parseFloat(width.name);
this.width = 0;
return;
}
if (Number.isInteger(width)) {
@ -28769,7 +28766,19 @@ var Font = function FontClosure() {
}
font.pos = (font.start ? font.start : 0) + header.offset;
font.pos += header.length - 2;
font.pos += 4;
font.pos += 2;
font.pos += 2;
font.pos += 2;
font.pos += 2;
font.pos += 2;
font.pos += 2;
font.pos += 2;
font.pos += 2;
font.pos += 2;
font.pos += 2;
font.pos += 8;
font.pos += 2;
var numOfMetrics = font.getUint16();
if (numOfMetrics > numGlyphs) {
@ -32260,11 +32269,11 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ExpertSubsetCharset = exports.ExpertCharset = exports.ISOAdobeCharset = void 0;
var ISOAdobeCharset = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 'yacute', 'ydieresis', 'zcaron'];
const ISOAdobeCharset = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 'yacute', 'ydieresis', 'zcaron'];
exports.ISOAdobeCharset = ISOAdobeCharset;
var ExpertCharset = ['.notdef', 'space', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 'onequarter', 'onehalf', 'threequarters', 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall'];
const ExpertCharset = ['.notdef', 'space', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 'onequarter', 'onehalf', 'threequarters', 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall'];
exports.ExpertCharset = ExpertCharset;
var ExpertSubsetCharset = ['.notdef', 'space', 'dollaroldstyle', 'dollarsuperior', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', 'hyphensuperior', 'colonmonetary', 'onefitted', 'rupiah', 'centoldstyle', 'figuredash', 'hypheninferior', 'onequarter', 'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior'];
const ExpertSubsetCharset = ['.notdef', 'space', 'dollaroldstyle', 'dollarsuperior', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', 'hyphensuperior', 'colonmonetary', 'onefitted', 'rupiah', 'centoldstyle', 'figuredash', 'hypheninferior', 'onequarter', 'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior'];
exports.ExpertSubsetCharset = ExpertSubsetCharset;
/***/ }),
@ -36871,7 +36880,7 @@ exports.getSupplementalGlyphMapForCalibri = exports.getSupplementalGlyphMapForAr
var _util = __w_pdfjs_require__(2);
var getStdFontMap = (0, _util.getLookupTableFactory)(function (t) {
const getStdFontMap = (0, _util.getLookupTableFactory)(function (t) {
t['ArialNarrow'] = 'Helvetica';
t['ArialNarrow-Bold'] = 'Helvetica-Bold';
t['ArialNarrow-BoldItalic'] = 'Helvetica-BoldOblique';
@ -36930,7 +36939,7 @@ var getStdFontMap = (0, _util.getLookupTableFactory)(function (t) {
t['TimesNewRomanPSMT-Italic'] = 'Times-Italic';
});
exports.getStdFontMap = getStdFontMap;
var getNonStdFontMap = (0, _util.getLookupTableFactory)(function (t) {
const getNonStdFontMap = (0, _util.getLookupTableFactory)(function (t) {
t['Calibri'] = 'Helvetica';
t['Calibri-Bold'] = 'Helvetica-Bold';
t['Calibri-BoldItalic'] = 'Helvetica-BoldOblique';
@ -36968,7 +36977,7 @@ var getNonStdFontMap = (0, _util.getLookupTableFactory)(function (t) {
t['Wingdings'] = 'ZapfDingbats';
});
exports.getNonStdFontMap = getNonStdFontMap;
var getSerifFonts = (0, _util.getLookupTableFactory)(function (t) {
const getSerifFonts = (0, _util.getLookupTableFactory)(function (t) {
t['Adobe Jenson'] = true;
t['Adobe Text'] = true;
t['Albertus'] = true;
@ -37104,13 +37113,13 @@ var getSerifFonts = (0, _util.getLookupTableFactory)(function (t) {
t['XITS'] = true;
});
exports.getSerifFonts = getSerifFonts;
var getSymbolsFonts = (0, _util.getLookupTableFactory)(function (t) {
const getSymbolsFonts = (0, _util.getLookupTableFactory)(function (t) {
t['Dingbats'] = true;
t['Symbol'] = true;
t['ZapfDingbats'] = true;
});
exports.getSymbolsFonts = getSymbolsFonts;
var getGlyphMapForStandardFonts = (0, _util.getLookupTableFactory)(function (t) {
const getGlyphMapForStandardFonts = (0, _util.getLookupTableFactory)(function (t) {
t[2] = 10;
t[3] = 32;
t[4] = 33;
@ -37506,13 +37515,13 @@ var getGlyphMapForStandardFonts = (0, _util.getLookupTableFactory)(function (t)
t[3416] = 8377;
});
exports.getGlyphMapForStandardFonts = getGlyphMapForStandardFonts;
var getSupplementalGlyphMapForArialBlack = (0, _util.getLookupTableFactory)(function (t) {
const getSupplementalGlyphMapForArialBlack = (0, _util.getLookupTableFactory)(function (t) {
t[227] = 322;
t[264] = 261;
t[291] = 346;
});
exports.getSupplementalGlyphMapForArialBlack = getSupplementalGlyphMapForArialBlack;
let getSupplementalGlyphMapForCalibri = (0, _util.getLookupTableFactory)(function (t) {
const getSupplementalGlyphMapForCalibri = (0, _util.getLookupTableFactory)(function (t) {
t[1] = 32;
t[4] = 65;
t[17] = 66;

Просмотреть файл

@ -1021,8 +1021,11 @@ let PDFViewerApplication = {
this.loadingBar.setWidth(this.appConfig.viewerContainer);
if (!_app_options.AppOptions.get('disableHistory') && !this.isViewerEmbedded) {
let resetHistory = !_app_options.AppOptions.get('showPreviousViewOnLoad');
this.pdfHistory.initialize(pdfDocument.fingerprint, resetHistory);
this.pdfHistory.initialize({
fingerprint: pdfDocument.fingerprint,
resetHistory: !_app_options.AppOptions.get('showPreviousViewOnLoad'),
updateUrl: _app_options.AppOptions.get('historyUpdateUrl')
});
if (this.pdfHistory.initialBookmark) {
this.initialBookmark = this.pdfHistory.initialBookmark;
@ -3928,6 +3931,10 @@ const defaultOptions = {
value: 0,
kind: OptionKind.VIEWER
},
historyUpdateUrl: {
value: false,
kind: OptionKind.VIEWER
},
imageResourcesPath: {
value: './images/',
kind: OptionKind.VIEWER
@ -5798,7 +5805,11 @@ class PDFHistory {
});
}
initialize(fingerprint, resetHistory = false) {
initialize({
fingerprint,
resetHistory = false,
updateUrl = false
}) {
if (!fingerprint || typeof fingerprint !== 'string') {
console.error('PDFHistory.initialize: The "fingerprint" must be a non-empty string.');
return;
@ -5806,6 +5817,7 @@ class PDFHistory {
let reInitialized = this.initialized && this.fingerprint !== fingerprint;
this.fingerprint = fingerprint;
this._updateUrl = updateUrl === true;
if (!this.initialized) {
this._bindEvents();
@ -5823,7 +5835,7 @@ class PDFHistory {
this._destination = null;
this._position = null;
if (!this._isValidState(state) || resetHistory) {
if (!this._isValidState(state, true) || resetHistory) {
let {
hash,
page,
@ -5970,11 +5982,21 @@ class PDFHistory {
this._updateInternalState(destination, newState.uid);
let newUrl;
if (this._updateUrl && destination && destination.hash) {
const baseUrl = document.location.href.split('#')[0];
if (!baseUrl.startsWith('file://')) {
newUrl = `${baseUrl}#${destination.hash}`;
}
}
if (shouldReplace) {
window.history.replaceState(newState, '');
window.history.replaceState(newState, '', newUrl);
} else {
this._maxUid = this._uid;
window.history.pushState(newState, '');
window.history.pushState(newState, '', newUrl);
}
}
@ -6023,13 +6045,25 @@ class PDFHistory {
this._pushOrReplaceState(position, forceReplace);
}
_isValidState(state) {
_isValidState(state, checkReload = false) {
if (!state) {
return false;
}
if (state.fingerprint !== this.fingerprint) {
return false;
if (checkReload) {
if (typeof state.fingerprint !== 'string' || state.fingerprint.length !== this.fingerprint.length) {
return false;
}
const [perfEntry] = performance.getEntriesByType('navigation');
if (!perfEntry || perfEntry.type !== 'reload') {
return false;
}
} else {
return false;
}
}
if (!Number.isInteger(state.uid) || state.uid < 0) {
@ -11600,6 +11634,7 @@ function getDefaultPreferences() {
"disableOpenActionDestination": true,
"disablePageMode": false,
"disablePageLabels": false,
"historyUpdateUrl": false,
"scrollModeOnLoad": 0,
"spreadModeOnLoad": 0
});

Просмотреть файл

@ -20,7 +20,7 @@ origin:
# Human-readable identifier for this version/release
# Generally "version NNN", "tag SSS", "bookmark SSS"
release: version 2.1.153
release: version 2.1.176
# The package's license, where possible using the mnemonic from
# https://spdx.org/licenses/

Просмотреть файл

@ -0,0 +1,45 @@
{
"presets": [
"react",
[
"env",
{
"targets": {
"browsers": ["last 1 Chrome version", "last 1 Firefox version"]
},
"modules": "commonjs"
}
]
],
"plugins": [
"transform-flow-strip-types",
"transform-class-properties",
"syntax-trailing-function-commas",
"syntax-object-rest-spread",
[
"module-resolver",
{
"alias": {
"devtools/client/shared/vendor/react": "react",
"devtools/client/shared/vendor/react-dom": "react-dom"
}
}
]
],
"env": {
"test": {
"presets": [
[
"env",
{
"targets": {
"node": 7
},
"modules": "commonjs"
}
]
]
}
}
}

Просмотреть файл

@ -0,0 +1,109 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const React = require("react");
import InlineSVG from "svg-inline-react";
const svg = {
"angle-brackets": require("./angle-brackets.svg"),
angular: require("./angular.svg"),
arrow: require("./arrow.svg"),
babel: require("./babel.svg"),
backbone: require("./backbone.svg"),
blackBox: require("./blackBox.svg"),
breadcrumb: require("./breadcrumbs-divider.svg"),
breakpoint: require("./breakpoint.svg"),
"column-breakpoint": require("./column-breakpoint.svg"),
"column-marker": require("./column-marker.svg"),
"case-match": require("./case-match.svg"),
choo: require("./choo.svg"),
close: require("./close.svg"),
coffeescript: require(`./coffeescript.svg`),
dojo: require("./dojo.svg"),
domain: require("./domain.svg"),
extension: require("./extension.svg"),
file: require("./file.svg"),
folder: require("./folder.svg"),
globe: require("./globe.svg"),
home: require("./home.svg"),
javascript: require("./javascript.svg"),
jquery: require("./jquery.svg"),
underscore: require("./underscore.svg"),
lodash: require("./lodash.svg"),
ember: require("./ember.svg"),
vuejs: require("./vuejs.svg"),
"magnifying-glass": require("./magnifying-glass.svg"),
"arrow-up": require("./arrow-up.svg"),
"arrow-down": require("./arrow-down.svg"),
pause: require("./pause.svg"),
"pause-exceptions": require("./pause-exceptions.svg"),
plus: require("./plus.svg"),
preact: require("./preact.svg"),
aframe: require("./aframe.svg"),
prettyPrint: require("./prettyPrint.svg"),
react: require("./react.svg"),
"regex-match": require("./regex-match.svg"),
redux: require("./redux.svg"),
immutable: require("./immutable.svg"),
resume: require("./resume.svg"),
settings: require("./settings.svg"),
stepIn: require("./stepIn.svg"),
stepOut: require("./stepOut.svg"),
stepOver: require("./stepOver.svg"),
subSettings: require("./subSettings.svg"),
tab: require("./tab.svg"),
toggleBreakpoints: require("./toggle-breakpoints.svg"),
togglePanes: require("./toggle-panes.svg"),
typescript: require("./typescript.svg"),
"whole-word-match": require("./whole-word-match.svg"),
worker: require("./worker.svg"),
"sad-face": require("devtools-mc-assets/assets/devtools/client/themes/images/sad-face.svg"),
refresh: require("devtools-mc-assets/assets/devtools/client/themes/images/reload.svg"),
webpack: require("./webpack.svg"),
node: require("./node.svg"),
express: require("./express.svg"),
pug: require("./pug.svg"),
extjs: require("./sencha-extjs.svg"),
mobx: require("./mobx.svg"),
marko: require("./marko.svg"),
nextjs: require("./nextjs.svg"),
showSources: require("./showSources.svg"),
showOutline: require("./showOutline.svg"),
nuxtjs: require("./nuxtjs.svg"),
rxjs: require("./rxjs.svg"),
loader: require('./loader.svg')
};
type SvgType = {
name: string,
className?: string,
onClick?: () => void,
"aria-label"?: string
};
function Svg({ name, className, onClick, "aria-label": ariaLabel }) {
if (!svg[name]) {
const error = `Unknown SVG: ${name}`;
console.warn(error);
return null;
}
className = `${name} ${className || ""}`;
if (name === "subSettings") {
className = "";
}
const props = {
className,
onClick,
["aria-label"]: ariaLabel,
src: svg[name]
};
return <InlineSVG {...props} />;
}
Svg.displayName = "Svg";
module.exports = Svg;

Просмотреть файл

@ -0,0 +1,40 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { resolve } = require("path");
const rootDir = resolve(__dirname);
module.exports = {
rootDir,
displayName: "test",
testURL: "http://localhost/",
testPathIgnorePatterns: [
"/node_modules/",
"/helpers/",
"/fixtures/",
"src/test/mochitest/examples/",
"<rootDir>/firefox",
"package.json",
"<rootDir>/packages"
],
modulePathIgnorePatterns: ["src/test/mochitest", "firefox"],
collectCoverageFrom: [
"src/**/*.js",
"!src/**/fixtures/*.js",
"!src/test/**/*.js",
"!src/components/stories/**/*.js",
"!**/*.mock.js",
"!**/*.spec.js"
],
transformIgnorePatterns: ["node_modules/(?!devtools-)"],
setupTestFrameworkScriptFile: "<rootDir>/src/test/tests-setup.js",
setupFiles: ["<rootDir>/src/test/shim.js", "jest-localstorage-mock"],
snapshotSerializers: [
"jest-serializer-babel-ast",
"enzyme-to-json/serializer"
],
moduleNameMapper: {
"\\.css$": "<rootDir>/src/test/__mocks__/styleMock.js",
"\\.svg$": "<rootDir>/src/test/__mocks__/svgMock.js"
}
};

Просмотреть файл

@ -0,0 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { resolve } = require("path");
const rootDir = resolve(__dirname);
module.exports = {
rootDir,
reporters: ["default"],
projects: [
"<rootDir>/jest-test.config.js",
"<rootDir>/packages/*/jest.config.js"
]
};

Просмотреть файл

@ -0,0 +1,137 @@
{
"name": "debugger.html",
"version": "0.6.0",
"license": "MPL-2.0",
"repository": {
"url": "git://github.com/devtools-html/debugger.html.git",
"type": "git"
},
"bugs": {
"url": "https://github.com/devtools-html/debugger.html/issues"
},
"homepage": "https://github.com/devtools-html/debugger.html#readme",
"engineStrict": true,
"engines": {
"node": ">=7.7.0"
},
"scripts": {
},
"dependencies": {
"@babel/core": "^7.0.0-beta.55",
"@babel/parser": "^7.0.0-beta.55",
"@babel/template": "^7.0.0-beta.55",
"@babel/types": "^7.0.0-beta.55",
"babel-plugin-transform-imports": "^1.5.0",
"codemirror": "^5.28.0",
"devtools-environment": "^0.0.6",
"devtools-launchpad": "^0.0.141",
"devtools-linters": "^0.0.4",
"devtools-reps": "0.23.0",
"devtools-source-map": "0.16.0",
"devtools-splitter": "^0.0.8",
"devtools-utils": "0.0.14",
"fuzzaldrin-plus": "^0.6.0",
"immutable": "^3.8.2",
"lodash": "^4.17.4",
"lodash-move": "^1.1.1",
"lodash.kebabcase": "^4.1.1",
"md5": "^2.2.1",
"parse-script-tags": "^0.1.6",
"pretty-fast": "^0.2.3",
"prop-types": "^15.6.0",
"react": "16.4.1",
"react-aria-components": "^0.0.4",
"react-dom": "16.4.1",
"react-immutable-proptypes": "^2.1.0",
"react-inlinesvg": "^0.8.1",
"react-redux": "^5.0.7",
"react-transition-group": "^2.2.1",
"reselect": "^4.0.0",
"svg-inline-react": "^3.0.0",
"wasmparser": "^0.7.0"
},
"private": true,
"workspaces": [
"packages/*"
],
"files": [
"src",
"assets"
],
"greenkeeper": {
"ignore": [
"react",
"react-dom",
"react-redux",
"redux",
"codemirror"
]
},
"main": "src/main.js",
"author": "Jason Laster <jlaster@mozilla.com>",
"devDependencies": {
"@sucrase/webpack-object-rest-spread-plugin": "^1.0.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.0.0",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"babel-plugin-syntax-trailing-function-commas": "^6.22.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"chalk": "^2.1.0",
"copy-paste": "^1.3.0",
"copy-webpack-plugin": "^4.5.2",
"devtools-license-check": "^0.7.0",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "3.3.4",
"eslint": "^5.0.0",
"eslint-config-prettier": "^3.0.0",
"eslint-plugin-babel": "^5.0.0",
"eslint-plugin-file-header": "0.0.1",
"eslint-plugin-flowtype": "^3.0.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jest": "^21.15.1",
"eslint-plugin-jsx-a11y": "^6.1.2",
"eslint-plugin-mozilla": "1.0.4",
"eslint-plugin-prettier": "^3.0.0",
"eslint-plugin-react": "^7.2.1",
"expect.js": "^0.3.1",
"flow-bin": "^0.89.0",
"glob": "^7.0.3",
"husky": "^1.0.1",
"jest": "^23.0.0",
"jest-environment-jsdom": "^23.0.0",
"jest-in-case": "^1.0.2",
"jest-junit": "^5.0.0",
"jest-localstorage-mock": "^2.2.0",
"jest-serializer-babel-ast": "^0.0.5",
"lint-staged": "^8.0.0",
"mochii": "^0.0.32",
"mock-require": "^3.0.0",
"node-emoji": "^1.8.1",
"npm-run-all": "^4.0.2",
"prettier": "^1.12.1",
"pretty-quick": "^1.4.1",
"remark-cli": "^6.0.0",
"remark-lint": "^6.0.1",
"remark-lint-list-item-bullet-indent": "^1.0.1",
"remark-lint-list-item-indent": "^1.0.1",
"remark-lint-no-shortcut-reference-image": "^1.0.1",
"remark-lint-no-shortcut-reference-link": "^1.0.2",
"remark-lint-no-table-indentation": "^1.0.0",
"remark-lint-no-unused-definitions": "^1.0.1",
"remark-lint-ordered-list-marker-style": "^1.0.1",
"remark-lint-table-cell-padding": "^1.0.0",
"remark-lint-table-pipes": "^1.0.0",
"remark-preset-lint-recommended": "^3.0.0",
"remark-validate-links": "^7.0.0",
"rimraf": "^2.6.1",
"single-line-log": "^1.1.2",
"stylelint": "^9.0.0",
"webpack": "^3.5.5",
"webpack-visualizer-plugin": "^0.1.11",
"workerjs": "github:jasonLaster/workerjs#a2425aaeebacae7a7640e496a54c2a41962f3890"
}
}

Просмотреть файл

@ -0,0 +1,7 @@
{
"presets": ["react", "flow"],
"plugins": [
"transform-es2015-modules-commonjs",
"transform-flow-strip-types"
]
}

Просмотреть файл

@ -0,0 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { configure } = require("@storybook/react");
function loadStories() {
require("../stories/index");
}
configure(loadStories, module);

Просмотреть файл

@ -0,0 +1,5 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module.exports = require("../webpack.config.js");

Просмотреть файл

@ -0,0 +1,362 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

Просмотреть файл

@ -0,0 +1,5 @@
## Devtools Components
Devtools shared Components
* _Tree_ - A performant tree

Просмотреть файл

@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Tree from "./src/tree";
module.exports = {
Tree
};

Просмотреть файл

@ -0,0 +1,20 @@
const { resolve } = require("path");
const rootDir = resolve(__dirname, "src");
module.exports = {
rootDir,
displayName: "devtools-components test",
setupFiles: [
"<rootDir>/../../../src/test/__mocks__/request-animation-frame.js",
"<rootDir>/tests/setup.js"
],
testMatch: ["**/tests/**/*.js"],
testPathIgnorePatterns: [
"/node_modules/",
"<rootDir>/tests/__mocks__/",
"<rootDir>/tests/setup.js"
],
testURL: "http://localhost/",
moduleNameMapper: {
"\\.css$": "<rootDir>/../../../src/test/__mocks__/styleMock.js"
}
};

Просмотреть файл

@ -0,0 +1,33 @@
{
"name": "devtools-components",
"version": "0.6.0",
"description": "DevTools HTML Components",
"main": "index.js",
"scripts": {
"copy-assets": "node bin/copy-assets",
"license-check": "devtools-license-check",
"storybook":
"NODE_ENV=storybook start-storybook -p 9002 -c .storybook -s ./src",
"test": "jest --projects jest.config.js"
},
"author": "Jason Laster",
"license": "MPL-2.0",
"dependencies": {
"prop-types": "^15.6.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-dom-factories": "^1.0.2"
},
"devDependencies": {
"@storybook/react": "^3.3.14",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-preset-react": "^6.24.1",
"devtools-license-check": "^0.7.0",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"eslint": "^5.0.0",
"eslint-plugin-mozilla": "1.0.4",
"fs-extra": "^7.0.0",
"lodash": "^4.17.2"
}
}

Просмотреть файл

@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const mapUrl = require("postcss-url-mapper");
const MC_PATH = "resource://devtools/client/debugger/new/images/";
const EXPRESS_PATH = "/devtools-components/images/";
function mapUrlProduction(url, type) {
return url.replace("/images/arrow.svg", MC_PATH + "arrow.svg");
}
function mapUrlDevelopment(url) {
return url.replace("/images/arrow.svg", EXPRESS_PATH + "arrow.svg");
}
module.exports = ({ file, options, env }) => {
// Here we don't want to do anything for storybook since we serve the images thanks
// to the `-s ./src` option in the `storybook` command (see package.json).
if (env === "storybook") {
return {};
}
// This will be used when creating a bundle for mozilla-central (from devtools-reps
// or debugger.html).
if (env === "production") {
return {
plugins: [mapUrl(mapUrlProduction)]
};
}
// This will be used when using this module in launchpad mode. We set a unique path so
// we can serve images from express.
return {
plugins: [mapUrl(mapUrlDevelopment)]
};
};

Просмотреть файл

@ -0,0 +1,6 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill #9B9B9B">
<path d="M8 13.4c-.5 0-.9-.2-1.2-.6L.4 5.2C0 4.7-.1 4.3.2 3.7S1 3 1.6 3h12.8c.6 0 1.2.1 1.4.7.3.6.2 1.1-.2 1.6l-6.4 7.6c-.3.4-.7.5-1.2.5z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 474 B

Просмотреть файл

@ -0,0 +1,908 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Tree Don't auto expand root with very large number of children 1`] = `
Array [
"key-A",
"key-B",
"key-E",
"key-F",
"key-G",
"key-C",
"key-H",
"key-I",
"key-D",
"key-J",
"key-M",
"key-N",
]
`;
exports[`Tree ignores key strokes when pressing modifiers 1`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 2`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 3`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 4`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 5`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 6`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 7`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 8`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 9`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 10`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 11`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 12`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 13`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 14`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 15`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 16`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree ignores key strokes when pressing modifiers 17`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders arrows as expected when nodes are collapsed 1`] = `
"
▶︎ A
▶︎ M
"
`;
exports[`Tree renders arrows as expected when nodes are expanded 1`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected 1`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected navigating down with keyboard on last node 1`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | [O]
"
`;
exports[`Tree renders as expected navigating down with keyboard on last node 2`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | [O]
"
`;
exports[`Tree renders as expected navigating up with the keyboard on a root 1`] = `
"
▼ [A]
| ▼ B
| | ▼ E
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected navigating up with the keyboard on a root 2`] = `
"
▼ [A]
| ▼ B
| | ▼ E
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected navigating with arrows on unexpandable roots 1`] = `
"
[A]
M
"
`;
exports[`Tree renders as expected navigating with arrows on unexpandable roots 2`] = `
"
A
[M]
"
`;
exports[`Tree renders as expected navigating with arrows on unexpandable roots 3`] = `
"
[A]
M
"
`;
exports[`Tree renders as expected when given a focused item 1`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | L
| | F
| | [G]
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when given a focused item 2`] = `
"
▶︎ [A]
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when given a focused item 3`] = `
"
▼ [A]
| ▼ B
| | ▼ E
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when given a focused item 4`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating down with the keyboard 1`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | [K]
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating down with the keyboard 2`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating down with the keyboard 3`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | L
| | [F]
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating up with the keyboard 1`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating up with the keyboard 2`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | [K]
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating up with the keyboard 3`] = `
"
▼ A
| ▼ B
| | ▼ [E]
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating with home/end 1`] = `
"
▶︎ A
▶︎ [M]
"
`;
exports[`Tree renders as expected when navigating with home/end 2`] = `
"
▶︎ [A]
▶︎ M
"
`;
exports[`Tree renders as expected when navigating with home/end 3`] = `
"
▶︎ [A]
▶︎ M
"
`;
exports[`Tree renders as expected when navigating with home/end 4`] = `
"
▶︎ A
▶︎ [M]
"
`;
exports[`Tree renders as expected when navigating with home/end 5`] = `
"
▶︎ A
▶︎ [M]
"
`;
exports[`Tree renders as expected when navigating with home/end 6`] = `
"
▶︎ A
▼ [M]
| ▶︎ N
"
`;
exports[`Tree renders as expected when navigating with home/end 7`] = `
"
▶︎ A
▼ M
| ▶︎ [N]
"
`;
exports[`Tree renders as expected when navigating with home/end 8`] = `
"
▶︎ A
▼ M
| ▶︎ [N]
"
`;
exports[`Tree renders as expected when navigating with home/end 9`] = `
"
▶︎ [A]
▼ M
| ▶︎ N
"
`;
exports[`Tree renders as expected when navigating with left arrows on roots 1`] = `
"
▶︎ A
▶︎ [M]
"
`;
exports[`Tree renders as expected when navigating with left arrows on roots 2`] = `
"
▶︎ [A]
▶︎ M
"
`;
exports[`Tree renders as expected when navigating with left arrows on roots 3`] = `
"
▶︎ [A]
▶︎ M
"
`;
exports[`Tree renders as expected when navigating with right/left arrows 1`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | K
| | | [L]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating with right/left arrows 2`] = `
"
▼ A
| ▼ B
| | ▼ [E]
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating with right/left arrows 3`] = `
"
▼ A
| ▼ B
| | ▶︎ [E]
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating with right/left arrows 4`] = `
"
▼ A
| ▼ B
| | ▼ [E]
| | | K
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when navigating with right/left arrows 5`] = `
"
▼ A
| ▼ B
| | ▼ E
| | | [K]
| | | L
| | F
| | G
| ▼ C
| | H
| | I
| ▼ D
| | J
▼ M
| ▼ N
| | O
"
`;
exports[`Tree renders as expected when passed autoDepth:1 1`] = `
"
▼ A
| ▶︎ B
| ▶︎ C
| ▶︎ D
▼ M
| ▶︎ N
"
`;
exports[`Tree renders as expected with collapsed nodes 1`] = `
"
▶︎ A
▼ M
| ▼ N
| | O
"
`;
exports[`Tree uses isExpandable prop if it exists to render tree nodes 1`] = `
"
▶︎ A
M
"
`;

Просмотреть файл

@ -0,0 +1,8 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
// Configure enzyme with React 16 adapter.
const Enzyme = require("enzyme");
const Adapter = require("enzyme-adapter-react-16");
Enzyme.configure({ adapter: new Adapter() });

Просмотреть файл

@ -0,0 +1,762 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/* global jest */
import React from "react";
import { mount } from "enzyme";
import Components from "../../index";
import dom from "react-dom-factories";
const { Component, createFactory } = React;
const Tree = createFactory(Components.Tree);
function mountTree(overrides = {}) {
return mount(
createFactory(
class container extends Component {
constructor(props) {
super(props);
const state = {
expanded: overrides.expanded || new Set(),
focused: overrides.focused
};
delete overrides.focused;
this.state = state;
}
render() {
return Tree(
Object.assign(
{
getParent: x => TEST_TREE.parent[x],
getChildren: x => TEST_TREE.children[x],
renderItem: (x, depth, focused, arrow) => {
return dom.div(
{},
arrow,
focused ? "[" : null,
x,
focused ? "]" : null
);
},
getRoots: () => ["A", "M"],
getKey: x => `key-${x}`,
itemHeight: 1,
onFocus: x => {
this.setState(previousState => {
return { focused: x };
});
},
onExpand: x => {
this.setState(previousState => {
const expanded = new Set(previousState.expanded);
expanded.add(x);
return { expanded };
});
},
onCollapse: x => {
this.setState(previousState => {
const expanded = new Set(previousState.expanded);
expanded.delete(x);
return { expanded };
});
},
isExpanded: x => this.state && this.state.expanded.has(x),
focused: this.state.focused
},
overrides
)
);
}
}
)()
);
}
describe("Tree", () => {
it("does not throw", () => {
expect(mountTree()).toBeTruthy();
});
it("Don't auto expand root with very large number of children", () => {
const children = Array.from(
{ length: 51 },
(_, i) => `should-not-be-visible-${i + 1}`
);
// N has a lot of children, so it won't be automatically expanded
const wrapper = mountTree({
autoExpandDepth: 2,
autoExpandNodeChildrenLimit: 50,
getChildren: item => {
if (item === "N") {
return children;
}
return TEST_TREE.children[item] || [];
}
});
const ids = getTreeNodes(wrapper).map(node => node.prop("id"));
expect(ids).toMatchSnapshot();
});
it("is accessible", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJMN".split(""))
});
expect(wrapper.getDOMNode().getAttribute("role")).toBe("tree");
expect(wrapper.getDOMNode().getAttribute("tabIndex")).toBe("0");
const expected = {
A: { id: "key-A", level: 1, expanded: true },
B: { id: "key-B", level: 2, expanded: true },
C: { id: "key-C", level: 2, expanded: true },
D: { id: "key-D", level: 2, expanded: true },
E: { id: "key-E", level: 3, expanded: true },
F: { id: "key-F", level: 3, expanded: true },
G: { id: "key-G", level: 3, expanded: true },
H: { id: "key-H", level: 3, expanded: true },
I: { id: "key-I", level: 3, expanded: true },
J: { id: "key-J", level: 3, expanded: true },
K: { id: "key-K", level: 4, expanded: undefined },
L: { id: "key-L", level: 4, expanded: undefined },
M: { id: "key-M", level: 1, expanded: true },
N: { id: "key-N", level: 2, expanded: true },
O: { id: "key-O", level: 3, expanded: undefined }
};
getTreeNodes(wrapper).forEach(node => {
const key = node.prop("id").replace("key-", "");
const item = expected[key];
expect(node.prop("id")).toBe(item.id);
expect(node.prop("role")).toBe("treeitem");
expect(node.prop("aria-level")).toBe(item.level);
expect(node.prop("aria-expanded")).toBe(item.expanded);
});
});
it("renders as expected", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split(""))
});
expect(formatTree(wrapper)).toMatchSnapshot();
});
it("renders as expected when passed a className", () => {
const wrapper = mountTree({
className: "testClassName"
});
expect(wrapper.find(".tree").hasClass("testClassName")).toBe(true);
});
it("renders as expected when passed a style", () => {
const wrapper = mountTree({
style: {
color: "red"
}
});
expect(wrapper.getDOMNode().style.color).toBe("red");
});
it("renders as expected when passed a label", () => {
const wrapper = mountTree({
label: "testAriaLabel"
});
expect(wrapper.getDOMNode().getAttribute("aria-label")).toBe(
"testAriaLabel"
);
});
it("renders as expected when passed an aria-labelledby", () => {
const wrapper = mountTree({
labelledby: "testAriaLabelBy"
});
expect(wrapper.getDOMNode().getAttribute("aria-labelledby")).toBe(
"testAriaLabelBy"
);
});
it("renders as expected with collapsed nodes", () => {
const wrapper = mountTree({
expanded: new Set("MNO".split(""))
});
expect(formatTree(wrapper)).toMatchSnapshot();
});
it("renders as expected when passed autoDepth:1", () => {
const wrapper = mountTree({
autoExpandDepth: 1
});
expect(formatTree(wrapper)).toMatchSnapshot();
});
it("renders as expected when given a focused item", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "G"
});
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-G"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-G");
getTreeNodes(wrapper)
.first()
.simulate("click");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-A"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-A");
getTreeNodes(wrapper)
.first()
.simulate("click");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-A"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-A");
wrapper.simulate("blur");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().hasAttribute("aria-activedescendant")).toBe(
false
);
expect(wrapper.find(".focused").exists()).toBe(false);
});
it("renders as expected when navigating up with the keyboard", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "L"
});
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-L"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-L");
simulateKeyDown(wrapper, "ArrowUp");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-K"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-K");
simulateKeyDown(wrapper, "ArrowUp");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-E"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-E");
});
it("renders as expected navigating up with the keyboard on a root", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "A"
});
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-A"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-A");
simulateKeyDown(wrapper, "ArrowUp");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-A"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-A");
});
it("renders as expected when navigating down with the keyboard", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "K"
});
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-K"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-K");
simulateKeyDown(wrapper, "ArrowDown");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-L"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-L");
simulateKeyDown(wrapper, "ArrowDown");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-F"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-F");
});
it("renders as expected navigating down with keyboard on last node", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "O"
});
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-O"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-O");
simulateKeyDown(wrapper, "ArrowDown");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-O"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-O");
});
it("renders as expected when navigating with right/left arrows", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "L"
});
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-L"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-L");
simulateKeyDown(wrapper, "ArrowLeft");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-E"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-E");
simulateKeyDown(wrapper, "ArrowLeft");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-E"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-E");
simulateKeyDown(wrapper, "ArrowRight");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-E"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-E");
simulateKeyDown(wrapper, "ArrowRight");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-K"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-K");
});
it("renders as expected when navigating with left arrows on roots", () => {
const wrapper = mountTree({
focused: "M"
});
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-M"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-M");
simulateKeyDown(wrapper, "ArrowLeft");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-A"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-A");
simulateKeyDown(wrapper, "ArrowLeft");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-A"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-A");
});
it("renders as expected when navigating with home/end", () => {
const wrapper = mountTree({
focused: "M"
});
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-M"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-M");
simulateKeyDown(wrapper, "Home");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-A"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-A");
simulateKeyDown(wrapper, "Home");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-A"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-A");
simulateKeyDown(wrapper, "End");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-M"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-M");
simulateKeyDown(wrapper, "End");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-M"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-M");
simulateKeyDown(wrapper, "ArrowRight");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-M"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-M");
simulateKeyDown(wrapper, "End");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-N"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-N");
simulateKeyDown(wrapper, "End");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-N"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-N");
simulateKeyDown(wrapper, "Home");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-A"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-A");
});
it("renders as expected navigating with arrows on unexpandable roots", () => {
const wrapper = mountTree({
focused: "A",
isExpandable: item => false
});
expect(formatTree(wrapper)).toMatchSnapshot();
simulateKeyDown(wrapper, "ArrowRight");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-M"
);
simulateKeyDown(wrapper, "ArrowLeft");
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-A"
);
});
it("calls onFocus when expected", () => {
const onFocus = jest.fn(x => {
wrapper &&
wrapper.setState(() => {
return { focused: x };
});
});
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "I",
onFocus
});
simulateKeyDown(wrapper, "ArrowUp");
expect(onFocus.mock.calls[0][0]).toBe("H");
simulateKeyDown(wrapper, "ArrowUp");
expect(onFocus.mock.calls[1][0]).toBe("C");
simulateKeyDown(wrapper, "ArrowLeft");
simulateKeyDown(wrapper, "ArrowLeft");
expect(onFocus.mock.calls[2][0]).toBe("A");
simulateKeyDown(wrapper, "ArrowRight");
expect(onFocus.mock.calls[3][0]).toBe("B");
simulateKeyDown(wrapper, "ArrowDown");
expect(onFocus.mock.calls[4][0]).toBe("E");
});
it("focus treeRef when a node is clicked", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split(""))
});
const treeRef = wrapper
.find("Tree")
.first()
.instance().treeRef;
treeRef.focus = jest.fn();
getTreeNodes(wrapper)
.first()
.simulate("click");
expect(treeRef.focus.mock.calls).toHaveLength(1);
});
it("doesn't focus treeRef when focused is null", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "A"
});
const treeRef = wrapper
.find("Tree")
.first()
.instance().treeRef;
treeRef.focus = jest.fn();
wrapper.simulate("blur");
expect(treeRef.focus.mock.calls).toHaveLength(0);
});
it("calls onActivate when expected", () => {
const onActivate = jest.fn();
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "A",
onActivate
});
simulateKeyDown(wrapper, "Enter");
expect(onActivate.mock.calls).toHaveLength(1);
expect(onActivate.mock.calls[0][0]).toBe("A");
simulateKeyDown(wrapper, "Enter");
expect(onActivate.mock.calls).toHaveLength(2);
expect(onActivate.mock.calls[1][0]).toBe("A");
simulateKeyDown(wrapper, "ArrowDown");
simulateKeyDown(wrapper, "Enter");
expect(onActivate.mock.calls).toHaveLength(3);
expect(onActivate.mock.calls[2][0]).toBe("B");
wrapper.simulate("blur");
simulateKeyDown(wrapper, "Enter");
expect(onActivate.mock.calls).toHaveLength(3);
});
it("does not throw when onActivate is undefined and Enter is pressed", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "A"
});
simulateKeyDown(wrapper, "Enter");
});
it("ignores key strokes when pressing modifiers", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split("")),
focused: "L"
});
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-L"
);
expect(wrapper.find(".focused").prop("id")).toBe("key-L");
const testKeys = [
{ key: "ArrowDown" },
{ key: "ArrowUp" },
{ key: "ArrowLeft" },
{ key: "ArrowRight" }
];
const modifiers = [
{ altKey: true },
{ ctrlKey: true },
{ metaKey: true },
{ shiftKey: true }
];
for (const key of testKeys) {
for (const modifier of modifiers) {
wrapper.simulate("keydown", Object.assign({}, key, modifier));
expect(formatTree(wrapper)).toMatchSnapshot();
expect(wrapper.getDOMNode().getAttribute("aria-activedescendant")).toBe(
"key-L"
);
}
}
});
it("renders arrows as expected when nodes are expanded", () => {
const wrapper = mountTree({
expanded: new Set("ABCDEFGHIJKLMNO".split(""))
});
expect(formatTree(wrapper)).toMatchSnapshot();
getTreeNodes(wrapper).forEach(n => {
if ("ABECDMN".split("").includes(getSanitizedNodeText(n))) {
expect(n.find(".arrow.expanded").exists()).toBe(true);
} else {
expect(n.find(".arrow").exists()).toBe(false);
}
});
});
it("renders arrows as expected when nodes are collapsed", () => {
const wrapper = mountTree();
expect(formatTree(wrapper)).toMatchSnapshot();
getTreeNodes(wrapper).forEach(n => {
const arrow = n.find(".arrow");
expect(arrow.exists()).toBe(true);
expect(arrow.hasClass("expanded")).toBe(false);
});
});
it("uses isExpandable prop if it exists to render tree nodes", () => {
const wrapper = mountTree({
isExpandable: item => item === "A"
});
expect(formatTree(wrapper)).toMatchSnapshot();
});
it("adds the expected data-expandable attribute", () => {
const wrapper = mountTree({
isExpandable: item => item === "A"
});
const nodes = getTreeNodes(wrapper);
expect(nodes.at(0).prop("data-expandable")).toBe(true);
expect(nodes.at(1).prop("data-expandable")).toBe(false);
});
});
function getTreeNodes(wrapper) {
return wrapper.find(".tree-node");
}
function simulateKeyDown(wrapper, key) {
wrapper.simulate("keydown", {
key,
preventDefault: () => {},
stopPropagation: () => {}
});
}
/*
* Takes an Enzyme wrapper (obtained with mount/mount/) and
* returns a stringified version of the Tree, e.g.
*
* A
* | B
* | | E
* | | | K
* | | | L
* | | F
* | | G
* | C
* | | H
* | | I
* | D
* | | J
* M
* | N
* | | O
*
*/
function formatTree(wrapper) {
const textTree = getTreeNodes(wrapper)
.map(node => {
const level = (node.prop("aria-level") || 1) - 1;
const indentStr = "| ".repeat(level);
const arrow = node.find(".arrow");
let arrowStr = " ";
if (arrow.exists()) {
arrowStr = arrow.hasClass("expanded") ? "▼ " : "▶︎ ";
}
return `${indentStr}${arrowStr}${getSanitizedNodeText(node)}`;
})
.join("\n");
// Wrap in new lines so tree nodes are aligned as expected.
return `\n${textTree}\n`;
}
function getSanitizedNodeText(node) {
// Stripping off the invisible space used in the indent.
return node.text().replace(/^\u200B+/, "");
}
// Encoding of the following tree/forest:
//
// A
// |-- B
// | |-- E
// | | |-- K
// | | `-- L
// | |-- F
// | `-- G
// |-- C
// | |-- H
// | `-- I
// `-- D
// `-- J
// M
// `-- N
// `-- O
var TEST_TREE = {
children: {
A: ["B", "C", "D"],
B: ["E", "F", "G"],
C: ["H", "I"],
D: ["J"],
E: ["K", "L"],
F: [],
G: [],
H: [],
I: [],
J: [],
K: [],
L: [],
M: ["N"],
N: ["O"],
O: []
},
parent: {
A: null,
B: "A",
C: "A",
D: "A",
E: "B",
F: "B",
G: "B",
H: "C",
I: "C",
J: "D",
K: "E",
L: "E",
M: null,
N: "M",
O: "N"
}
};

Просмотреть файл

@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* We can remove the outline since we do add our own focus style on nodes */
.tree:focus {
outline: none;
}
.tree.inline {
display: inline-block;
}
.tree.nowrap {
white-space: nowrap;
}
.tree.noselect {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
.tree .tree-node {
display: flex;
}
.tree .tree-node:not(.focused):hover {
background-color: var(--theme-selection-background-hover);
}
.tree-indent {
display: inline-block;
width: 12px;
margin-inline-start: 5px;
border-inline-start: 1px solid #A2D1FF;
flex-shrink: 0;
}
/* Align with expandables siblings (where we have the arrow) */
.tree-node[data-expandable="false"] .tree-indent:last-of-type {
margin-inline-end: 15px;
}
/* For non expandable root nodes, we don't have .tree-indent elements, so we declare
the margin on the start of the node */
.tree-node[data-expandable="false"][aria-level="1"] {
padding-inline-start: 15px
}
.tree .tree-node[data-expandable="true"] {
cursor: default;
}
.tree-node button.arrow {
background:url(/images/arrow.svg) no-repeat;
background-size:contain;
background-position:center center;
width: 9px;
height: 9px;
border:0;
padding:0;
margin-inline-start: 1px;
margin-inline-end: 4px;
transform: rotate(-90deg);
transform-origin: center center;
transition: transform 0.125s ease;
align-self: center;
-moz-context-properties: fill;
fill: var(--theme-splitter-color, #9B9B9B);
}
html[dir="rtl"] .tree-node button.arrow {
transform: rotate(90deg);
}
.tree-node button.arrow.expanded.expanded {
transform: rotate(0deg);
}
.tree .tree-node.focused {
color: white;
background-color: var(--theme-selection-background, #0a84ff);
}
.tree-node.focused button.arrow {
fill: currentColor;
}

Просмотреть файл

@ -0,0 +1,852 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
import React from "react";
const { Component, createFactory } = React;
import dom from "react-dom-factories";
import PropTypes from "prop-types";
require("./tree.css");
// depth
const AUTO_EXPAND_DEPTH = 0;
/**
* An arrow that displays whether its node is expanded () or collapsed
* (). When its node has no children, it is hidden.
*/
class ArrowExpander extends Component {
static get propTypes() {
return {
expanded: PropTypes.bool
};
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.expanded !== nextProps.expanded;
}
render() {
const { expanded } = this.props;
const classNames = ["arrow"];
if (expanded) {
classNames.push("expanded");
}
return dom.button({
className: classNames.join(" ")
});
}
}
const treeIndent = dom.span({ className: "tree-indent" }, "\u200B");
class TreeNode extends Component {
static get propTypes() {
return {
id: PropTypes.any.isRequired,
index: PropTypes.number.isRequired,
depth: PropTypes.number.isRequired,
focused: PropTypes.bool.isRequired,
expanded: PropTypes.bool.isRequired,
item: PropTypes.any.isRequired,
isExpandable: PropTypes.bool.isRequired,
onClick: PropTypes.func,
renderItem: PropTypes.func.isRequired
};
}
shouldComponentUpdate(nextProps) {
return (
this.props.item !== nextProps.item ||
this.props.focused !== nextProps.focused ||
this.props.expanded !== nextProps.expanded
);
}
render() {
const {
depth,
id,
item,
focused,
expanded,
renderItem,
isExpandable
} = this.props;
const arrow = isExpandable
? ArrowExpanderFactory({
item,
expanded
})
: null;
let ariaExpanded;
if (this.props.isExpandable) {
ariaExpanded = false;
}
if (this.props.expanded) {
ariaExpanded = true;
}
const indents = Array.from({ length: depth }).fill(treeIndent);
const items = indents.concat(
renderItem(item, depth, focused, arrow, expanded)
);
return dom.div(
{
id,
className: `tree-node${focused ? " focused" : ""}`,
onClick: this.props.onClick,
role: "treeitem",
"aria-level": depth + 1,
"aria-expanded": ariaExpanded,
"data-expandable": this.props.isExpandable
},
...items
);
}
}
const ArrowExpanderFactory = createFactory(ArrowExpander);
const TreeNodeFactory = createFactory(TreeNode);
/**
* Create a function that calls the given function `fn` only once per animation
* frame.
*
* @param {Function} fn
* @returns {Function}
*/
function oncePerAnimationFrame(fn) {
let animationId = null;
let argsToPass = null;
return function(...args) {
argsToPass = args;
if (animationId !== null) {
return;
}
animationId = requestAnimationFrame(() => {
fn.call(this, ...argsToPass);
animationId = null;
argsToPass = null;
});
};
}
/**
* A generic tree component. See propTypes for the public API.
*
* This tree component doesn't make any assumptions about the structure of your
* tree data. Whether children are computed on demand, or stored in an array in
* the parent's `_children` property, it doesn't matter. We only require the
* implementation of `getChildren`, `getRoots`, `getParent`, and `isExpanded`
* functions.
*
* This tree component is well tested and reliable. See the tests in ./tests
* and its usage in the performance and memory panels in mozilla-central.
*
* This tree component doesn't make any assumptions about how to render items in
* the tree. You provide a `renderItem` function, and this component will ensure
* that only those items whose parents are expanded and which are visible in the
* viewport are rendered. The `renderItem` function could render the items as a
* "traditional" tree or as rows in a table or anything else. It doesn't
* restrict you to only one certain kind of tree.
*
* The tree comes with basic styling for the indent, the arrow, as well as
* hovered and focused styles which can be override in CSS.
*
* ### Example Usage
*
* Suppose we have some tree data where each item has this form:
*
* {
* id: Number,
* label: String,
* parent: Item or null,
* children: Array of child items,
* expanded: bool,
* }
*
* Here is how we could render that data with this component:
*
* class MyTree extends Component {
* static get propTypes() {
* // The root item of the tree, with the form described above.
* return {
* root: PropTypes.object.isRequired
* };
* },
*
* render() {
* return Tree({
* itemHeight: 20, // px
*
* getRoots: () => [this.props.root],
*
* getParent: item => item.parent,
* getChildren: item => item.children,
* getKey: item => item.id,
* isExpanded: item => item.expanded,
*
* renderItem: (item, depth, isFocused, arrow, isExpanded) => {
* let className = "my-tree-item";
* if (isFocused) {
* className += " focused";
* }
* return dom.div({
* className,
* },
* arrow,
* // And here is the label for this item.
* dom.span({ className: "my-tree-item-label" }, item.label)
* );
* },
*
* onExpand: item => dispatchExpandActionToRedux(item),
* onCollapse: item => dispatchCollapseActionToRedux(item),
* });
* }
* }
*/
class Tree extends Component {
static get propTypes() {
return {
// Required props
// A function to get an item's parent, or null if it is a root.
//
// Type: getParent(item: Item) -> Maybe<Item>
//
// Example:
//
// // The parent of this item is stored in its `parent` property.
// getParent: item => item.parent
getParent: PropTypes.func.isRequired,
// A function to get an item's children.
//
// Type: getChildren(item: Item) -> [Item]
//
// Example:
//
// // This item's children are stored in its `children` property.
// getChildren: item => item.children
getChildren: PropTypes.func.isRequired,
// A function which takes an item and ArrowExpander component instance and
// returns a component, or text, or anything else that React considers
// renderable.
//
// Type: renderItem(item: Item,
// depth: Number,
// isFocused: Boolean,
// arrow: ReactComponent,
// isExpanded: Boolean) -> ReactRenderable
//
// Example:
//
// renderItem: (item, depth, isFocused, arrow, isExpanded) => {
// let className = "my-tree-item";
// if (isFocused) {
// className += " focused";
// }
// return dom.div(
// {
// className,
// style: { marginLeft: depth * 10 + "px" }
// },
// arrow,
// dom.span({ className: "my-tree-item-label" }, item.label)
// );
// },
renderItem: PropTypes.func.isRequired,
// A function which returns the roots of the tree (forest).
//
// Type: getRoots() -> [Item]
//
// Example:
//
// // In this case, we only have one top level, root item. You could
// // return multiple items if you have many top level items in your
// // tree.
// getRoots: () => [this.props.rootOfMyTree]
getRoots: PropTypes.func.isRequired,
// A function to get a unique key for the given item. This helps speed up
// React's rendering a *TON*.
//
// Type: getKey(item: Item) -> String
//
// Example:
//
// getKey: item => `my-tree-item-${item.uniqueId}`
getKey: PropTypes.func.isRequired,
// A function to get whether an item is expanded or not. If an item is not
// expanded, then it must be collapsed.
//
// Type: isExpanded(item: Item) -> Boolean
//
// Example:
//
// isExpanded: item => item.expanded,
isExpanded: PropTypes.func.isRequired,
// Optional props
// The currently focused item, if any such item exists.
focused: PropTypes.any,
// Handle when a new item is focused.
onFocus: PropTypes.func,
// The depth to which we should automatically expand new items.
autoExpandDepth: PropTypes.number,
// Should auto expand all new items or just the new items under the first
// root item.
autoExpandAll: PropTypes.bool,
// Auto expand a node only if number of its children
// are less than autoExpandNodeChildrenLimit
autoExpandNodeChildrenLimit: PropTypes.number,
// Note: the two properties below are mutually exclusive. Only one of the
// label properties is necessary.
// ID of an element whose textual content serves as an accessible label
// for a tree.
labelledby: PropTypes.string,
// Accessibility label for a tree widget.
label: PropTypes.string,
// Optional event handlers for when items are expanded or collapsed.
// Useful for dispatching redux events and updating application state,
// maybe lazily loading subtrees from a worker, etc.
//
// Type:
// onExpand(item: Item)
// onCollapse(item: Item)
//
// Example:
//
// onExpand: item => dispatchExpandActionToRedux(item)
onExpand: PropTypes.func,
onCollapse: PropTypes.func,
// Optional event handler called with the current focused node when the
// Enter key is pressed. Can be useful to allow further keyboard actions
// within the tree node.
onActivate: PropTypes.func,
isExpandable: PropTypes.func,
// Additional classes to add to the root element.
className: PropTypes.string,
// style object to be applied to the root element.
style: PropTypes.object,
// Prevents blur when Tree loses focus
preventBlur: PropTypes.bool
};
}
static get defaultProps() {
return {
autoExpandDepth: AUTO_EXPAND_DEPTH,
autoExpandAll: true
};
}
constructor(props) {
super(props);
this.state = {
seen: new Set()
};
this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this);
this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this);
this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this);
this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this);
this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(
this
);
this._focusFirstNode = oncePerAnimationFrame(this._focusFirstNode).bind(
this
);
this._focusLastNode = oncePerAnimationFrame(this._focusLastNode).bind(this);
this._autoExpand = this._autoExpand.bind(this);
this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
this._dfs = this._dfs.bind(this);
this._dfsFromRoots = this._dfsFromRoots.bind(this);
this._focus = this._focus.bind(this);
this._scrollNodeIntoView = this._scrollNodeIntoView.bind(this);
this._onBlur = this._onBlur.bind(this);
this._onKeyDown = this._onKeyDown.bind(this);
this._nodeIsExpandable = this._nodeIsExpandable.bind(this);
this._activateNode = oncePerAnimationFrame(this._activateNode).bind(this);
}
componentDidMount() {
this._autoExpand();
if (this.props.focused) {
this._scrollNodeIntoView(this.props.focused);
// Always keep the focus on the tree itself.
this.treeRef.focus();
}
}
componentWillReceiveProps(nextProps) {
this._autoExpand();
}
componentDidUpdate(prevProps, prevState) {
if (this.props.focused && prevProps.focused !== this.props.focused) {
this._scrollNodeIntoView(this.props.focused);
// Always keep the focus on the tree itself.
this.treeRef.focus();
}
}
_autoExpand() {
const { autoExpandDepth, autoExpandNodeChildrenLimit } = this.props;
if (!autoExpandDepth) {
return;
}
// Automatically expand the first autoExpandDepth levels for new items. Do
// not use the usual DFS infrastructure because we don't want to ignore
// collapsed nodes.
const autoExpand = (item, currentDepth) => {
if (currentDepth >= autoExpandDepth || this.state.seen.has(item)) {
return;
}
const children = this.props.getChildren(item);
if (
autoExpandNodeChildrenLimit &&
children.length > autoExpandNodeChildrenLimit
) {
return;
}
this.props.onExpand(item);
this.state.seen.add(item);
const length = children.length;
for (let i = 0; i < length; i++) {
autoExpand(children[i], currentDepth + 1);
}
};
const roots = this.props.getRoots();
const length = roots.length;
if (this.props.autoExpandAll) {
for (let i = 0; i < length; i++) {
autoExpand(roots[i], 0);
}
} else if (length != 0) {
autoExpand(roots[0], 0);
}
}
_preventArrowKeyScrolling(e) {
switch (e.key) {
case "ArrowUp":
case "ArrowDown":
case "ArrowLeft":
case "ArrowRight":
e.preventDefault();
e.stopPropagation();
if (e.nativeEvent) {
if (e.nativeEvent.preventDefault) {
e.nativeEvent.preventDefault();
}
if (e.nativeEvent.stopPropagation) {
e.nativeEvent.stopPropagation();
}
}
}
}
/**
* Perform a pre-order depth-first search from item.
*/
_dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) {
traversal.push({ item, depth: _depth });
if (!this.props.isExpanded(item)) {
return traversal;
}
const nextDepth = _depth + 1;
if (nextDepth > maxDepth) {
return traversal;
}
const children = this.props.getChildren(item);
const length = children.length;
for (let i = 0; i < length; i++) {
this._dfs(children[i], maxDepth, traversal, nextDepth);
}
return traversal;
}
/**
* Perform a pre-order depth-first search over the whole forest.
*/
_dfsFromRoots(maxDepth = Infinity) {
const traversal = [];
const roots = this.props.getRoots();
const length = roots.length;
for (let i = 0; i < length; i++) {
this._dfs(roots[i], maxDepth, traversal);
}
return traversal;
}
/**
* Expands current row.
*
* @param {Object} item
* @param {Boolean} expandAllChildren
*/
_onExpand(item, expandAllChildren) {
if (this.props.onExpand) {
this.props.onExpand(item);
if (expandAllChildren) {
const children = this._dfs(item);
const length = children.length;
for (let i = 0; i < length; i++) {
this.props.onExpand(children[i].item);
}
}
}
}
/**
* Collapses current row.
*
* @param {Object} item
*/
_onCollapse(item) {
if (this.props.onCollapse) {
this.props.onCollapse(item);
}
}
/**
* Sets the passed in item to be the focused item.
*
* @param {Object|undefined} item
* The item to be focused, or undefined to focus no item.
*
* @param {Object|undefined} options
* An options object which can contain:
* - dir: "up" or "down" to indicate if we should scroll the element
* to the top or the bottom of the scrollable container when
* the element is off canvas.
*/
_focus(item, options = {}) {
const { preventAutoScroll } = options;
if (item && !preventAutoScroll) {
this._scrollNodeIntoView(item, options);
}
if (this.props.onFocus) {
this.props.onFocus(item);
}
}
/**
* Sets the passed in item to be the focused item.
*
* @param {Object|undefined} item
* The item to be scrolled to.
*
* @param {Object|undefined} options
* An options object which can contain:
* - dir: "up" or "down" to indicate if we should scroll the element
* to the top or the bottom of the scrollable container when
* the element is off canvas.
*/
_scrollNodeIntoView(item, options = {}) {
if (item !== undefined) {
const treeElement = this.treeRef;
const element = document.getElementById(this.props.getKey(item));
if (element) {
const { top, bottom } = element.getBoundingClientRect();
const closestScrolledParent = node => {
if (node == null) {
return null;
}
if (node.scrollHeight > node.clientHeight) {
return node;
}
return closestScrolledParent(node.parentNode);
};
const scrolledParent = closestScrolledParent(treeElement);
const scrolledParentRect = scrolledParent
? scrolledParent.getBoundingClientRect()
: null;
const isVisible =
!scrolledParent ||
(top >= scrolledParentRect.top &&
bottom <= scrolledParentRect.bottom);
if (!isVisible) {
const { alignTo } = options;
const scrollToTop = alignTo
? alignTo === "top"
: !scrolledParentRect || top < scrolledParentRect.top;
element.scrollIntoView(scrollToTop);
}
}
}
}
/**
* Sets the state to have no focused item.
*/
_onBlur() {
if (!this.props.preventBlur) {
this._focus(undefined);
}
}
/**
* Handles key down events in the tree's container.
*
* @param {Event} e
*/
_onKeyDown(e) {
if (this.props.focused == null) {
return;
}
// Allow parent nodes to use navigation arrows with modifiers.
if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
return;
}
this._preventArrowKeyScrolling(e);
switch (e.key) {
case "ArrowUp":
this._focusPrevNode();
return;
case "ArrowDown":
this._focusNextNode();
return;
case "ArrowLeft":
if (
this.props.isExpanded(this.props.focused) &&
this._nodeIsExpandable(this.props.focused)
) {
this._onCollapse(this.props.focused);
} else {
this._focusParentNode();
}
return;
case "ArrowRight":
if (
this._nodeIsExpandable(this.props.focused) &&
!this.props.isExpanded(this.props.focused)
) {
this._onExpand(this.props.focused);
} else {
this._focusNextNode();
}
return;
case "Home":
this._focusFirstNode();
return;
case "End":
this._focusLastNode();
return;
case "Enter":
this._activateNode();
}
}
/**
* Sets the previous node relative to the currently focused item, to focused.
*/
_focusPrevNode() {
// Start a depth first search and keep going until we reach the currently
// focused node. Focus the previous node in the DFS, if it exists. If it
// doesn't exist, we're at the first node already.
let prev;
const traversal = this._dfsFromRoots();
const length = traversal.length;
for (let i = 0; i < length; i++) {
const item = traversal[i].item;
if (item === this.props.focused) {
break;
}
prev = item;
}
if (prev === undefined) {
return;
}
this._focus(prev, { alignTo: "top" });
}
/**
* Handles the down arrow key which will focus either the next child
* or sibling row.
*/
_focusNextNode() {
// Start a depth first search and keep going until we reach the currently
// focused node. Focus the next node in the DFS, if it exists. If it
// doesn't exist, we're at the last node already.
const traversal = this._dfsFromRoots();
const length = traversal.length;
let i = 0;
while (i < length) {
if (traversal[i].item === this.props.focused) {
break;
}
i++;
}
if (i + 1 < traversal.length) {
this._focus(traversal[i + 1].item, { alignTo: "bottom" });
}
}
/**
* Handles the left arrow key, going back up to the current rows'
* parent row.
*/
_focusParentNode() {
const parent = this.props.getParent(this.props.focused);
if (!parent) {
this._focusPrevNode(this.props.focused);
return;
}
this._focus(parent, { alignTo: "top" });
}
_focusFirstNode() {
const traversal = this._dfsFromRoots();
this._focus(traversal[0].item, { alignTo: "top" });
}
_focusLastNode() {
const traversal = this._dfsFromRoots();
const lastIndex = traversal.length - 1;
this._focus(traversal[lastIndex].item, { alignTo: "bottom" });
}
_activateNode() {
if (this.props.onActivate) {
this.props.onActivate(this.props.focused);
}
}
_nodeIsExpandable(item) {
return this.props.isExpandable
? this.props.isExpandable(item)
: !!this.props.getChildren(item).length;
}
render() {
const traversal = this._dfsFromRoots();
const { focused } = this.props;
const nodes = traversal.map((v, i) => {
const { item, depth } = traversal[i];
const key = this.props.getKey(item, i);
return TreeNodeFactory({
key,
id: key,
index: i,
item,
depth,
renderItem: this.props.renderItem,
focused: focused === item,
expanded: this.props.isExpanded(item),
isExpandable: this._nodeIsExpandable(item),
onExpand: this._onExpand,
onCollapse: this._onCollapse,
onClick: e => {
// We can stop the propagation since click handler on the node can be
// created in `renderItem`.
e.stopPropagation();
// Since the user just clicked the node, there's no need to check if
// it should be scrolled into view.
this._focus(item, { preventAutoScroll: true });
if (this.props.isExpanded(item)) {
this.props.onCollapse(item);
} else {
this.props.onExpand(item, e.altKey);
}
}
});
});
const style = Object.assign({}, this.props.style || {}, {
padding: 0,
margin: 0
});
return dom.div(
{
className: `tree ${this.props.className ? this.props.className : ""}`,
ref: el => {
this.treeRef = el;
},
role: "tree",
tabIndex: "0",
onKeyDown: this._onKeyDown,
onKeyPress: this._preventArrowKeyScrolling,
onKeyUp: this._preventArrowKeyScrolling,
onFocus: ({ nativeEvent }) => {
if (focused || !nativeEvent || !this.treeRef) {
return;
}
const { explicitOriginalTarget } = nativeEvent;
// Only set default focus to the first tree node if the focus came
// from outside the tree (e.g. by tabbing to the tree from other
// external elements).
if (
explicitOriginalTarget !== this.treeRef &&
!this.treeRef.contains(explicitOriginalTarget)
) {
this._focus(traversal[0].item);
}
},
onBlur: this._onBlur,
"aria-label": this.props.label,
"aria-labelledby": this.props.labelledby,
"aria-activedescendant": focused && this.props.getKey(focused),
style
},
nodes
);
}
}
export default Tree;

Просмотреть файл

@ -0,0 +1,5 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
require("./tree");

Просмотреть файл

@ -0,0 +1,244 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
const { Component, createFactory, createElement } = React;
import Components from "../index";
const Tree = createFactory(Components.Tree);
import { storiesOf } from "@storybook/react";
storiesOf("Tree", module)
.add("Simple tree - autoExpand 1", () => {
return renderTree({
autoExpandDepth: 1,
});
})
.add("Simple tree - autoExpand 2", () => {
return renderTree({
autoExpandDepth: 2,
});
})
.add("Simple tree - autoExpand ∞", () => {
return renderTree({
autoExpandDepth: Infinity,
});
})
.add("Multiple root tree", () => {
return renderTree({
autoExpandDepth: Infinity,
getRoots: () => ["A", "P", "M", "Q", "W", "R"],
});
})
.add("focused node", () => {
return renderTree({
focused: "W",
getRoots: () => ["A", "W"],
expanded: new Set(["A"])
});
})
.add("variable height nodes", () => {
const nodes = Array.from({length: 10})
.map((_, i) => `item ${i + 1} - `.repeat(10 + Math.random() * 50));
return renderTree({
getRoots: () => ["ROOT"],
expanded: new Set(["ROOT"])
}, {
children: {"ROOT": nodes}
});
})
.add("scrollable tree", () => {
const nodes = Array.from({length: 500}).map((_, i) => (i + 1).toString());
class container extends Component {
constructor(props) {
super(props);
const state = {
expanded: new Set(),
focused: null
};
this.state = state;
}
render() {
return createElement("div", {},
createElement("label", {
style: {position: "fixed", right: 0},
},
"Enter node number to set focus on: ",
createElement("input", {
type: "number",
min: 1,
max: 500,
onInput: e => {
// Storing balue since `e` can be reused by the time the setState
// callback is called.
const value = e.target.value.toString();
this.setState(previousState => {
return {focused: value};
});
}
}),
),
createTreeElement({getRoots: () => nodes}, this, {})
);
}
}
return createFactory(container)();
})
.add("scrollable tree with focused node", () => {
const nodes = Array.from({length: 500}).map((_, i) => `item ${i + 1}`);
return renderTree({
focused: "item 250",
getRoots: () => nodes,
}, {});
})
.add("1000 items tree", () => {
const nodes = Array.from({length: 1000}).map((_, i) => `item-${i + 1}`);
return renderTree({
getRoots: () => ["ROOT"],
expanded: new Set()
}, {
children: {"ROOT": nodes}
});
})
.add("30,000 items tree", () => {
const nodes = Array.from({length: 1000}).map((_, i) => `item-${i + 1}`);
return renderTree({
getRoots: () => nodes,
expanded: new Set(Array.from({length: 2000}).map((_, i) => `item-${i + 1}`))
}, {
children: Array.from({length: 2000}).reduce((res, _, i) => {
res[`item-${i + 1}`] = [`item-${i + 1001}`];
return res;
}, {})
});
});
// Encoding of the following tree/forest:
//
// A
// |-- B
// | |-- E
// | | |-- K
// | | `-- L
// | |-- F
// | `-- G
// |-- C
// | |-- H
// | `-- I
// `-- D
// `-- J
// M
// `-- N
// `-- O
// P
// Q
// R
// W
// `-- X
// `-- Z
// `-- Y
const TEST_TREE = {
children: {
A: ["B", "C", "D"],
B: ["E", "F", "G"],
C: ["H", "I"],
D: ["J"],
E: ["K", "L"],
F: [],
G: [],
H: [],
I: [],
J: [],
K: [],
L: [],
M: ["N"],
N: ["O"],
O: [],
P: [],
Q: [],
R: [],
W: ["X", "Y"],
X: ["Z"],
Y: [],
Z: [],
},
parent: {
A: null,
B: "A",
C: "A",
D: "A",
E: "B",
F: "B",
G: "B",
H: "C",
I: "C",
J: "D",
K: "E",
L: "E",
M: null,
N: "M",
O: "N",
P: null,
Q: null,
R: null,
W: null,
X: "W",
Y: "W",
Z: "X",
},
};
function renderTree(props, tree = TEST_TREE) {
class container extends Component {
constructor(props2) {
super(props2);
const state = {
expanded: props2.expanded || new Set(),
focused: props2.focused
};
delete props2.focused;
this.state = state;
}
render() {
return createTreeElement(props, this, tree);
}
}
return createFactory(container)();
}
function createTreeElement(props, context, tree) {
return Tree(Object.assign({
getParent: x => tree.parent && tree.parent[x],
getChildren: x => tree.children && tree.children[x]
? tree.children[x]
: [],
renderItem: (x, depth, focused, arrow, expanded) => [arrow, x],
getRoots: () => ["A"],
getKey: x => "key-" + x,
onFocus: x => {
context.setState(previousState => {
return {focused: x};
});
},
onExpand: x => {
context.setState(previousState => {
const expanded = new Set(previousState.expanded);
expanded.add(x);
return {expanded};
});
},
onCollapse: x => {
context.setState(previousState => {
const expanded = new Set(previousState.expanded);
expanded.delete(x);
return {expanded};
});
},
isExpanded: x => context.state && context.state.expanded.has(x),
focused: context.state.focused,
}, props));
}

Просмотреть файл

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

Просмотреть файл

@ -0,0 +1,104 @@
# DevTools Reps
![](http://g.recordit.co/IxhfRP8pNf.gif)
Reps is Firefox DevTools' remote object formatter. It stands for _representation_.
## Example
```js
const React = require("react");
let {
Rep,
Grip,
MODE,
ObjectInspector,
ObjectInspectorUtils
} = require("devtools-reps");
function renderRep({ object, mode }) {
return Rep({ object, defaultRep: Grip, mode });
}
ReactDOM.render(
Rep({ object, defaultRep: Grip, mode }),
document.createElement("div")
);
```
## What is what?
### `Rep`
`Rep` is the top-level component that is capable of formatting any type.
Supported types:
> RegExp, StyleSheet, Event, DateTime, TextNode, Attribute, Func, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, GripArray, GripMap, Grip, Undefined, Null, StringRep, Number, SymbolRep,
### `Grip`
`Grip` is a client representation of a remote JS object and is used as an input object for this rep component.
## Getting started
You need to clone the debugger.html repository, then install dependencies, for which you'll need the [Yarn](https://yarnpkg.com/en/) tool:
```
git clone https://github.com/devtools-html/debugger.html.git
cd debugger.html
yarn install
```
Once everything is installed, you can start the development server with:
```bash
cd packages/devtools-reps/
yarn start
```
and navigate to `http://localhost:8000` to access the dashboard.
## Running the demo app
Navigating to the above address will have landed you on an empty launchpad UI:
![Image of empty launchpad](./assets/images/empty-launchpad.png)
Click on the _Launch Firefox_ button. This should launch Firefox with a dedicated profile, listening for connections on port 6080.
The UI should update automatically and show you at least one tab for the new Firefox instance. If it doesn't, reload the dashboard.
![Image of launchpad](./assets/images/launchpad-app.png)
Click on any of the tabs. This should open the demo app:
![Image of demo app](./assets/images/demo-app.png)
Then you can type any expression in the input field. They will be evaluated against the target tab selected in the previous steps (so if there specific objects on window on this webpage, you can check how they are represented with reps etc, ...).
## Running the tests
Reps tests are written with jest.
They are run on every pull request with Circle CI, and you can run them locally by executing `yarn test` from /packages/devtools-reps.
## History
The Reps project was ported to Github January 18th, 2017. You can view the history of a file after that date on [github][history] or by running this query:
```bash
git log --before "2017-1-17" devtools/client/shared/components/reps
```
They were first moved to the [devtools-reps][gh-devtools-reps] repository, then to the [devtools-core][gh-devtools-core] one, before being migrated to this repository.
[history]: https://github.com/mozilla/gecko-dev/commits/master/devtools/client/shared/components/reps
[gh-devtools-reps]:
https://github.com/devtools-html/reps/commits/master
[gh-devtools-core]:
https://github.com/devtools-html/devtools-core/commits/5ba3d6f6a44def9978a983edd6f2f89747dca2c7/packages/devtools-reps
## License
[MPL 2](./LICENSE)

Просмотреть файл

@ -0,0 +1 @@
The assets directory is mandatory to run `../bin/publish-assets.js`.

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 85 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 70 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 97 KiB

Просмотреть файл

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const path = require("path");
const toolbox = require("devtools-launchpad/index");
const serve = require("express-static");
const config = require("../config");
let webpackConfig = require("../webpack.config");
let { app } = toolbox.startDevServer(config, webpackConfig, __dirname);
// Serve devtools-reps images
app.use(
"/devtools-reps/images/",
serve(path.join(__dirname, "../src/shared/images"))
);
// As well as devtools-components ones, with a different path, which we are going to
// write in the postCSS config in development mode.
app.use(
"/devtools-components/images/",
serve(path.join(__dirname, "../../../node_modules/devtools-components/src/images"))
);

Просмотреть файл

@ -0,0 +1,16 @@
module.exports = {
"title": "Reps",
"hotReloading": true,
"defaultURL": "https://nchevobbe.github.io/demo/console-test-app.html",
"environment": "development",
"theme": "light",
"firefox": {
"webSocketConnection": false,
"host": "localhost",
"webSocketPort": 9000,
"tcpPort": 6080,
},
"development": {
"serverPort": 8000
}
};

Просмотреть файл

@ -0,0 +1,26 @@
const { resolve } = require("path");
const rootDir = resolve(__dirname, "src");
module.exports = {
rootDir,
displayName: "devtools-reps test",
setupFiles: [
"<rootDir>/../../../src/test/__mocks__/request-animation-frame.js",
"<rootDir>/test/__mocks__/selection.js",
"<rootDir>/test/setup.js"
],
setupTestFrameworkScriptFile: "<rootDir>/test/setup-file.js",
testMatch: ["**/tests/**/*.js"],
testPathIgnorePatterns: [
"/node_modules/",
"<rootDir>/test/",
"<rootDir>/reps/tests/test-helpers",
"<rootDir>/utils/tests/fixtures/",
"<rootDir>/object-inspector/tests/__mocks__/",
"<rootDir>/object-inspector/tests/test-utils"
],
testURL: "http://localhost/",
transformIgnorePatterns: ["node_modules/(?!devtools-)"],
moduleNameMapper: {
"\\.css$": "<rootDir>/../../../src/test/__mocks__/styleMock.js"
}
};

Просмотреть файл

@ -0,0 +1,56 @@
{
"name": "devtools-reps",
"version": "0.23.0",
"description": "Devtools Reps",
"main": "src/index.js",
"scripts": {
"start": "node bin/dev-server.js",
"lint-js": "eslint src",
"firefox": "./node_modules/.bin/start-firefox --start --location https://devtools-html.github.io/debugger-examples/",
"chrome": "./node_modules/.bin/start-chrome",
"license-check": "devtools-license-check",
"test": "jest --projects jest.config.js"
},
"author": "",
"license": "MPL-2.0",
"repository": {
"url": "git://github.com/devtools-html/reps.git",
"type": "git"
},
"engineStrict": true,
"engines": {
"node": ">=8.9.4"
},
"dependencies": {
"classnames": "^2.2.5",
"devtools-components": "^0.6.0",
"lodash": "^4.17.2",
"prop-types": "^15.6.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-dom-factories": "^1.0.2",
"react-redux": "^5.0.7",
"redux": "^3.7.2"
},
"devDependencies": {
"@sucrase/webpack-object-rest-spread-plugin": "^1.0.0",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-preset-react": "^6.24.1",
"devtools-config": "^0.0.16",
"devtools-launchpad": "^0.0.141",
"devtools-license-check": "^0.7.0",
"devtools-modules": "~1.1.0",
"devtools-services": "^0.0.1",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "^3.3.1",
"eslint": "^5.0.0",
"eslint-plugin-mozilla": "1.0.4",
"fs-extra": "^7.0.0",
"immutable": "^3.8.2",
"postcss-url-mapper": "^1.2.0",
"react-immutable-proptypes": "^2.1.0",
"redux-logger": "=3.0.6"
}
}

Просмотреть файл

@ -0,0 +1,35 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const mapUrl = require("postcss-url-mapper");
const MC_PATH = "resource://devtools/client/shared/components/reps/images/";
const EXPRESS_PATH = "/devtools-reps/images/";
const IMAGES = ["open-inspector.svg", "jump-definition.svg", "input.svg"];
function mapUrlProduction(url, type) {
for (const img of IMAGES) {
url = url.replace(`/images/${img}`, `${MC_PATH}${img}`);
}
return url;
}
function mapUrlDevelopment(url) {
for (const img of IMAGES) {
url = url.replace(`/images/${img}`, `${EXPRESS_PATH}${img}`);
}
return url;
}
module.exports = ({ file, options, env }) => {
if (env === "production") {
return {
plugins: [mapUrl(mapUrlProduction)]
};
}
return {
plugins: [mapUrl(mapUrlDevelopment)]
};
};

Просмотреть файл

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { MODE } = require("./reps/constants");
const { REPS, getRep } = require("./reps/rep");
const objectInspector = require("./object-inspector");
const {
parseURLEncodedText,
parseURLParams,
maybeEscapePropertyName,
getGripPreviewItems
} = require("./reps/rep-utils");
module.exports = {
REPS,
getRep,
MODE,
maybeEscapePropertyName,
parseURLEncodedText,
parseURLParams,
getGripPreviewItems,
objectInspector
};

Просмотреть файл

@ -0,0 +1,76 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const constants = require("../constants");
const { generateKey } = require("../utils/utils");
function evaluateInput(input) {
return async function({ dispatch, client }) {
try {
const packet = await client.clientCommands.evaluate(input);
dispatch(addExpression(input, packet));
} catch (err) {
console.warn("Error when evaluating expression", err);
}
};
}
function addExpression(input, packet) {
return {
key: generateKey(),
type: constants.ADD_EXPRESSION,
value: {
input,
packet
}
};
}
function clearExpressions() {
return {
type: constants.CLEAR_EXPRESSIONS
};
}
function showResultPacket(key) {
return {
key,
type: constants.SHOW_RESULT_PACKET
};
}
function hideResultPacket(key) {
return {
key,
type: constants.HIDE_RESULT_PACKET
};
}
function createObjectClient(grip) {
return function({ dispatch, client }) {
return client.getObjectClient(grip);
};
}
function createLongStringClient(grip) {
return function({ dispatch, client }) {
return client.getLongStringClient(grip);
};
}
function releaseActor(actor) {
return function({ dispatch, client }) {
client.releaseActor(actor);
};
}
module.exports = {
addExpression,
clearExpressions,
evaluateInput,
showResultPacket,
hideResultPacket,
createObjectClient,
createLongStringClient,
releaseActor
};

Просмотреть файл

@ -0,0 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const expressions = require("./expressions");
const input = require("./input");
module.exports = {
...expressions,
...input
};

Просмотреть файл

@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const constants = require("../constants");
const expressionsActions = require("./expressions");
const { generateKey } = require("../utils/utils");
function addInput(input) {
return ({ dispatch }) => {
dispatch(expressionsActions.evaluateInput(input));
dispatch({
key: generateKey(),
type: constants.ADD_INPUT,
value: input
});
};
}
function changeCurrentInput(input) {
return {
type: constants.CHANGE_CURRENT_INPUT,
value: input
};
}
function navigateInputHistory(dir) {
return {
type: constants.NAVIGATE_INPUT_HISTORY,
value: dir
};
}
module.exports = {
addInput,
changeCurrentInput,
navigateInputHistory
};

Просмотреть файл

@ -0,0 +1,89 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
main {
--default-border: 1px solid var(--theme-splitter-color);
height: 100vh;
display: flex;
flex-direction: column;
}
.expressions {
padding: 2em 1em;
flex: 1;
overflow-y: auto;
}
.rep-row {
border: var(--default-border);
margin-bottom: 1em;
}
.rep-input {
font-family: monospace;
background-color: var(--theme-toolbar-background-alt);
color: var(--theme-body-color);
padding: 0.5rem;
}
.rep-input:before {
content: "➜ ";
}
.reps {
display: flex;
flex-wrap: wrap;
padding: 1rem;
margin-bottom: 0.5rem;
}
.rep-element {
white-space: pre-wrap;
word-wrap: break-word;
}
.rep-element + .rep-element {
margin-left: 0.5rem;
}
.rep-element[data-mode]::before {
content: attr(data-mode)":";
background-color: var(--theme-toolbar-background);
font-family: monospace;
display: inline-block;
font-size: 0.75rem;
padding: 0.1rem 0.25rem;
margin-right: 0.25rem;
border-radius: 0.25rem;
}
.packet header {
display: flex;
background-color: var(--theme-toolbar-background-alt);
border-top: var(--default-border);
color: var(--theme-body-color);
padding: 0.5rem;
}
.packet header::before {
display: inline-block;
padding-right: 0.5rem;
}
.packet header.packet-expanded::before {
content: "";
}
.packet header.packet-collapsed::before {
content: "+";
}
.copy-packet-button {
margin-left: auto;
margin-right: 0.5rem;
}
.packet .packet-rep {
padding: 1rem;
}

Просмотреть файл

@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const React = require("react");
const { Component, createFactory } = React;
const PropTypes = require("prop-types");
const dom = require("react-dom-factories");
const { KeyShortcuts } = require("devtools-modules");
const shortcuts = new KeyShortcuts({ window });
const { connect } = require("react-redux");
const { bindActionCreators } = require("redux");
const selectors = require("../selectors");
const Header = createFactory(require("./Header"));
const ResultsList = createFactory(require("./ResultsList"));
require("./Console.css");
class Console extends Component {
static get propTypes() {
return {
addInput: PropTypes.func.isRequired,
changeCurrentInput: PropTypes.func.isRequired,
clearExpressions: PropTypes.func.isRequired,
currentInputValue: PropTypes.string.isRequired,
evaluateInput: PropTypes.func.isRequired,
expressions: PropTypes.object.isRequired,
hideResultPacket: PropTypes.func.isRequired,
navigateInputHistory: PropTypes.func.isRequired,
showResultPacket: PropTypes.func.isRequired
};
}
componentDidMount() {
shortcuts.on("CmdOrCtrl+Shift+L", this.props.clearExpressions);
}
componentWillUnmount() {
shortcuts.off("CmdOrCtrl+Shift+L");
}
render() {
const {
addInput,
changeCurrentInput,
clearExpressions,
currentInputValue,
evaluateInput,
expressions,
hideResultPacket,
navigateInputHistory,
showResultPacket
} = this.props;
return dom.main(
{},
Header({
addInput,
changeCurrentInput,
clearResultsList: clearExpressions,
currentInputValue,
evaluate: evaluateInput,
navigateInputHistory
}),
ResultsList({
expressions: expressions.reverse(),
hideResultPacket,
showResultPacket
})
);
}
}
function mapStateToProps(state) {
return {
expressions: selectors.getExpressions(state),
currentInputValue: selectors.getCurrentInputValue(state)
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(require("../actions"), dispatch);
}
module.exports = connect(
mapStateToProps,
mapDispatchToProps
)(Console);

Просмотреть файл

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.console-header {
background: var(--theme-highlight-pink);
color: white;
margin: 0;
}
.console-header form {
display: flex;
padding: 1rem;
}
.console-header h1 {
font-size: 14px;
font-weight: 100;
color: white;
}
.console-header input {
background-color: var(--theme-highlight-pink);
flex: 1;
border: 1px solid white;
color: white;
font-size: 1.25rem;
padding: 0.25rem;
line-height: 2rem;
padding-left: 1rem;
margin: 0 1rem;
}
.console-header ::-webkit-input-placeholder {
color: white;
font-size: 1rem;
line-height: 2rem;
margin: 0;
}
.console-header .clear-button {
background: rgba(255, 255, 255, 0.25);
border: 1px solid rgba(255, 255, 255, 0.25);
color: white;
font-size: 1.2rem;
}

Просмотреть файл

@ -0,0 +1,87 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const React = require("react");
const { Component, createFactory } = React;
const PropTypes = require("prop-types");
const dom = require("react-dom-factories");
const constants = require("../constants");
const QuickLinks = createFactory(require("./QuickLinks"));
require("./Header.css");
class Header extends Component {
static get propTypes() {
return {
addInput: PropTypes.func.isRequired,
changeCurrentInput: PropTypes.func.isRequired,
clearResultsList: PropTypes.func.isRequired,
currentInputValue: PropTypes.string,
evaluate: PropTypes.func.isRequired,
navigateInputHistory: PropTypes.func.isRequired
};
}
constructor(props) {
super(props);
this.onSubmitForm = this.onSubmitForm.bind(this);
this.onInputChange = this.onInputChange.bind(this);
this.onInputKeyDown = this.onInputKeyDown.bind(this);
this.onClearButtonClick = this.onClearButtonClick.bind(this);
}
onSubmitForm(e) {
e.preventDefault();
const data = new FormData(e.target);
const expression = data.get("expression");
this.props.addInput(expression);
}
onInputChange(e) {
this.props.changeCurrentInput(e.target.value);
}
onInputKeyDown(e) {
if (["ArrowUp", "ArrowDown"].includes(e.key)) {
this.props.navigateInputHistory(
e.key === "ArrowUp" ? constants.DIR_BACKWARD : constants.DIR_FORWARD
);
}
}
onClearButtonClick(e) {
this.props.clearResultsList();
}
render() {
const { currentInputValue, evaluate } = this.props;
return dom.header(
{ className: "console-header" },
dom.form(
{ onSubmit: this.onSubmitForm },
dom.h1({}, "Reps"),
dom.input({
type: "text",
placeholder: "Enter an expression",
name: "expression",
value: currentInputValue || "",
autoFocus: true,
onChange: this.onInputChange,
onKeyDown: this.onInputKeyDown
}),
dom.button(
{
className: "clear-button",
type: "button",
onClick: this.onClearButtonClick
},
"Clear"
)
),
QuickLinks({ evaluate })
);
}
}
module.exports = Header;

Просмотреть файл

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.quick-links {
display: flex;
justify-content: center;
flex-wrap: wrap;
background: rgba(255, 255, 255, 0.25);
padding: 0.5rem 1rem;
}
.quick-links button {
font-size: 1.2rem;
background: none;
border: none;
color: white;
margin: 0.5rem 0.25rem;
cursor: pointer;
}

Просмотреть файл

@ -0,0 +1,54 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const React = require("react");
const { Component } = React;
const PropTypes = require("prop-types");
const dom = require("react-dom-factories");
require("./QuickLinks.css");
const samples = require("../samples.js");
class QuickLinks extends Component {
static get propTypes() {
return {
evaluate: PropTypes.func.isRequired
};
}
constructor(props) {
super(props);
this.evaluateExpressions = this.evaluateExpressions.bind(this);
this.renderLinks = this.renderLinks.bind(this);
}
evaluateExpressions(expressions) {
expressions.forEach(expression => this.props.evaluate(expression));
}
renderLinks() {
return Object.keys(samples).map(label => {
const expressions = samples[label];
const length = expressions.length;
return dom.button(
{
key: label,
title:
label === "yolo"
? "Add all sample expressions"
: `Add ${length} ${label} sample expression${
length > 1 ? "s" : ""
}`,
onClick: () => this.evaluateExpressions(expressions)
},
label
);
});
}
render() {
return dom.div({ className: "quick-links" }, this.renderLinks());
}
}
module.exports = QuickLinks;

Просмотреть файл

@ -0,0 +1,145 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const React = require("react");
const { Component } = React;
const PropTypes = require("prop-types");
const dom = require("react-dom-factories");
const { MODE } = require("../../reps/constants");
const { ObjectInspector } = require("../../index").objectInspector;
const { Rep } = require("../../reps/rep");
class Result extends Component {
static get propTypes() {
return {
expression: PropTypes.object.isRequired,
showResultPacket: PropTypes.func.isRequired,
hideResultPacket: PropTypes.func.isRequired
};
}
constructor(props) {
super(props);
this.copyPacketToClipboard = this.copyPacketToClipboard.bind(this);
this.onHeaderClick = this.onHeaderClick.bind(this);
this.renderRepInAllModes = this.renderRepInAllModes.bind(this);
this.renderRep = this.renderRep.bind(this);
this.renderPacket = this.renderPacket.bind(this);
}
copyPacketToClipboard(e, packet) {
e.stopPropagation();
const textField = document.createElement("textarea");
textField.innerHTML = JSON.stringify(packet, null, " ");
document.body.appendChild(textField);
textField.select();
document.execCommand("copy");
textField.remove();
}
onHeaderClick() {
const { expression } = this.props;
if (expression.showPacket === true) {
this.props.hideResultPacket();
} else {
this.props.showResultPacket();
}
}
renderRepInAllModes({ object }) {
return Object.keys(MODE).map(modeKey =>
this.renderRep({ object, modeKey })
);
}
renderRep({ object, modeKey }) {
const path = Symbol(modeKey + object.actor);
return dom.div(
{
className: "rep-element",
key: path.toString(),
"data-mode": modeKey
},
ObjectInspector({
roots: [
{
path,
contents: {
value: object
}
}
],
autoExpandDepth: 0,
mode: MODE[modeKey],
// The following properties are optional function props called by the
// objectInspector on some occasions. Here we pass dull functions that
// only logs the parameters with which the callback was called.
onCmdCtrlClick: (node, { depth, event, focused, expanded }) =>
console.log("CmdCtrlClick", {
node,
depth,
event,
focused,
expanded
}),
onInspectIconClick: nodeFront =>
console.log("inspectIcon click", { nodeFront }),
onViewSourceInDebugger: location =>
console.log("onViewSourceInDebugger", { location }),
recordTelemetryEvent: (eventName, extra = {}) => {
console.log("📊", eventName, "📊", extra);
}
})
);
}
renderPacket(expression) {
const { packet, showPacket } = expression;
const headerClassName = showPacket ? "packet-expanded" : "packet-collapsed";
const headerLabel = showPacket
? "Hide expression packet"
: "Show expression packet";
return dom.div(
{ className: "packet" },
dom.header(
{
className: headerClassName,
onClick: this.onHeaderClick
},
headerLabel,
showPacket &&
dom.button(
{
className: "copy-packet-button",
onClick: e => this.copyPacketToClipboard(e, packet)
},
"Copy as JSON"
)
),
showPacket &&
dom.div({ className: "packet-rep" }, Rep({ object: packet }))
);
}
render() {
const { expression } = this.props;
const { input, packet } = expression;
return dom.div(
{ className: "rep-row" },
dom.div({ className: "rep-input" }, input),
dom.div(
{ className: "reps" },
this.renderRepInAllModes({
object: packet.exception || packet.result
})
),
this.renderPacket(expression)
);
}
}
module.exports = Result;

Просмотреть файл

@ -0,0 +1,42 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const React = require("react");
const { Component, createFactory } = React;
const PropTypes = require("prop-types");
const dom = require("react-dom-factories");
const ImPropTypes = require("react-immutable-proptypes");
const Result = createFactory(require("./Result"));
class ResultsList extends Component {
static get propTypes() {
return {
expressions: ImPropTypes.map.isRequired,
showResultPacket: PropTypes.func.isRequired,
hideResultPacket: PropTypes.func.isRequired
};
}
render() {
const { expressions, showResultPacket, hideResultPacket } = this.props;
return dom.div(
{ className: "expressions" },
expressions
.entrySeq()
.toJS()
.map(([key, expression]) =>
Result({
key,
expression: expression.toJS(),
showResultPacket: () => showResultPacket(key),
hideResultPacket: () => hideResultPacket(key)
})
)
);
}
}
module.exports = ResultsList;

Просмотреть файл

@ -0,0 +1,19 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
module.exports = {
ADD_EXPRESSION: Symbol("ADD_EXPRESSION"),
LOAD_PROPERTIES: Symbol("LOAD_PROPERTIES"),
LOAD_ENTRIES: Symbol("LOAD_ENTRIES"),
EVALUATE_EXPRESSION: Symbol("EVALUATE_EXPRESSION"),
CLEAR_EXPRESSIONS: Symbol("CLEAR_EXPRESSIONS"),
SHOW_RESULT_PACKET: Symbol("SHOW_RESULT_PACKET"),
HIDE_RESULT_PACKET: Symbol("HIDE_RESULT_PACKET"),
ADD_INPUT: Symbol("ADD_INPUT"),
CHANGE_CURRENT_INPUT: Symbol("CHANGE_CURRENT_INPUT"),
NAVIGATE_INPUT_HISTORY: Symbol("NAVIGATE_INPUT_HISTORY"),
DIR_FORWARD: Symbol("DIR_FORWARD "),
DIR_BACKWARD: Symbol("DIR_BACKWARD"),
LS_EXPRESSIONS_KEY: "LS_EXPRESSIONS_KEY"
};

Просмотреть файл

@ -0,0 +1,62 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
// globals window, document
require("../reps/reps.css");
const React = require("react");
const ReactDOM = require("react-dom");
const { bootstrap, renderRoot } = require("devtools-launchpad");
const RepsConsole = require("./components/Console");
const { configureStore } = require("./store");
require("./launchpad.css");
function onConnect(connection) {
if (!connection) {
return;
}
const client = {
clientCommands: {
evaluate: input =>
new Promise(resolve => {
connection.tabConnection.tabTarget.activeConsole.evaluateJS(
input,
result => resolve(result)
);
})
},
createObjectClient: function(grip) {
return connection.tabConnection.threadClient.pauseGrip(grip);
},
createLongStringClient: function(grip) {
return connection.tabConnection.tabTarget.activeConsole.longString(grip);
},
releaseActor: function(actor) {
return connection.tabConnection.debuggerClient.release(actor);
}
};
const store = configureStore({
makeThunkArgs: (args, state) => ({ ...args, client }),
client
});
renderRoot(React, ReactDOM, RepsConsole, store);
}
function onConnectionError(e) {
const h1 = document.createElement("h1");
h1.innerText = `An error occured during the connection: «${e.message}»`;
console.warn("An error occured during the connection", e);
renderRoot(React, ReactDOM, h1);
}
bootstrap(React, ReactDOM)
.then(onConnect, onConnectionError)
.catch(onConnectionError);

Просмотреть файл

@ -0,0 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
}

Просмотреть файл

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const constants = require("../constants");
const Immutable = require("immutable");
const initialState = Immutable.Map();
function update(state = initialState, action) {
const { type, value, key } = action;
switch (type) {
case constants.ADD_EXPRESSION:
const newState = state.set(key, Immutable.Map(value));
window.localStorage.setItem(
constants.LS_EXPRESSIONS_KEY,
JSON.stringify(newState.toJS())
);
return newState;
case constants.CLEAR_EXPRESSIONS:
window.localStorage.removeItem(constants.LS_EXPRESSIONS_KEY);
return state.clear();
case constants.SHOW_RESULT_PACKET:
return state.mergeIn([key], { showPacket: true });
case constants.HIDE_RESULT_PACKET:
return state.mergeIn([key], { showPacket: false });
}
return state;
}
module.exports = update;

Просмотреть файл

@ -0,0 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const expressions = require("./expressions");
const input = require("./input");
const { objectInspector } = require("../../index");
module.exports = {
expressions,
input,
objectInspector: objectInspector.reducer.default
};

Просмотреть файл

@ -0,0 +1,63 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const constants = require("../constants");
const Immutable = require("immutable");
const initialState = Immutable.Map({
currentValue: "",
currentNavigationKey: null,
history: Immutable.OrderedMap()
});
function update(state = initialState, action) {
const { type, value, key } = action;
const currentValue = state.get("currentValue");
const currentNavigationKey = state.get("currentNavigationKey");
const history = state.get("history");
switch (type) {
case constants.ADD_INPUT:
return state.withMutations(map => {
map
.set("history", history.set(key, value))
.set("currentValue", "")
.set("currentNavigationKey", null);
});
case constants.CHANGE_CURRENT_INPUT:
return state.set("currentValue", value);
case constants.NAVIGATE_INPUT_HISTORY:
const keys = history.reverse().keySeq();
const navigationIndex = keys.indexOf(currentNavigationKey);
const dir = value;
const newNavigationIndex =
dir === constants.DIR_BACKWARD
? navigationIndex + 1
: navigationIndex - 1;
const newNavigationKey =
newNavigationIndex >= 0 ? keys.get(newNavigationIndex) : null;
const fallbackValue = dir === constants.DIR_BACKWARD ? currentValue : "";
const fallbackNavigationKey =
dir === constants.DIR_BACKWARD ? currentNavigationKey : -1;
return state.withMutations(map => {
map
.set("currentValue", history.get(newNavigationKey) || fallbackValue)
.set(
"currentNavigationKey",
newNavigationKey || fallbackNavigationKey
);
});
}
return state;
}
module.exports = update;

Просмотреть файл

@ -0,0 +1,196 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const samples = {
array: ['x = [1, "2", {three: 3}, []]', "x = []"],
boolean: ["true", "false"],
date: ["new Date()"],
function: ["x = () => { 2 }"],
node: [
`x = document.createElement("div");
x.setAttribute("id", "myNodeId");
x.setAttribute("class", "my-class and another");
x.textContent = "My node id";
x;`,
`x = document.createElementNS("http://www.w3.org/2000/svg", "clipPath");
x.setAttribute("id", "myNodeId");
x.setAttribute("class", "my-class and another");
x;`,
"document.createComment('my comment node')",
"document.createTextNode('foo')",
`x = document.createAttribute('foo');
x.value = "bar";
x;`
],
"map & sets": [
`
({
"small set": new Set([1,2,3,4]),
"small map": new Map([
["a", {suba: 1}],
[{bkey: "b"}, 2]]
),
"medium set": new Set(
Array.from({length: 20})
.map((el, i) => ({
[String.fromCharCode(65 + i)]: i + 1,
test: {
[i] : "item" + i
}
})
)),
"medium map": new Map(
Array
.from({length: 20})
.map((el, i) => [
{
[String.fromCharCode(65 + i)]: i + 1,
test: {[i] : "item" + i, body: document.body}
},
Symbol(i + 1)
])
),
"large set": new Set(
Array.from({length: 2000})
.map((el, i) => ({
[String.fromCharCode(65 + i)]: i + 1,
test: {
[i] : "item" + i
}
})
)),
"large map": new Map(
Array
.from({length: 2000})
.map((el, i) => [
{
[String.fromCharCode(65 + i)]: i + 1,
document
},
Symbol(i + 1)
])
),
})
`
],
number: ["1", "-1", "-3.14", "0", "-0", "Infinity", "-Infinity", "NaN"],
object: [
"x = {a: 2}",
`
Object.create(null, Object.getOwnPropertyDescriptors({
get myStringGetter() {
return "hello"
},
get myNumberGetter() {
return 123;
},
get myUndefinedGetter() {
return undefined;
},
get myNullGetter() {
return null;
},
get myObjectGetter() {
return {foo: "bar"}
},
get myArrayGetter() {
return Array.from({length: 100000}, (_, i) => i)
},
get myMapGetter() {
return new Map([["foo", {bar: "baz"}]])
},
get mySetGetter() {
return new Set([1, {bar: "baz"}]);
},
get myProxyGetter() {
var handler = { get: function(target, name) {
return name in target ? target[name] : 37; }
};
return new Proxy({a: 1}, handler);
},
get myThrowingGetter() {
return a.b.c.d.e.f;
},
get myLongStringGetter() {
return "ab ".repeat(1e5)
},
set mySetterOnly(x) {}
}))
`
],
promise: [
"Promise.resolve([1, 2, 3])",
"Promise.reject(new Error('This is wrong'))",
"new Promise(() => {})"
],
proxy: [
`
var handler = {
get: function(target, name) {
return name in target ?
target[name] :
37;
}
};
new Proxy({a: 1}, handler);
`
],
regexp: ["new RegExp('^[-]?[0-9]+[.]?[0-9]+$')"],
string: [
"'foo'",
"'bar\nbaz\nyup'",
"'http://example.com'",
"'blah'.repeat(10000)",
"'http://example.com '.repeat(1000)"
],
symbol: ["Symbol('foo')", "Symbol()"],
errors: [
"throw new Error('This is a simple error message.');",
`
var error = new Error('Complicated error message');
error.stack =
"unserializeProfileOfArbitraryFormat@http://localhost:4242/2969802751c9e11c0c2d.bundle.js:26705:11\\n" +
"_callee7$@http://localhost:4242/2969802751c9e11c0c2d.bundle.js:27948:27\\n" +
"tryCatch@http://localhost:4242/2969802751c9e11c0c2d.bundle.js:64198:37\\n" +
"invoke@http://localhost:4242/2969802751c9e11c0c2d.bundle.js:64432:22\\n" +
"defineIteratorMethods/</prototype[method]@http://localhost:4242/2969802751c9e11c0c2d.bundle.js:64250:16\\n" +
"step@http://localhost:4242/2969802751c9e11c0c2d.bundle.js:5257:22\\n" +
"step/<@http://localhost:4242/2969802751c9e11c0c2d.bundle.js:5268:13\\n" +
"run@http://localhost:4242/2969802751c9e11c0c2d.bundle.js:64973:22\\n" +
"notify/<@http://localhost:4242/2969802751c9e11c0c2d.bundle.js:64986:28\\n" +
"flush@http://localhost:4242/2969802751c9e11c0c2d.bundle.js:65282:9\\n";
error;
`,
`
var error = new Error('Complicated error message');
error.stack =
"onPacket@resource://devtools/shared/base-loader.js -> resource://devtools/shared/client/debugger-client.js:856:9\\n" +
"send/<@resource://devtools/shared/base-loader.js -> resource://devtools/shared/transport/transport.js:569:13\\n" +
"exports.makeInfallible/<@resource://devtools/shared/base-loader.js -> resource://devtools/shared/ThreadSafeDevToolsUtils.js:109:14\\n" +
"exports.makeInfallible/<@resource://devtools/shared/base-loader.js -> resource://devtools/shared/ThreadSafeDevToolsUtils.js:109:14\\n";
error;
`
]
};
samples.yolo = Object.keys(samples).reduce((res, key) => {
return [...res, ...samples[key]];
}, []);
module.exports = samples;

Просмотреть файл

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
function getExpressions(state) {
return state.expressions;
}
function getInputState(state) {
return state.input;
}
function getCurrentInputValue(state) {
return getInputState(state).get("currentValue");
}
module.exports = {
getCurrentInputValue,
getExpressions
};

Просмотреть файл

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { applyMiddleware, combineReducers, createStore } = require("redux");
const { logger } = require("redux-logger");
const { promise } = require("./utils/redux/middleware/promise");
const { thunk } = require("./utils/redux/middleware/thunk");
const reducers = require("./reducers");
function configureStore(options, client) {
return createStore(
combineReducers(reducers),
applyMiddleware(thunk(options.makeThunkArgs), promise, logger)
);
}
module.exports = {
configureStore
};

Просмотреть файл

@ -0,0 +1,57 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { executeSoon, filterByKey } = require("../../utils");
const PROMISE = (exports.PROMISE = "@@dispatch/promise");
let seqIdVal = 1;
function seqIdGen() {
return seqIdVal++;
}
function promiseMiddleware({ dispatch, getState }) {
return next => action => {
if (!action || !Object.keys(action).includes(PROMISE)) {
return next(action);
}
const promiseInst = action[PROMISE];
const seqId = seqIdGen().toString();
// Create a new action that doesn't have the promise field and has
// the `seqId` field that represents the sequence id
action = Object.assign(filterByKey(action, key => key !== PROMISE), {
seqId
});
dispatch(Object.assign({}, action, { status: "start" }));
// Return the promise so action creators can still compose if they want to.
return Promise.resolve(promiseInst)
.finally(() => new Promise(resolve => executeSoon(resolve)))
.then(
value => {
dispatch(
Object.assign({}, action, {
status: "done",
value: value
})
);
return value;
},
error => {
dispatch(
Object.assign({}, action, {
status: "error",
error: error.message || error
})
);
return Promise.reject(error);
}
);
};
}
exports.promise = promiseMiddleware;

Просмотреть файл

@ -0,0 +1,23 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/**
* A middleware that allows thunks (functions) to be dispatched. If
* it's a thunk, it is called with an argument that contains
* `dispatch`, `getState`, and any additional args passed in via the
* middleware constructure. This allows the action to create multiple
* actions (most likely asynchronously).
*/
function thunk(makeArgs) {
return ({ dispatch, getState }) => {
const args = { dispatch, getState };
return next => action => {
return typeof action === "function"
? action(makeArgs ? makeArgs(args, getState()) : args)
: next(action);
};
};
}
exports.thunk = thunk;

Просмотреть файл

@ -0,0 +1,60 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/**
* Returns an object with a promise and its resolve and reject function,
* so they can be called outside of the promise callback.
*
* @returns {{resolve: function, reject: function, promise: Promise}}
*/
function defer() {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return {
resolve,
reject,
promise
};
}
/**
* Takes a function and executes it on the next tick.
*
* @param function fn
*/
function executeSoon(fn) {
setTimeout(fn, 0);
}
/**
* Takes an object into another object,
* filtered on its keys by the given predicate.
*
* @param object obj
* @param function predicate
* @returns object
*/
function filterByKey(obj, predicate) {
return Object.keys(obj).reduce((res, key) => {
if (predicate(key)) {
return Object.assign(res, { [key]: obj[key] });
}
return res;
}, {});
}
function generateKey() {
return `${performance.now()}`;
}
module.exports = {
defer,
executeSoon,
filterByKey,
generateKey
};

Просмотреть файл

@ -0,0 +1,133 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
// @flow
import type { GripProperties, Node, Props, ReduxAction } from "./types";
const { loadItemProperties } = require("./utils/load-properties");
const { getLoadedProperties, getActors } = require("./reducer");
type Dispatch = ReduxAction => void;
type ThunkArg = {
getState: () => {},
dispatch: Dispatch
};
/**
* This action is responsible for expanding a given node, which also means that
* it will call the action responsible to fetch properties.
*/
function nodeExpand(node: Node, actor) {
return async ({ dispatch, getState }: ThunkArg) => {
dispatch({ type: "NODE_EXPAND", data: { node } });
dispatch(nodeLoadProperties(node, actor));
};
}
function nodeCollapse(node: Node) {
return {
type: "NODE_COLLAPSE",
data: { node }
};
}
/*
* This action checks if we need to fetch properties, entries, prototype and
* symbols for a given node. If we do, it will call the appropriate ObjectClient
* functions.
*/
function nodeLoadProperties(node: Node, actor) {
return async ({ dispatch, client, getState }: ThunkArg) => {
const state = getState();
const loadedProperties = getLoadedProperties(state);
if (loadedProperties.has(node.path)) {
return;
}
try {
const properties = await loadItemProperties(
node,
client.createObjectClient,
client.createLongStringClient,
loadedProperties
);
dispatch(nodePropertiesLoaded(node, actor, properties));
} catch (e) {
console.error(e);
}
};
}
function nodePropertiesLoaded(
node: Node,
actor?: string,
properties: GripProperties
) {
return {
type: "NODE_PROPERTIES_LOADED",
data: { node, actor, properties }
};
}
function closeObjectInspector() {
return async ({ getState, client }: ThunkArg) => {
releaseActors(getState(), client);
};
}
/*
* This action is dispatched when the `roots` prop, provided by a consumer of
* the ObjectInspector (inspector, console, ), is modified. It will clean the
* internal state properties (expandedPaths, loadedProperties, ) and release
* the actors consumed with the previous roots.
* It takes a props argument which reflects what is passed by the upper-level
* consumer.
*/
function rootsChanged(props: Props) {
return async ({ dispatch, client, getState }: ThunkArg) => {
releaseActors(getState(), client);
dispatch({
type: "ROOTS_CHANGED",
data: props
});
};
}
function releaseActors(state, client) {
const actors = getActors(state);
for (const actor of actors) {
client.releaseActor(actor);
}
}
function invokeGetter(node: Node, grip: object, getterName: string) {
return async ({ dispatch, client, getState }: ThunkArg) => {
try {
const objectClient = client.createObjectClient(grip);
const result = await objectClient.getPropertyValue(getterName);
dispatch({
type: "GETTER_INVOKED",
data: {
node,
result
}
});
} catch (e) {
console.error(e);
}
};
}
module.exports = {
closeObjectInspector,
invokeGetter,
nodeExpand,
nodeCollapse,
nodeLoadProperties,
nodePropertiesLoaded,
rootsChanged
};

Просмотреть файл

@ -0,0 +1,53 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.tree.object-inspector .node.object-node {
display: inline-block;
}
.tree.object-inspector .object-label,
.tree.object-inspector .object-label * {
color: var(--theme-highlight-blue);
}
.tree.object-inspector .node .unavailable {
color: var(--theme-comment);
}
.tree.object-inspector .lessen,
.tree.object-inspector .lessen *,
.tree.object-inspector .lessen .object-label,
.tree.object-inspector .lessen .object-label * {
color: var(--theme-comment);
}
.tree.object-inspector .block .object-label,
.tree.object-inspector .block .object-label * {
color: var(--theme-body-color);
}
.tree.object-inspector .block .object-label:before {
content: "☲ ";
font-size: 1.1em;
}
.object-inspector .object-delimiter {
color: var(--theme-comment);
}
.object-inspector .tree-node .arrow {
display: inline-block;
vertical-align: middle;
}
/* Focused styles */
.tree.object-inspector .tree-node.focused * {
color: inherit;
}
.tree-node.focused button.jump-definition,
.tree-node.focused button.open-inspector,
.tree-node.focused button.invoke-getter {
background-color: currentColor;
}

Просмотреть файл

@ -0,0 +1,289 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
// @flow
const { Component, createFactory, createElement } = require("react");
const { connect } = require("react-redux");
const actions = require("../actions");
const selectors = require("../reducer");
import Components from "devtools-components";
const Tree = createFactory(Components.Tree);
require("./ObjectInspector.css");
const ObjectInspectorItem = createFactory(require("./ObjectInspectorItem"));
const classnames = require("classnames");
const Utils = require("../utils");
const { renderRep, shouldRenderRootsInReps } = Utils;
const {
getChildrenWithEvaluations,
getActor,
getParent,
nodeIsPrimitive,
nodeHasGetter,
nodeHasSetter
} = Utils.node;
import type { CachedNodes, Props } from "../types";
type DefaultProps = {
autoExpandAll: boolean,
autoExpandDepth: number
};
// This implements a component that renders an interactive inspector
// for looking at JavaScript objects. It expects descriptions of
// objects from the protocol, and will dynamically fetch children
// properties as objects are expanded.
//
// If you want to inspect a single object, pass the name and the
// protocol descriptor of it:
//
// ObjectInspector({
// name: "foo",
// desc: { writable: true, ..., { value: { actor: "1", ... }}},
// ...
// })
//
// If you want multiple top-level objects (like scopes), you can pass
// an array of manually constructed nodes as `roots`:
//
// ObjectInspector({
// roots: [{ name: ... }, ...],
// ...
// });
// There are 3 types of nodes: a simple node with a children array, an
// object that has properties that should be children when they are
// fetched, and a primitive value that should be displayed with no
// children.
class ObjectInspector extends Component<Props> {
static defaultProps: DefaultProps;
constructor(props: Props) {
super();
this.cachedNodes = new Map();
const self: any = this;
self.getItemChildren = this.getItemChildren.bind(this);
self.isNodeExpandable = this.isNodeExpandable.bind(this);
self.setExpanded = this.setExpanded.bind(this);
self.focusItem = this.focusItem.bind(this);
self.getRoots = this.getRoots.bind(this);
self.getNodeKey = this.getNodeKey.bind(this);
}
componentWillMount() {
this.roots = this.props.roots;
this.focusedItem = this.props.focusedItem;
}
componentWillUpdate(nextProps) {
this.removeOutdatedNodesFromCache(nextProps);
if (this.roots !== nextProps.roots) {
// Since the roots changed, we assume the properties did as well,
// so we need to cleanup the component internal state.
this.roots = nextProps.roots;
this.focusedItem = nextProps.focusedItem;
if (this.props.rootsChanged) {
this.props.rootsChanged();
}
return;
}
}
removeOutdatedNodesFromCache(nextProps) {
// When the roots changes, we can wipe out everything.
if (this.roots !== nextProps.roots) {
this.cachedNodes.clear();
return;
}
// If there are new evaluations, we want to remove the existing cached
// nodes from the cache.
if (nextProps.evaluations > this.props.evaluations) {
for (const key of nextProps.evaluations.keys()) {
if (!this.props.evaluations.has(key)) {
this.cachedNodes.delete(key);
}
}
}
}
shouldComponentUpdate(nextProps: Props) {
const { expandedPaths, loadedProperties, evaluations } = this.props;
// We should update if:
// - there are new loaded properties
// - OR there are new evaluations
// - OR the expanded paths number changed, and all of them have properties
// loaded
// - OR the expanded paths number did not changed, but old and new sets
// differ
// - OR the focused node changed.
return (
loadedProperties.size !== nextProps.loadedProperties.size ||
evaluations.size !== nextProps.evaluations.size ||
(expandedPaths.size !== nextProps.expandedPaths.size &&
[...nextProps.expandedPaths].every(path =>
nextProps.loadedProperties.has(path)
)) ||
(expandedPaths.size === nextProps.expandedPaths.size &&
[...nextProps.expandedPaths].some(key => !expandedPaths.has(key))) ||
this.focusedItem !== nextProps.focusedItem ||
this.roots !== nextProps.roots
);
}
componentWillUnmount() {
this.props.closeObjectInspector();
}
props: Props;
cachedNodes: CachedNodes;
getItemChildren(item: Node): Array<Node> | NodeContents | null {
const { loadedProperties, evaluations } = this.props;
const { cachedNodes } = this;
return getChildrenWithEvaluations({
evaluations,
loadedProperties,
cachedNodes,
item
});
}
getRoots(): Array<Node> {
return this.props.roots;
}
getNodeKey(item: Node): string {
return item.path && typeof item.path.toString === "function"
? item.path.toString()
: JSON.stringify(item);
}
isNodeExpandable(item: Node): boolean {
if (nodeIsPrimitive(item)) {
return false;
}
if (nodeHasSetter(item) || nodeHasGetter(item)) {
return false;
}
return true;
}
setExpanded(item: Node, expand: boolean) {
if (!this.isNodeExpandable(item)) {
return;
}
const {
nodeExpand,
nodeCollapse,
recordTelemetryEvent,
roots
} = this.props;
if (expand === true) {
const actor = getActor(item, roots);
nodeExpand(item, actor);
if (recordTelemetryEvent) {
recordTelemetryEvent("object_expanded");
}
} else {
nodeCollapse(item);
}
}
focusItem(item: Node) {
const { focusable = true, onFocus } = this.props;
if (focusable && this.focusedItem !== item) {
this.focusedItem = item;
this.forceUpdate();
if (onFocus) {
onFocus(item);
}
}
}
render() {
const {
autoExpandAll = true,
autoExpandDepth = 1,
focusable = true,
disableWrap = false,
expandedPaths,
inline
} = this.props;
return Tree({
className: classnames({
inline,
nowrap: disableWrap,
"object-inspector": true
}),
autoExpandAll,
autoExpandDepth,
isExpanded: item => expandedPaths && expandedPaths.has(item.path),
isExpandable: this.isNodeExpandable,
focused: this.focusedItem,
getRoots: this.getRoots,
getParent,
getChildren: this.getItemChildren,
getKey: this.getNodeKey,
onExpand: item => this.setExpanded(item, true),
onCollapse: item => this.setExpanded(item, false),
onFocus: focusable ? this.focusItem : null,
renderItem: (item, depth, focused, arrow, expanded) =>
ObjectInspectorItem({
...this.props,
item,
depth,
focused,
arrow,
expanded,
setExpanded: this.setExpanded
})
});
}
}
function mapStateToProps(state, props) {
return {
expandedPaths: selectors.getExpandedPaths(state),
loadedProperties: selectors.getLoadedProperties(state),
evaluations: selectors.getEvaluations(state)
};
}
const OI = connect(
mapStateToProps,
actions
)(ObjectInspector);
module.exports = (props: Props) => {
const { roots } = props;
if (shouldRenderRootsInReps(roots)) {
return renderRep(roots[0], props);
}
return createElement(OI, props);
};

Просмотреть файл

@ -0,0 +1,308 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
// @flow
const { Component } = require("react");
const dom = require("react-dom-factories");
import Services from "devtools-services";
const { appinfo } = Services;
const isMacOS = appinfo.OS === "Darwin";
const classnames = require("classnames");
const { MODE } = require("../../reps/constants");
const Utils = require("../utils");
const {
getValue,
nodeHasAccessors,
nodeHasProperties,
nodeIsBlock,
nodeIsDefaultProperties,
nodeIsFunction,
nodeIsGetter,
nodeIsMapEntry,
nodeIsMissingArguments,
nodeIsOptimizedOut,
nodeIsPrimitive,
nodeIsPrototype,
nodeIsSetter,
nodeIsUninitializedBinding,
nodeIsUnmappedBinding,
nodeIsUnscopedBinding,
nodeIsWindow,
nodeIsLongString,
nodeHasFullText,
nodeHasGetter,
getNonPrototypeParentGripValue
} = Utils.node;
type Props = {
item: Node,
depth: number,
expanded: boolean,
focused: boolean,
arrow: ReactElement,
setExpanded: (item: Node, expanded: boolean) => void,
mode: Mode,
dimTopLevelWindow: boolean,
invokeGetter: () => void,
onDoubleClick: ?(
item: Node,
options: {
depth: number,
focused: boolean,
expanded: boolean
}
) => any,
onCmdCtrlClick: ?(
item: Node,
options: {
depth: number,
event: SyntheticEvent,
focused: boolean,
expanded: boolean
}
) => any,
onLabelClick: ?(
item: Node,
options: {
depth: number,
focused: boolean,
expanded: boolean,
setExpanded: (Node, boolean) => any
}
) => any
};
class ObjectInspectorItem extends Component<Props> {
// eslint-disable-next-line complexity
getLabelAndValue(): {
value?: string | Element,
label?: string
} {
const { item, depth, expanded, mode } = this.props;
const label = item.name;
const isPrimitive = nodeIsPrimitive(item);
if (nodeIsOptimizedOut(item)) {
return {
label,
value: dom.span({ className: "unavailable" }, "(optimized away)")
};
}
if (nodeIsUninitializedBinding(item)) {
return {
label,
value: dom.span({ className: "unavailable" }, "(uninitialized)")
};
}
if (nodeIsUnmappedBinding(item)) {
return {
label,
value: dom.span({ className: "unavailable" }, "(unmapped)")
};
}
if (nodeIsUnscopedBinding(item)) {
return {
label,
value: dom.span({ className: "unavailable" }, "(unscoped)")
};
}
const itemValue = getValue(item);
const unavailable =
isPrimitive &&
itemValue &&
itemValue.hasOwnProperty &&
itemValue.hasOwnProperty("unavailable");
if (nodeIsMissingArguments(item) || unavailable) {
return {
label,
value: dom.span({ className: "unavailable" }, "(unavailable)")
};
}
if (
nodeIsFunction(item) &&
!nodeIsGetter(item) &&
!nodeIsSetter(item) &&
(mode === MODE.TINY || !mode)
) {
return {
label: Utils.renderRep(item, {
...this.props,
functionName: label
})
};
}
if (
nodeHasProperties(item) ||
nodeHasAccessors(item) ||
nodeIsMapEntry(item) ||
nodeIsLongString(item) ||
isPrimitive
) {
const repProps = { ...this.props };
if (depth > 0) {
repProps.mode = mode === MODE.LONG ? MODE.SHORT : MODE.TINY;
}
if (expanded) {
repProps.mode = MODE.TINY;
}
if (nodeIsLongString(item)) {
repProps.member = {
open: nodeHasFullText(item) && expanded
};
}
if (nodeHasGetter(item)) {
const parentGrip = getNonPrototypeParentGripValue(item);
if (parentGrip) {
Object.assign(repProps, {
onInvokeGetterButtonClick: () =>
this.props.invokeGetter(item, parentGrip, item.name)
});
}
}
return {
label,
value: Utils.renderRep(item, repProps)
};
}
return {
label
};
}
getTreeItemProps(): Object {
const {
item,
depth,
focused,
expanded,
onCmdCtrlClick,
onDoubleClick,
dimTopLevelWindow
} = this.props;
const parentElementProps: Object = {
className: classnames("node object-node", {
focused,
lessen:
!expanded &&
(nodeIsDefaultProperties(item) ||
nodeIsPrototype(item) ||
nodeIsGetter(item) ||
nodeIsSetter(item) ||
(dimTopLevelWindow === true && nodeIsWindow(item) && depth === 0)),
block: nodeIsBlock(item)
}),
onClick: e => {
if (
onCmdCtrlClick &&
((isMacOS && e.metaKey) || (!isMacOS && e.ctrlKey))
) {
onCmdCtrlClick(item, {
depth,
event: e,
focused,
expanded
});
e.stopPropagation();
return;
}
// If this click happened because the user selected some text, bail out.
// Note that if the user selected some text before and then clicks here,
// the previously selected text will be first unselected, unless the
// user clicked on the arrow itself. Indeed because the arrow is an
// image, clicking on it does not remove any existing text selection.
// So we need to also check if the arrow was clicked.
if (
Utils.selection.documentHasSelection() &&
!(e.target && e.target.matches && e.target.matches(".arrow"))
) {
e.stopPropagation();
}
}
};
if (onDoubleClick) {
parentElementProps.onDoubleClick = e => {
e.stopPropagation();
onDoubleClick(item, {
depth,
focused,
expanded
});
};
}
return parentElementProps;
}
renderLabel(label: string) {
if (label === null || typeof label === "undefined") {
return null;
}
const { item, depth, focused, expanded, onLabelClick } = this.props;
return dom.span(
{
className: "object-label",
onClick: onLabelClick
? event => {
event.stopPropagation();
// If the user selected text, bail out.
if (Utils.selection.documentHasSelection()) {
return;
}
onLabelClick(item, {
depth,
focused,
expanded,
setExpanded: this.props.setExpanded
});
}
: undefined
},
label
);
}
render() {
const { arrow } = this.props;
const { label, value } = this.getLabelAndValue();
const labelElement = this.renderLabel(label);
const delimiter =
value && labelElement
? dom.span({ className: "object-delimiter" }, ": ")
: null;
return dom.div(
this.getTreeItemProps(),
arrow,
labelElement,
delimiter,
value
);
}
}
module.exports = ObjectInspectorItem;

Просмотреть файл

@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const ObjectInspector = require("./components/ObjectInspector");
const utils = require("./utils");
const reducer = require("./reducer");
module.exports = { ObjectInspector, utils, reducer };

Просмотреть файл

@ -0,0 +1,117 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
// @flow
import type { ReduxAction, State } from "./types";
function initialState() {
return {
expandedPaths: new Set(),
loadedProperties: new Map(),
evaluations: new Map(),
actors: new Set()
};
}
function reducer(
state: State = initialState(),
action: ReduxAction = {}
): State {
const { type, data } = action;
const cloneState = overrides => ({ ...state, ...overrides });
if (type === "NODE_EXPAND") {
return cloneState({
expandedPaths: new Set(state.expandedPaths).add(data.node.path)
});
}
if (type === "NODE_COLLAPSE") {
const expandedPaths = new Set(state.expandedPaths);
expandedPaths.delete(data.node.path);
return cloneState({ expandedPaths });
}
if (type === "NODE_PROPERTIES_LOADED") {
return cloneState({
actors: data.actor
? new Set(state.actors || []).add(data.actor)
: state.actors,
loadedProperties: new Map(state.loadedProperties).set(
data.node.path,
action.data.properties
)
});
}
if (type === "ROOTS_CHANGED") {
return cloneState();
}
if (type === "GETTER_INVOKED") {
return cloneState({
actors: data.actor
? new Set(state.actors || []).add(data.result.from)
: state.actors,
evaluations: new Map(state.evaluations).set(data.node.path, {
getterValue:
data.result &&
data.result.value &&
(data.result.value.return || data.result.value.throw)
})
});
}
// NOTE: we clear the state on resume because otherwise the scopes pane
// would be out of date. Bug 1514760
if (type === "RESUME" || type == "NAVIGATE") {
return initialState();
}
return state;
}
function getObjectInspectorState(state) {
return state.objectInspector;
}
function getExpandedPaths(state) {
return getObjectInspectorState(state).expandedPaths;
}
function getExpandedPathKeys(state) {
return [...getExpandedPaths(state).keys()];
}
function getActors(state) {
return getObjectInspectorState(state).actors;
}
function getLoadedProperties(state) {
return getObjectInspectorState(state).loadedProperties;
}
function getLoadedPropertyKeys(state) {
return [...getLoadedProperties(state).keys()];
}
function getEvaluations(state) {
return getObjectInspectorState(state).evaluations;
}
const selectors = {
getActors,
getEvaluations,
getExpandedPathKeys,
getExpandedPaths,
getLoadedProperties,
getLoadedPropertyKeys
};
Object.defineProperty(module.exports, "__esModule", {
value: true
});
module.exports = selectors;
module.exports.default = reducer;

Просмотреть файл

@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const stubs = new Map();
stubs.set("proto-properties-symbols", {
ownProperties: {
a: {
configurable: true,
enumerable: true,
writable: true,
value: 1
}
},
from: "server2.conn13.child19/propertyIterator160",
prototype: {
type: "object",
actor: "server2.conn13.child19/obj162",
class: "Object",
extensible: true,
frozen: false,
sealed: false,
ownPropertyLength: 15,
preview: {
kind: "Object",
ownProperties: {},
ownSymbols: [],
ownPropertiesLength: 15,
ownSymbolsLength: 0,
safeGetterValues: {}
}
},
ownSymbols: [
{
name: "Symbol()",
descriptor: {
configurable: true,
enumerable: true,
writable: true,
value: "hello"
}
}
]
});
stubs.set("longs-string-safe-getter", {
ownProperties: {
baseVal: {
getterValue: {
type: "longString",
initial: "data:image/png;base64,initial",
length: 95080,
actor: "server1.conn1.child1/longString28"
},
getterPrototypeLevel: 1,
enumerable: true,
writable: true
}
},
from: "server1.conn1.child1/propertyIterator30"
});
module.exports = stubs;

Просмотреть файл

@ -0,0 +1,154 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const stubs = new Map();
stubs.set("properties", {
from: "server2.conn14.child18/obj30",
prototype: {
type: "object",
actor: "server2.conn14.child18/obj31",
class: "Object",
extensible: true,
frozen: false,
sealed: false,
ownPropertyLength: 11,
preview: {
kind: "Object",
ownProperties: {},
ownSymbols: [],
ownPropertiesLength: 11,
ownSymbolsLength: 2,
safeGetterValues: {}
}
},
ownProperties: {},
ownSymbols: [],
safeGetterValues: {
size: {
getterValue: 2,
getterPrototypeLevel: 2,
enumerable: false,
writable: true
}
}
});
stubs.set("11-entries", {
ownProperties: {
"0": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-0",
value: "value-0"
}
}
},
"1": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-1",
value: "value-1"
}
}
},
"2": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-2",
value: "value-2"
}
}
},
"3": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-3",
value: "value-3"
}
}
},
"4": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-4",
value: "value-4"
}
}
},
"5": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-5",
value: "value-5"
}
}
},
"6": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-6",
value: "value-6"
}
}
},
"7": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-7",
value: "value-7"
}
}
},
"8": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-8",
value: "value-8"
}
}
},
"9": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-9",
value: "value-9"
}
}
},
"10": {
enumerable: true,
value: {
type: "mapEntry",
preview: {
key: "key-10",
value: "value-10"
}
}
}
},
from: "server4.conn4.child19/propertyIterator54"
});
module.exports = stubs;

Просмотреть файл

@ -0,0 +1,784 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const stubs = new Map();
stubs.set("performance", {
from: "server2.conn4.child1/obj30",
prototype: {
type: "object",
actor: "server2.conn4.child1/obj33",
class: "PerformancePrototype",
extensible: true,
frozen: false,
sealed: false,
ownPropertyLength: 16,
preview: {
kind: "Object",
ownProperties: {
now: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj34",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "now",
displayName: "now"
}
},
getEntries: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj35",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "getEntries",
displayName: "getEntries"
}
},
getEntriesByType: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj36",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "getEntriesByType",
displayName: "getEntriesByType"
}
},
getEntriesByName: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj37",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "getEntriesByName",
displayName: "getEntriesByName"
}
},
clearResourceTimings: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj38",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "clearResourceTimings",
displayName: "clearResourceTimings"
}
},
setResourceTimingBufferSize: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj39",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "setResourceTimingBufferSize",
displayName: "setResourceTimingBufferSize"
}
},
mark: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj40",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "mark",
displayName: "mark"
}
},
clearMarks: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj41",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "clearMarks",
displayName: "clearMarks"
}
},
measure: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj42",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "measure",
displayName: "measure"
}
},
clearMeasures: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj43",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "clearMeasures",
displayName: "clearMeasures"
}
}
},
ownPropertiesLength: 16
}
},
ownProperties: {
userTimingJsNow: {
configurable: true,
enumerable: true,
writable: true,
value: false
},
userTimingJsNowPrefixed: {
configurable: true,
enumerable: true,
writable: true,
value: false
},
userTimingJsUserTiming: {
configurable: true,
enumerable: true,
writable: true,
value: false
},
userTimingJsUserTimingPrefixed: {
configurable: true,
enumerable: true,
writable: true,
value: false
},
userTimingJsPerformanceTimeline: {
configurable: true,
enumerable: true,
writable: true,
value: false
},
userTimingJsPerformanceTimelinePrefixed: {
configurable: true,
enumerable: true,
writable: true,
value: false
},
timeOrigin: {
enumerable: true,
writable: true,
value: 1500971976372.9033
},
timing: {
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj44",
class: "PerformanceTiming",
extensible: true,
frozen: false,
sealed: false,
ownPropertyLength: 0,
preview: {
kind: "Object",
ownProperties: {},
ownPropertiesLength: 0,
safeGetterValues: {
navigationStart: {
getterValue: 1500971976373,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
unloadEventStart: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
unloadEventEnd: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
redirectStart: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
redirectEnd: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
fetchStart: {
getterValue: 1500971982226,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domainLookupStart: {
getterValue: 1500971982251,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domainLookupEnd: {
getterValue: 1500971982255,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
connectStart: {
getterValue: 1500971982255,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
connectEnd: {
getterValue: 1500971982638,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
}
}
}
}
},
navigation: {
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server2.conn4.child1/obj45",
class: "PerformanceNavigation",
extensible: true,
frozen: false,
sealed: false,
ownPropertyLength: 0,
preview: {
kind: "Object",
ownProperties: {},
ownPropertiesLength: 0,
safeGetterValues: {
type: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
redirectCount: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
}
}
}
}
},
onresourcetimingbufferfull: {
enumerable: true,
writable: true,
value: {
type: "null"
}
}
},
safeGetterValues: {
timeOrigin: {
getterValue: 1500971976372.9033,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
timing: {
getterValue: {
type: "object",
actor: "server2.conn4.child1/obj44",
class: "PerformanceTiming",
extensible: true,
frozen: false,
sealed: false,
ownPropertyLength: 0,
preview: {
kind: "Object",
ownProperties: {},
ownPropertiesLength: 0,
safeGetterValues: {
navigationStart: {
getterValue: 1500971976373,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
unloadEventStart: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
unloadEventEnd: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
redirectStart: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
redirectEnd: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
fetchStart: {
getterValue: 1500971982226,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domainLookupStart: {
getterValue: 1500971982251,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domainLookupEnd: {
getterValue: 1500971982255,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
connectStart: {
getterValue: 1500971982255,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
connectEnd: {
getterValue: 1500971982638,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
}
}
}
},
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
navigation: {
getterValue: {
type: "object",
actor: "server2.conn4.child1/obj45",
class: "PerformanceNavigation",
extensible: true,
frozen: false,
sealed: false,
ownPropertyLength: 0,
preview: {
kind: "Object",
ownProperties: {},
ownPropertiesLength: 0,
safeGetterValues: {
type: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
redirectCount: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
}
}
}
},
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
onresourcetimingbufferfull: {
getterValue: {
type: "null"
},
getterPrototypeLevel: 1,
enumerable: true,
writable: true
}
}
});
stubs.set("timing", {
from: "server1.conn1.child1/obj31",
prototype: {
type: "object",
actor: "server1.conn1.child1/obj32",
class: "PerformanceTimingPrototype",
extensible: true,
frozen: false,
sealed: false,
ownPropertyLength: 23,
preview: {
kind: "Object",
ownProperties: {
toJSON: {
configurable: true,
enumerable: true,
writable: true,
value: {
type: "object",
actor: "server1.conn1.child1/obj33",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "toJSON",
displayName: "toJSON"
}
},
navigationStart: {
configurable: true,
enumerable: true,
get: {
type: "object",
actor: "server1.conn1.child1/obj34",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "get navigationStart",
displayName: "get navigationStart"
},
set: {
type: "undefined"
}
},
unloadEventStart: {
configurable: true,
enumerable: true,
get: {
type: "object",
actor: "server1.conn1.child1/obj35",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "get unloadEventStart",
displayName: "get unloadEventStart"
},
set: {
type: "undefined"
}
},
unloadEventEnd: {
configurable: true,
enumerable: true,
get: {
type: "object",
actor: "server1.conn1.child1/obj36",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "get unloadEventEnd",
displayName: "get unloadEventEnd"
},
set: {
type: "undefined"
}
},
redirectStart: {
configurable: true,
enumerable: true,
get: {
type: "object",
actor: "server1.conn1.child1/obj37",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "get redirectStart",
displayName: "get redirectStart"
},
set: {
type: "undefined"
}
},
redirectEnd: {
configurable: true,
enumerable: true,
get: {
type: "object",
actor: "server1.conn1.child1/obj38",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "get redirectEnd",
displayName: "get redirectEnd"
},
set: {
type: "undefined"
}
},
fetchStart: {
configurable: true,
enumerable: true,
get: {
type: "object",
actor: "server1.conn1.child1/obj39",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "get fetchStart",
displayName: "get fetchStart"
},
set: {
type: "undefined"
}
},
domainLookupStart: {
configurable: true,
enumerable: true,
get: {
type: "object",
actor: "server1.conn1.child1/obj40",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "get domainLookupStart",
displayName: "get domainLookupStart"
},
set: {
type: "undefined"
}
},
domainLookupEnd: {
configurable: true,
enumerable: true,
get: {
type: "object",
actor: "server1.conn1.child1/obj41",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "get domainLookupEnd",
displayName: "get domainLookupEnd"
},
set: {
type: "undefined"
}
},
connectStart: {
configurable: true,
enumerable: true,
get: {
type: "object",
actor: "server1.conn1.child1/obj42",
class: "Function",
extensible: true,
frozen: false,
sealed: false,
name: "get connectStart",
displayName: "get connectStart"
},
set: {
type: "undefined"
}
}
},
ownPropertiesLength: 23
}
},
ownProperties: {},
safeGetterValues: {
navigationStart: {
getterValue: 1500967716401,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
unloadEventStart: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
unloadEventEnd: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
redirectStart: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
redirectEnd: {
getterValue: 0,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
fetchStart: {
getterValue: 1500967716401,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domainLookupStart: {
getterValue: 1500967716401,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domainLookupEnd: {
getterValue: 1500967716401,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
connectStart: {
getterValue: 1500967716401,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
connectEnd: {
getterValue: 1500967716401,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
secureConnectionStart: {
getterValue: 1500967716401,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
requestStart: {
getterValue: 1500967716401,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
responseStart: {
getterValue: 1500967716401,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
responseEnd: {
getterValue: 1500967716401,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domLoading: {
getterValue: 1500967716426,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domInteractive: {
getterValue: 1500967716552,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domContentLoadedEventStart: {
getterValue: 1500967716696,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domContentLoadedEventEnd: {
getterValue: 1500967716715,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
domComplete: {
getterValue: 1500967716719,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
loadEventStart: {
getterValue: 1500967716719,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
},
loadEventEnd: {
getterValue: 1500967716720,
getterPrototypeLevel: 1,
enumerable: true,
writable: true
}
}
});
module.exports = stubs;

Просмотреть файл

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
function LongStringClient(grip, overrides) {
return {
grip,
substring: function() {
return Promise.resolve({
fullText: ""
});
},
...overrides
};
}
module.exports = LongStringClient;

Просмотреть файл

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
function ObjectClient(grip, overrides) {
return {
grip,
enumEntries: function() {
return Promise.resolve({
iterator: this.getIterator({
ownProperties: {}
})
});
},
enumProperties: function(options) {
return Promise.resolve({
iterator: this.getIterator({
ownProperties: {}
})
});
},
enumSymbols: function() {
return Promise.resolve({
iterator: this.getIterator({
ownSymbols: []
})
});
},
getPrototype: function() {
return Promise.resolve({
prototype: {}
});
},
// Declared here so we can override it.
getIterator(res) {
return {
slice: function(start, count) {
return Promise.resolve(res);
}
};
},
...overrides
};
}
module.exports = ObjectClient;

Просмотреть файл

@ -0,0 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ObjectInspector - renders renders as expected 1`] = `
"
▶︎ {…}
"
`;
exports[`ObjectInspector - renders renders as expected 2`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", … }
"
`;
exports[`ObjectInspector - renders renders as expected 3`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … }
"
`;
exports[`ObjectInspector - renders renders as expected 4`] = `
"
▶︎ {…}
"
`;
exports[`ObjectInspector - renders renders as expected when not provided a name 1`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", … }
"
`;
exports[`ObjectInspector - renders renders block nodes as expected 1`] = `
"
▼ ☲ Block
| a: 30
| b: 32
"
`;
exports[`ObjectInspector - renders renders objects as expected when provided a name 1`] = `
"
▶︎ myproperty: Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", … }
"
`;
exports[`ObjectInspector - renders renders primitives as expected when provided a name 1`] = `
"
myproperty: 42
"
`;
exports[`ObjectInspector - renders updates when the root changes 1`] = `
"
[ ▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … } ]
"
`;
exports[`ObjectInspector - renders updates when the root changes 2`] = `
"
[ ▶︎ Object { a: \\"a\\", b: \\"b\\", c: \\"c\\" } ]
"
`;

Просмотреть файл

@ -0,0 +1,148 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ObjectInspector - classnames has the expected class 1`] = `
<div
className="tree object-inspector"
onBlur={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
role="tree"
style={
Object {
"margin": 0,
"padding": 0,
}
}
tabIndex="0"
>
<div
aria-level={1}
className="tree-node"
data-expandable={false}
id="root"
onClick={[Function]}
role="treeitem"
>
<div
className="node object-node"
onClick={[Function]}
>
<span
className="object-label"
>
root
</span>
<span
className="object-delimiter"
>
:
</span>
<span
className="objectBox objectBox-number"
>
42
</span>
</div>
</div>
</div>
`;
exports[`ObjectInspector - classnames has the inline class when inline prop is true 1`] = `
<div
className="tree inline object-inspector"
onBlur={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
role="tree"
style={
Object {
"margin": 0,
"padding": 0,
}
}
tabIndex="0"
>
<div
aria-level={1}
className="tree-node"
data-expandable={false}
id="root"
onClick={[Function]}
role="treeitem"
>
<div
className="node object-node"
onClick={[Function]}
>
<span
className="object-label"
>
root
</span>
<span
className="object-delimiter"
>
:
</span>
<span
className="objectBox objectBox-number"
>
42
</span>
</div>
</div>
</div>
`;
exports[`ObjectInspector - classnames has the nowrap class when disableWrap prop is true 1`] = `
<div
className="tree nowrap object-inspector"
onBlur={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
role="tree"
style={
Object {
"margin": 0,
"padding": 0,
}
}
tabIndex="0"
>
<div
aria-level={1}
className="tree-node"
data-expandable={false}
id="root"
onClick={[Function]}
role="treeitem"
>
<div
className="node object-node"
onClick={[Function]}
>
<span
className="object-label"
>
root
</span>
<span
className="object-delimiter"
>
:
</span>
<span
className="objectBox objectBox-number"
>
42
</span>
</div>
</div>
</div>
`;

Просмотреть файл

@ -0,0 +1,67 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ObjectInspector - entries calls ObjectClient.enumEntries when expected 1`] = `
"
▼ Map(11)
| ▶︎ <entries>
"
`;
exports[`ObjectInspector - entries calls ObjectClient.enumEntries when expected 2`] = `
"
▼ Map(11)
[ | ▼ <entries> ]
| | ▶︎ 0: \\"key-0\\" → \\"value-0\\"
| | ▶︎ 1: \\"key-1\\" → \\"value-1\\"
| | ▶︎ 2: \\"key-2\\" → \\"value-2\\"
| | ▶︎ 3: \\"key-3\\" → \\"value-3\\"
| | ▶︎ 4: \\"key-4\\" → \\"value-4\\"
| | ▶︎ 5: \\"key-5\\" → \\"value-5\\"
| | ▶︎ 6: \\"key-6\\" → \\"value-6\\"
| | ▶︎ 7: \\"key-7\\" → \\"value-7\\"
| | ▶︎ 8: \\"key-8\\" → \\"value-8\\"
| | ▶︎ 9: \\"key-9\\" → \\"value-9\\"
| | ▶︎ 10: \\"key-10\\" → \\"value-10\\"
"
`;
exports[`ObjectInspector - entries calls ObjectClient.enumEntries when expected 3`] = `
"
▼ Map(11)
[ | ▶︎ <entries> ]
"
`;
exports[`ObjectInspector - entries calls ObjectClient.enumEntries when expected 4`] = `
"
▼ Map(11)
[ | ▼ <entries> ]
| | ▶︎ 0: \\"key-0\\" → \\"value-0\\"
| | ▶︎ 1: \\"key-1\\" → \\"value-1\\"
| | ▶︎ 2: \\"key-2\\" → \\"value-2\\"
| | ▶︎ 3: \\"key-3\\" → \\"value-3\\"
| | ▶︎ 4: \\"key-4\\" → \\"value-4\\"
| | ▶︎ 5: \\"key-5\\" → \\"value-5\\"
| | ▶︎ 6: \\"key-6\\" → \\"value-6\\"
| | ▶︎ 7: \\"key-7\\" → \\"value-7\\"
| | ▶︎ 8: \\"key-8\\" → \\"value-8\\"
| | ▶︎ 9: \\"key-9\\" → \\"value-9\\"
| | ▶︎ 10: \\"key-10\\" → \\"value-10\\"
"
`;
exports[`ObjectInspector - entries renders Object with entries as expected 1`] = `
"
▼ Map(2)
| size: 2
| ▼ <entries>
| | ▼ 0: Symbol(a) → \\"value-a\\"
| | | <key>: Symbol(a)
| | | <value>: \\"value-a\\"
| | ▼ 1: Symbol(b) → \\"value-b\\"
| | | <key>: Symbol(b)
| | | <value>: \\"value-b\\"
| ▼ <prototype>: {…}
| | <prototype>: Object { }
"
`;

Просмотреть файл

@ -0,0 +1,151 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ObjectInspector - state does not expand if the user selected some text 1`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … }
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state does not expand if the user selected some text 2`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … }
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state does not throw when expanding a block node 1`] = `
"
▶︎ ☲ Block
▶︎ Proxy: Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state does not throw when expanding a block node 2`] = `
"
[ ▼ ☲ Block ]
| a: 30
| b: 32
▶︎ Proxy: Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state expanding a getter returning a longString does not throw 1`] = `
"
▼ {…}
| ▼ baseVal: \\"data:image/png;base64,initial<<<<\\"
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state expands if user selected some text and clicked the arrow 1`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … }
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state expands if user selected some text and clicked the arrow 2`] = `
"
[ ▼ {…} ]
| a: 1
| Symbol(): \\"hello\\"
| ▶︎ <prototype>: Object { … }
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state has the expected expandedPaths state when clicking nodes 1`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … }
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state has the expected expandedPaths state when clicking nodes 2`] = `
"
[ ▼ {…} ]
| a: 1
| Symbol(): \\"hello\\"
| ▶︎ <prototype>: Object { … }
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state has the expected expandedPaths state when clicking nodes 3`] = `
"
[ ▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … } ]
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state has the expected expandedPaths state when clicking nodes 4`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … }
[ ▼ Proxy ]
| ▶︎ <target>: Object { … }
| ▶︎ <handler>: Array(3) [ … ]
"
`;
exports[`ObjectInspector - state has the expected expandedPaths state when clicking nodes 5`] = `
"
[ ▼ {…} ]
| a: 1
| Symbol(): \\"hello\\"
| ▶︎ <prototype>: Object { … }
▼ Proxy
| ▶︎ <target>: Object { … }
| ▶︎ <handler>: Array(3) [ … ]
"
`;
exports[`ObjectInspector - state has the expected state when expanding a node 1`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … }
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state has the expected state when expanding a node 2`] = `
"
[ ▼ {…} ]
| ▶︎ <prototype>: Object { }
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state has the expected state when expanding a node 3`] = `
"
▼ {…}
[ | ▼ <prototype>: {} ]
| | ▶︎ <prototype>: Object { }
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state has the expected state when expanding a proxy node 1`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … }
▶︎ Proxy { <target>: {…}, <handler>: (3) […] }
"
`;
exports[`ObjectInspector - state has the expected state when expanding a proxy node 2`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … }
[ ▼ Proxy ]
| ▶︎ <target>: Object { … }
| ▶︎ <handler>: Array(3) [ … ]
"
`;
exports[`ObjectInspector - state has the expected state when expanding a proxy node 3`] = `
"
▶︎ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … }
▼ Proxy
| ▶︎ <target>: Object { … }
[ | ▼ <handler>: (3) […] ]
| | <prototype>: Object { }
"
`;

Просмотреть файл

@ -0,0 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ObjectInspector - getters & setters onInvokeGetterButtonClick + getter & setter 1`] = `
"
▼ root
| x: (>>)
| ▶︎ <get x()>: function x()
| ▶︎ <set x()>: function x()
"
`;
exports[`ObjectInspector - getters & setters onInvokeGetterButtonClick + getter 1`] = `
"
▼ root
| x: (>>)
| ▶︎ <get x()>: function x()
"
`;
exports[`ObjectInspector - getters & setters onInvokeGetterButtonClick + setter 1`] = `
"
▼ root
| x: Setter
| ▶︎ <set x()>: function x()
"
`;
exports[`ObjectInspector - getters & setters renders getters and setters as expected 1`] = `
"
▼ root
| x: Getter & Setter
| ▶︎ <get x()>: function x()
| ▶︎ <set x()>: function x()
"
`;
exports[`ObjectInspector - getters & setters renders getters as expected 1`] = `
"
▼ root
| x: Getter
| ▶︎ <get x()>: function x()
"
`;
exports[`ObjectInspector - getters & setters renders setters as expected 1`] = `
"
▼ root
| x: Setter
| ▶︎ <set x()>: function x()
"
`;

Просмотреть файл

@ -0,0 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ObjectInspector - keyboard navigation works as expected 1`] = `
"
▶︎ Object { a: \\"a\\", b: \\"b\\", c: \\"c\\" }
"
`;
exports[`ObjectInspector - keyboard navigation works as expected 2`] = `
"
[ ▶︎ Object { a: \\"a\\", b: \\"b\\", c: \\"c\\" } ]
"
`;
exports[`ObjectInspector - keyboard navigation works as expected 3`] = `
"
[ ▼ {…} ]
| <prototype>: Object { }
"
`;
exports[`ObjectInspector - keyboard navigation works as expected 4`] = `
"
▼ {…}
[ | <prototype>: Object { } ]
"
`;
exports[`ObjectInspector - keyboard navigation works as expected 5`] = `
"
[ ▼ {…} ]
| <prototype>: Object { }
"
`;
exports[`ObjectInspector - keyboard navigation works as expected 6`] = `
"
▼ {…}
[ | <prototype>: Object { } ]
"
`;
exports[`ObjectInspector - keyboard navigation works as expected 7`] = `
"
[ ▼ {…} ]
| <prototype>: Object { }
"
`;
exports[`ObjectInspector - keyboard navigation works as expected 8`] = `
"
▼ {…}
| <prototype>: Object { }
"
`;

Просмотреть файл

@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ObjectInspector - properties renders uninitialized bindings 1`] = `
"
someFoo: (uninitialized)
"
`;
exports[`ObjectInspector - properties renders unmapped bindings 1`] = `
"
someFoo: (unmapped)
"
`;
exports[`ObjectInspector - properties renders unscoped bindings 1`] = `
"
someFoo: (unscoped)
"
`;

Просмотреть файл

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ObjectInspector - Proxy renders Proxy as expected 1`] = `
"
▼ Proxy
| ▶︎ <target>: Object { … }
| ▶︎ <handler>: Array(3) [ … ]
"
`;

Просмотреть файл

@ -0,0 +1,306 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ObjectInspector - dimTopLevelWindow renders collapsed top-level window when dimTopLevelWindow =false 1`] = `
<div
className="tree object-inspector"
onBlur={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
role="tree"
style={
Object {
"margin": 0,
"padding": 0,
}
}
tabIndex="0"
>
<div
aria-expanded={false}
aria-level={1}
className="tree-node"
data-expandable={true}
id="Symbol(window)"
onClick={[Function]}
role="treeitem"
>
<div
className="node object-node"
onClick={[Function]}
>
<button
className="arrow"
/>
<span
className="object-label"
>
window
</span>
<span
className="object-delimiter"
>
:
</span>
<span
className="objectBox objectBox-Window"
data-link-actor-id="server1.conn3.obj198"
>
<span
className="objectTitle"
>
Window
</span>
</span>
</div>
</div>
</div>
`;
exports[`ObjectInspector - dimTopLevelWindow renders sub-level window 1`] = `
<div
aria-activedescendant="Symbol(root)"
className="tree object-inspector"
onBlur={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
role="tree"
style={
Object {
"margin": 0,
"padding": 0,
}
}
tabIndex="0"
>
<div
aria-expanded={true}
aria-level={1}
className="tree-node focused"
data-expandable={true}
id="Symbol(root)"
onClick={[Function]}
role="treeitem"
>
<div
className="node object-node focused"
onClick={[Function]}
>
<button
className="arrow expanded"
/>
<span
className="object-label"
>
root
</span>
</div>
</div>
<div
aria-expanded={false}
aria-level={2}
className="tree-node"
data-expandable={true}
id="Symbol(window)"
onClick={[Function]}
role="treeitem"
>
<span
className="tree-indent"
>
</span>
<div
className="node object-node"
onClick={[Function]}
>
<button
className="arrow"
/>
<span
className="object-label"
>
window
</span>
<span
className="object-delimiter"
>
:
</span>
<span
className="objectBox objectBox-Window"
data-link-actor-id="server1.conn3.obj198"
>
<span
className="objectTitle"
>
Window
</span>
</span>
</div>
</div>
</div>
`;
exports[`ObjectInspector - dimTopLevelWindow renders window as expected when dimTopLevelWindow is true 1`] = `
<div
className="tree object-inspector"
onBlur={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
role="tree"
style={
Object {
"margin": 0,
"padding": 0,
}
}
tabIndex="0"
>
<div
aria-expanded={false}
aria-level={1}
className="tree-node"
data-expandable={true}
id="Symbol(window)"
onClick={[Function]}
role="treeitem"
>
<div
className="node object-node lessen"
onClick={[Function]}
>
<button
className="arrow"
/>
<span
className="object-label"
>
window
</span>
<span
className="object-delimiter"
>
:
</span>
<span
className="objectBox objectBox-Window"
data-link-actor-id="server1.conn3.obj198"
>
<span
className="objectTitle"
>
Window
</span>
</span>
</div>
</div>
</div>
`;
exports[`ObjectInspector - dimTopLevelWindow renders window as expected when dimTopLevelWindow is true 2`] = `
<div
aria-activedescendant="Symbol(window)"
className="tree object-inspector"
onBlur={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
role="tree"
style={
Object {
"margin": 0,
"padding": 0,
}
}
tabIndex="0"
>
<div
aria-expanded={true}
aria-level={1}
className="tree-node focused"
data-expandable={true}
id="Symbol(window)"
onClick={[Function]}
role="treeitem"
>
<div
className="node object-node focused"
onClick={[Function]}
>
<button
className="arrow expanded"
/>
<span
className="object-label"
>
window
</span>
<span
className="object-delimiter"
>
:
</span>
<span
className="objectBox objectBox-Window"
data-link-actor-id="server1.conn3.obj198"
>
<span
className="objectTitle"
>
Window
</span>
</span>
</div>
</div>
<div
aria-level={2}
className="tree-node"
data-expandable={false}
id="Symbol(window/<prototype>)"
onClick={[Function]}
role="treeitem"
>
<span
className="tree-indent"
>
</span>
<div
className="node object-node lessen"
onClick={[Function]}
>
<span
className="object-label"
>
&lt;prototype&gt;
</span>
<span
className="object-delimiter"
>
:
</span>
<span
className="objectBox objectBox-object"
>
<span
className="objectLeftBrace"
>
{
</span>
<span
className="objectRightBrace"
>
}
</span>
</span>
</div>
</div>
</div>
`;

Просмотреть файл

@ -0,0 +1,432 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { mountObjectInspector } = require("../test-utils");
const { mount } = require("enzyme");
const { createNode, NODE_TYPES } = require("../../utils/node");
const repsPath = "../../../reps";
const { MODE } = require(`${repsPath}/constants`);
const { Rep } = require(`${repsPath}/rep`);
const {
formatObjectInspector,
waitForDispatch,
waitForLoadedProperties
} = require("../test-utils");
const ObjectClient = require("../__mocks__/object-client");
const gripRepStubs = require(`${repsPath}/stubs/grip`);
function generateDefaults(overrides) {
return {
autoExpandDepth: 0,
...overrides
};
}
function mountOI(props, { initialState } = {}) {
const client = {
createObjectClient: grip => ObjectClient(grip)
};
const obj = mountObjectInspector({
client,
props: generateDefaults(props),
initialState: {
objectInspector: {
...initialState,
evaluations: new Map()
}
}
});
return obj;
}
function renderOI(props, opts) {
return mountOI(props, opts).wrapper;
}
describe("ObjectInspector - renders", () => {
it("renders as expected", () => {
const stub = gripRepStubs.get("testMoreThanMaxProps");
const renderObjectInspector = mode =>
renderOI({
roots: [
{
path: "root",
contents: {
value: stub
}
}
],
mode
});
const renderRep = mode => Rep({ object: stub, mode });
const tinyOi = renderObjectInspector(MODE.TINY);
expect(tinyOi.find(".arrow").exists()).toBeTruthy();
expect(tinyOi.contains(renderRep(MODE.TINY))).toBeTruthy();
expect(formatObjectInspector(tinyOi)).toMatchSnapshot();
const shortOi = renderObjectInspector(MODE.SHORT);
expect(shortOi.find(".arrow").exists()).toBeTruthy();
expect(shortOi.contains(renderRep(MODE.SHORT))).toBeTruthy();
expect(formatObjectInspector(shortOi)).toMatchSnapshot();
const longOi = renderObjectInspector(MODE.LONG);
expect(longOi.find(".arrow").exists()).toBeTruthy();
expect(longOi.contains(renderRep(MODE.LONG))).toBeTruthy();
expect(formatObjectInspector(longOi)).toMatchSnapshot();
const oi = renderObjectInspector();
expect(oi.find(".arrow").exists()).toBeTruthy();
// When no mode is provided, it defaults to TINY mode to render the Rep.
expect(oi.contains(renderRep(MODE.TINY))).toBeTruthy();
expect(formatObjectInspector(oi)).toMatchSnapshot();
});
it("directly renders a Rep when the stub is not expandable", () => {
const object = 42;
const renderObjectInspector = mode =>
renderOI({
roots: [
{
path: "root",
contents: {
value: object
}
}
],
mode
});
const renderRep = mode => mount(Rep({ object, mode }));
const tinyOi = renderObjectInspector(MODE.TINY);
expect(tinyOi.find(".arrow").exists()).toBeFalsy();
expect(tinyOi.html()).toEqual(renderRep(MODE.TINY).html());
const shortOi = renderObjectInspector(MODE.SHORT);
expect(shortOi.find(".arrow").exists()).toBeFalsy();
expect(shortOi.html()).toEqual(renderRep(MODE.SHORT).html());
const longOi = renderObjectInspector(MODE.LONG);
expect(longOi.find(".arrow").exists()).toBeFalsy();
expect(longOi.html()).toEqual(renderRep(MODE.LONG).html());
const oi = renderObjectInspector();
expect(oi.find(".arrow").exists()).toBeFalsy();
// When no mode is provided, it defaults to TINY mode to render the Rep.
expect(oi.html()).toEqual(renderRep(MODE.TINY).html());
});
it("renders objects as expected when provided a name", () => {
const object = gripRepStubs.get("testMoreThanMaxProps");
const name = "myproperty";
const oi = renderOI({
roots: [
{
path: "root",
name,
contents: {
value: object
}
}
],
mode: MODE.SHORT
});
expect(oi.find(".object-label").text()).toEqual(name);
expect(formatObjectInspector(oi)).toMatchSnapshot();
});
it("renders primitives as expected when provided a name", () => {
const value = 42;
const name = "myproperty";
const oi = renderOI({
roots: [
{
path: "root",
name,
contents: { value }
}
],
mode: MODE.SHORT
});
expect(oi.find(".object-label").text()).toEqual(name);
expect(formatObjectInspector(oi)).toMatchSnapshot();
});
it("renders as expected when not provided a name", () => {
const object = gripRepStubs.get("testMoreThanMaxProps");
const oi = renderOI({
roots: [
{
path: "root",
contents: {
value: object
}
}
],
mode: MODE.SHORT
});
expect(oi.find(".object-label").exists()).toBeFalsy();
expect(formatObjectInspector(oi)).toMatchSnapshot();
});
it("renders leaves with a shorter mode than the root", async () => {
const stub = gripRepStubs.get("testMaxProps");
const renderObjectInspector = mode =>
renderOI(
{
autoExpandDepth: 1,
roots: [
{
path: "root",
contents: {
value: stub
}
}
],
mode
},
{
initialState: {
loadedProperties: new Map([
[
"root",
{
ownProperties: Object.keys(stub.preview.ownProperties).reduce(
(res, key) => ({
[key]: {
value: stub
},
...res
}),
{}
)
}
]
])
}
}
);
const renderRep = mode => Rep({ object: stub, mode });
const tinyOi = renderObjectInspector(MODE.TINY);
expect(
tinyOi
.find(".node")
.at(1)
.contains(renderRep(MODE.TINY))
).toBeTruthy();
const shortOi = renderObjectInspector(MODE.SHORT);
expect(
shortOi
.find(".node")
.at(1)
.contains(renderRep(MODE.TINY))
).toBeTruthy();
const longOi = renderObjectInspector(MODE.LONG);
expect(
longOi
.find(".node")
.at(1)
.contains(renderRep(MODE.SHORT))
).toBeTruthy();
const oi = renderObjectInspector();
// When no mode is provided, it defaults to TINY mode to render the Rep.
expect(
oi
.find(".node")
.at(1)
.contains(renderRep(MODE.TINY))
).toBeTruthy();
});
it("renders less-important nodes as expected", async () => {
const defaultPropertiesNode = createNode({
name: "<default>",
contents: [],
type: NODE_TYPES.DEFAULT_PROPERTIES
});
// The <default properties> node should have the "lessen" class only when
// collapsed.
let { store, wrapper } = mountOI({
roots: [defaultPropertiesNode]
});
let defaultPropertiesElementNode = wrapper.find(".node");
expect(defaultPropertiesElementNode.hasClass("lessen")).toBe(true);
let onPropertiesLoaded = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
defaultPropertiesElementNode.simulate("click");
await onPropertiesLoaded;
wrapper.update();
defaultPropertiesElementNode = wrapper.find(".node").first();
expect(
wrapper
.find(".node")
.first()
.hasClass("lessen")
).toBe(false);
const prototypeNode = createNode({
name: "<prototype>",
contents: [],
type: NODE_TYPES.PROTOTYPE
});
// The <prototype> node should have the "lessen" class only when collapsed.
({ wrapper, store } = mountOI({
roots: [prototypeNode],
injectWaitService: true
}));
let protoElementNode = wrapper.find(".node");
expect(protoElementNode.hasClass("lessen")).toBe(true);
onPropertiesLoaded = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
protoElementNode.simulate("click");
await onPropertiesLoaded;
wrapper.update();
protoElementNode = wrapper.find(".node").first();
expect(protoElementNode.hasClass("lessen")).toBe(false);
});
it("renders block nodes as expected", async () => {
const blockNode = createNode({
name: "Block",
contents: [
{
name: "a",
contents: {
value: 30
}
},
{
name: "b",
contents: {
value: 32
}
}
],
type: NODE_TYPES.BLOCK
});
const { wrapper, store } = mountOI({
roots: [blockNode],
autoExpandDepth: 1
});
await waitForLoadedProperties(store, ["Symbol(Block)"]);
wrapper.update();
const blockElementNode = wrapper.find(".node").first();
expect(blockElementNode.hasClass("block")).toBe(true);
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
it.skip("updates when the root changes", async () => {
let root = {
path: "root",
contents: {
value: gripRepStubs.get("testMoreThanMaxProps")
}
};
const { wrapper } = mountOI({
roots: [root],
mode: MODE.LONG,
focusedItem: root
});
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
root = {
path: "root-2",
contents: {
value: gripRepStubs.get("testMaxProps")
}
};
wrapper.setProps({
roots: [root],
focusedItem: root
});
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
it.skip("updates when the root changes but has same path", async () => {
const { wrapper, store } = mountOI({
injectWaitService: true,
roots: [
{
path: "root",
name: "root",
contents: [
{
name: "a",
contents: {
value: 30
}
},
{
name: "b",
contents: {
value: 32
}
}
]
}
],
mode: MODE.LONG
});
wrapper
.find(".node")
.at(0)
.simulate("click");
const oldTree = formatObjectInspector(wrapper);
const onRootsChanged = waitForDispatch(store, "ROOTS_CHANGED");
wrapper.setProps({
roots: [
{
path: "root",
name: "root",
contents: [
{
name: "c",
contents: {
value: "i'm the new node"
}
}
]
}
]
});
await onRootsChanged;
wrapper.update();
expect(formatObjectInspector(wrapper)).not.toBe(oldTree);
});
});

Просмотреть файл

@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const ObjectClient = require("../__mocks__/object-client");
const { mountObjectInspector } = require("../test-utils");
function generateDefaults(overrides) {
return {
autoExpandDepth: 0,
roots: [
{
path: "root",
name: "root",
contents: { value: 42 }
}
],
...overrides
};
}
function mount(props) {
const client = { createObjectClient: grip => ObjectClient(grip) };
return mountObjectInspector({
client,
props: generateDefaults(props)
});
}
describe("ObjectInspector - classnames", () => {
it("has the expected class", () => {
const { tree } = mount();
expect(tree.hasClass("tree")).toBeTruthy();
expect(tree.hasClass("inline")).toBeFalsy();
expect(tree.hasClass("nowrap")).toBeFalsy();
expect(tree).toMatchSnapshot();
});
it("has the nowrap class when disableWrap prop is true", () => {
const { tree } = mount({ disableWrap: true });
expect(tree.hasClass("nowrap")).toBeTruthy();
expect(tree).toMatchSnapshot();
});
it("has the inline class when inline prop is true", () => {
const { tree } = mount({ inline: true });
expect(tree.hasClass("inline")).toBeTruthy();
expect(tree).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,92 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/* global jest */
const { mountObjectInspector } = require("../test-utils");
const ObjectClient = require("../__mocks__/object-client");
const LongStringClient = require("../__mocks__/long-string-client");
const repsPath = "../../../reps";
const longStringStubs = require(`${repsPath}/stubs/long-string`);
function mount(props) {
const substring = jest.fn(() => Promise.resolve({ fullText: "" }));
const client = {
createObjectClient: grip => ObjectClient(grip),
createLongStringClient: jest.fn(grip =>
LongStringClient(grip, { substring })
)
};
const obj = mountObjectInspector({
client,
props
});
return { ...obj, substring };
}
describe("createLongStringClient", () => {
it("is called with the expected object for longString node", () => {
const stub = longStringStubs.get("testUnloadedFullText");
const { client } = mount({
autoExpandDepth: 1,
roots: [
{
path: "root",
contents: {
value: stub
}
}
]
});
expect(client.createLongStringClient.mock.calls[0][0]).toBe(stub);
});
describe("substring", () => {
it("is called for longStrings with unloaded full text", () => {
const stub = longStringStubs.get("testUnloadedFullText");
const { substring } = mount({
autoExpandDepth: 1,
roots: [
{
path: "root",
contents: {
value: stub
}
}
]
});
// Third argument is the callback which holds the string response.
expect(substring.mock.calls[0]).toHaveLength(3);
const [start, length] = substring.mock.calls[0];
expect(start).toBe(stub.initial.length);
expect(length).toBe(stub.length);
});
it("is not called for longString node w/ loaded full text", () => {
const stub = longStringStubs.get("testLoadedFullText");
const { substring } = mount({
autoExpandDepth: 1,
roots: [
{
path: "root",
contents: {
value: stub
}
}
]
});
expect(substring.mock.calls).toHaveLength(0);
});
});
});

Просмотреть файл

@ -0,0 +1,109 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/* global jest */
const { mountObjectInspector } = require("../test-utils");
const ObjectClient = require("../__mocks__/object-client");
const {
createNode,
makeNodesForEntries,
makeNumericalBuckets
} = require("../../utils/node");
const repsPath = "../../../reps";
const gripRepStubs = require(`${repsPath}/stubs/grip`);
const gripArrayRepStubs = require(`${repsPath}/stubs/grip-array`);
function mount(props, overrides = {}) {
const client = {
createObjectClient:
overrides.createObjectClient || jest.fn(grip => ObjectClient(grip))
};
return mountObjectInspector({
client,
props
});
}
describe("createObjectClient", () => {
it("is called with the expected object for regular node", () => {
const stub = gripRepStubs.get("testMoreThanMaxProps");
const { client } = mount({
autoExpandDepth: 1,
roots: [
{
path: "root",
contents: {
value: stub
}
}
]
});
expect(client.createObjectClient.mock.calls[0][0]).toBe(stub);
});
it("is called with the expected object for entries node", () => {
const grip = Symbol();
const mapStubNode = createNode({ name: "map", contents: { value: grip } });
const entriesNode = makeNodesForEntries(mapStubNode);
const { client } = mount({
autoExpandDepth: 1,
roots: [entriesNode]
});
expect(client.createObjectClient.mock.calls[0][0]).toBe(grip);
});
it("is called with the expected object for bucket node", () => {
const grip = gripArrayRepStubs.get("testMaxProps");
const root = createNode({ name: "root", contents: { value: grip } });
const [bucket] = makeNumericalBuckets(root);
const { client } = mount({
autoExpandDepth: 1,
roots: [bucket]
});
expect(client.createObjectClient.mock.calls[0][0]).toBe(grip);
});
it("is called with the expected object for sub-bucket node", () => {
const grip = gripArrayRepStubs.get("testMaxProps");
const root = createNode({ name: "root", contents: { value: grip } });
const [bucket] = makeNumericalBuckets(root);
const [subBucket] = makeNumericalBuckets(bucket);
const { client } = mount({
autoExpandDepth: 1,
roots: [subBucket]
});
expect(client.createObjectClient.mock.calls[0][0]).toBe(grip);
});
it("doesn't fail when ObjectClient doesn't have expected methods", () => {
const stub = gripRepStubs.get("testMoreThanMaxProps");
const root = createNode({ name: "root", contents: { value: stub } });
// Override console.error so we don't spam test results.
const originalConsoleError = console.error;
console.error = () => {};
const createObjectClient = x => ({});
mount(
{
autoExpandDepth: 1,
roots: [root]
},
{ createObjectClient }
);
// rollback console.error.
console.error = originalConsoleError;
});
});

Просмотреть файл

@ -0,0 +1,137 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/* global jest */
const { mountObjectInspector } = require("../test-utils");
const { MODE } = require("../../../reps/constants");
const {
formatObjectInspector,
waitForDispatch,
waitForLoadedProperties
} = require("../test-utils");
const gripMapRepStubs = require("../../../reps/stubs/grip-map");
const mapStubs = require("../../stubs/map");
const ObjectClient = require("../__mocks__/object-client");
function generateDefaults(overrides) {
return {
autoExpandDepth: 0,
createObjectClient: grip => ObjectClient(grip),
...overrides
};
}
function getEnumEntriesMock() {
return jest.fn(() => ({
iterator: {
slice: () => mapStubs.get("11-entries")
}
}));
}
function mount(props, { initialState }) {
const enumEntries = getEnumEntriesMock();
const client = {
createObjectClient: grip => ObjectClient(grip, { enumEntries })
};
const obj = mountObjectInspector({
client,
props: generateDefaults(props),
initialState: {
objectInspector: {
...initialState,
evaluations: new Map()
}
}
});
return { ...obj, enumEntries };
}
describe("ObjectInspector - entries", () => {
it("renders Object with entries as expected", async () => {
const stub = gripMapRepStubs.get("testSymbolKeyedMap");
const { store, wrapper, enumEntries } = mount(
{
autoExpandDepth: 3,
roots: [
{
path: "root",
contents: { value: stub }
}
],
mode: MODE.LONG
},
{
initialState: {
loadedProperties: new Map([["root", mapStubs.get("properties")]])
}
}
);
await waitForLoadedProperties(store, [
"Symbol(root/<entries>/0)",
"Symbol(root/<entries>/1)"
]);
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// enumEntries shouldn't have been called since everything
// is already in the preview property.
expect(enumEntries.mock.calls).toHaveLength(0);
});
it("calls ObjectClient.enumEntries when expected", async () => {
const stub = gripMapRepStubs.get("testMoreThanMaxEntries");
const { wrapper, store, enumEntries } = mount(
{
autoExpandDepth: 1,
injectWaitService: true,
roots: [
{
path: "root",
contents: {
value: stub
}
}
]
},
{
initialState: {
loadedProperties: new Map([
["root", { ownProperties: stub.preview.entries }]
])
}
}
);
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
const nodes = wrapper.find(".node");
const entriesNode = nodes.at(1);
expect(entriesNode.text()).toBe("<entries>");
const onEntrieLoad = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
entriesNode.simulate("click");
await onEntrieLoad;
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
expect(enumEntries.mock.calls).toHaveLength(1);
entriesNode.simulate("click");
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
entriesNode.simulate("click");
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// it does not call enumEntries if entries were already loaded.
expect(enumEntries.mock.calls).toHaveLength(1);
});
});

Просмотреть файл

@ -0,0 +1,169 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/* global jest */
const { mountObjectInspector } = require("../test-utils");
const gripRepStubs = require("../../../reps/stubs/grip");
const ObjectClient = require("../__mocks__/object-client");
function generateDefaults(overrides) {
return {
autoExpandDepth: 0,
...overrides
};
}
function mount(props) {
const client = { createObjectClient: grip => ObjectClient(grip) };
return mountObjectInspector({
client,
props: generateDefaults(props)
});
}
describe("ObjectInspector - properties", () => {
it("calls the onFocus prop when provided one and given focus", () => {
const stub = gripRepStubs.get("testMaxProps");
const onFocus = jest.fn();
const { wrapper } = mount({
roots: [
{
path: "root",
contents: {
value: stub
}
}
],
onFocus
});
const node = wrapper.find(".node").first();
node.simulate("focus");
expect(onFocus.mock.calls).toHaveLength(1);
});
it("doesn't call the onFocus when given focus but focusable is false", () => {
const stub = gripRepStubs.get("testMaxProps");
const onFocus = jest.fn();
const { wrapper } = mount({
focusable: false,
roots: [
{
path: "root",
contents: {
value: stub
}
}
],
onFocus
});
const node = wrapper.find(".node").first();
node.simulate("focus");
expect(onFocus.mock.calls).toHaveLength(0);
});
it("calls onDoubleClick prop when provided one and double clicked", () => {
const stub = gripRepStubs.get("testMaxProps");
const onDoubleClick = jest.fn();
const { wrapper } = mount({
roots: [
{
path: "root",
contents: {
value: stub
}
}
],
onDoubleClick
});
const node = wrapper.find(".node").first();
node.simulate("doubleclick");
expect(onDoubleClick.mock.calls).toHaveLength(1);
});
it("calls the onCmdCtrlClick prop when provided and cmd/ctrl-clicked", () => {
const stub = gripRepStubs.get("testMaxProps");
const onCmdCtrlClick = jest.fn();
const { wrapper } = mount({
roots: [
{
path: "root",
contents: {
value: stub
}
}
],
onCmdCtrlClick
});
const node = wrapper.find(".node").first();
node.simulate("click", { ctrlKey: true });
expect(onCmdCtrlClick.mock.calls).toHaveLength(1);
});
it("calls the onLabel prop when provided one and label clicked", () => {
const stub = gripRepStubs.get("testMaxProps");
const onLabelClick = jest.fn();
const { wrapper } = mount({
roots: [
{
path: "root",
name: "Label",
contents: {
value: stub
}
}
],
onLabelClick
});
const label = wrapper.find(".object-label").first();
label.simulate("click");
expect(onLabelClick.mock.calls).toHaveLength(1);
});
it("does not call the onLabel prop when the user selected text", () => {
const stub = gripRepStubs.get("testMaxProps");
const onLabelClick = jest.fn();
const { wrapper } = mount({
roots: [
{
path: "root",
name: "Label",
contents: {
value: stub
}
}
],
onLabelClick
});
const label = wrapper.find(".object-label").first();
// Set a selection using the mock.
getSelection().setMockSelection("test");
label.simulate("click");
expect(onLabelClick.mock.calls).toHaveLength(0);
// Clear the selection for other tests.
getSelection().setMockSelection();
});
});

Просмотреть файл

@ -0,0 +1,406 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { mountObjectInspector } = require("../test-utils");
const repsPath = "../../../reps";
const { MODE } = require(`${repsPath}/constants`);
const ObjectClient = require("../__mocks__/object-client");
const gripRepStubs = require(`${repsPath}/stubs/grip`);
const gripPropertiesStubs = require("../../stubs/grip");
const {
formatObjectInspector,
storeHasExactExpandedPaths,
storeHasExpandedPath,
storeHasLoadedProperty,
waitForDispatch
} = require("../test-utils");
const { createNode, NODE_TYPES } = require("../../utils/node");
const { getActors, getExpandedPaths } = require("../../reducer");
const protoStub = {
prototype: {
type: "object",
actor: "server2.conn0.child1/obj628",
class: "Object"
}
};
function generateDefaults(overrides) {
return {
autoExpandDepth: 0,
roots: [
{
path: "root-1",
contents: {
value: gripRepStubs.get("testMoreThanMaxProps")
}
},
{
path: "root-2",
contents: {
value: gripRepStubs.get("testProxy")
}
}
],
createObjectClient: grip => ObjectClient(grip),
mode: MODE.LONG,
...overrides
};
}
const LongStringClientMock = require("../__mocks__/long-string-client");
function mount(props, { initialState } = {}) {
const client = {
createObjectClient: grip =>
ObjectClient(grip, {
getPrototype: () => Promise.resolve(protoStub)
}),
createLongStringClient: grip =>
LongStringClientMock(grip, {
substring: function(initiaLength, length, cb) {
cb({
substring: "<<<<"
});
}
})
};
return mountObjectInspector({
client,
props: generateDefaults(props),
initialState: {
objectInspector: {
...initialState,
evaluations: new Map()
}
}
});
}
describe("ObjectInspector - state", () => {
it("has the expected expandedPaths state when clicking nodes", async () => {
const { wrapper, store } = mount(
{},
{
initialState: {
loadedProperties: new Map([
["root-1", gripPropertiesStubs.get("proto-properties-symbols")]
])
}
}
);
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
let nodes = wrapper.find(".node");
// Clicking on the root node adds it path to "expandedPaths".
const root1 = nodes.at(0);
const root2 = nodes.at(1);
root1.simulate("click");
expect(storeHasExactExpandedPaths(store, ["root-1"])).toBeTruthy();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
//
// Clicking on the root node removes it path from "expandedPaths".
root1.simulate("click");
expect(storeHasExactExpandedPaths(store, [])).toBeTruthy();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
const onPropertiesLoaded = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
root2.simulate("click");
await onPropertiesLoaded;
expect(storeHasExactExpandedPaths(store, ["root-2"])).toBeTruthy();
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
root1.simulate("click");
expect(
storeHasExactExpandedPaths(store, ["root-1", "root-2"])
).toBeTruthy();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
nodes = wrapper.find(".node");
const propNode = nodes.at(1);
const symbolNode = nodes.at(2);
const protoNode = nodes.at(3);
propNode.simulate("click");
symbolNode.simulate("click");
protoNode.simulate("click");
expect(
storeHasExactExpandedPaths(store, [
"root-1",
"root-2",
"Symbol(root-1/<prototype>)"
])
).toBeTruthy();
// The property and symbols have primitive values, and can't be expanded.
expect(getExpandedPaths(store.getState()).size).toBe(3);
});
it("has the expected state when expanding a node", async () => {
const { wrapper, store } = mount({}, {});
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
let nodes = wrapper.find(".node");
const root1 = nodes.at(0);
let onPropertiesLoad = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
root1.simulate("click");
await onPropertiesLoad;
wrapper.update();
expect(storeHasLoadedProperty(store, "root-1")).toBeTruthy();
// We don't want to track root actors.
expect(
getActors(store.getState()).has(
gripRepStubs.get("testMoreThanMaxProps").actor
)
).toBeFalsy();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
nodes = wrapper.find(".node");
const protoNode = nodes.at(1);
onPropertiesLoad = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
protoNode.simulate("click");
await onPropertiesLoad;
wrapper.update();
// Once all the loading promises are resolved, actors and loadedProperties
// should have the expected values.
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
expect(
storeHasLoadedProperty(store, "Symbol(root-1/<prototype>)")
).toBeTruthy();
expect(
getActors(store.getState()).has(protoStub.prototype.actor)
).toBeTruthy();
});
xit("has the expected state when expanding a proxy node", async () => {
const { wrapper, store } = mount({});
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
let nodes = wrapper.find(".node");
const proxyNode = nodes.at(1);
let onLoadProperties = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
proxyNode.simulate("click");
await onLoadProperties;
wrapper.update();
// Once the properties are loaded, actors and loadedProperties should have
// the expected values.
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// We don't want to track root actors.
expect(
getActors(store.getState()).has(gripRepStubs.get("testProxy").actor)
).toBeFalsy();
nodes = wrapper.find(".node");
const handlerNode = nodes.at(3);
onLoadProperties = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
handlerNode.simulate("click");
await onLoadProperties;
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
expect(
storeHasLoadedProperty(store, "Symbol(root-2/<handler>)")
).toBeTruthy();
});
it("does not expand if the user selected some text", async () => {
const { wrapper, store } = mount(
{},
{
initialSate: {
loadedProperties: new Map([
["root-1", gripPropertiesStubs.get("proto-properties-symbols")]
])
}
}
);
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
const nodes = wrapper.find(".node");
// Set a selection using the mock.
getSelection().setMockSelection("test");
const root1 = nodes.at(0);
root1.simulate("click");
expect(storeHasExpandedPath(store, "root-1")).toBeFalsy();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// Clear the selection for other tests.
getSelection().setMockSelection();
});
it("expands if user selected some text and clicked the arrow", async () => {
const { wrapper, store } = mount(
{},
{
initialState: {
loadedProperties: new Map([
["root-1", gripPropertiesStubs.get("proto-properties-symbols")]
])
}
}
);
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
const nodes = wrapper.find(".node");
// Set a selection using the mock.
getSelection().setMockSelection("test");
const root1 = nodes.at(0);
root1.find(".arrow").simulate("click");
expect(getExpandedPaths(store.getState()).has("root-1")).toBeTruthy();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// Clear the selection for other tests.
getSelection().setMockSelection();
});
it("does not throw when expanding a block node", async () => {
const blockNode = createNode({
name: "Block",
contents: [
{
name: "a",
contents: {
value: 30
}
},
{
name: "b",
contents: {
value: 32
}
}
],
type: NODE_TYPES.BLOCK
});
const proxyNode = createNode({
name: "Proxy",
contents: {
value: gripRepStubs.get("testProxy")
}
});
const { wrapper, store } = mount({
roots: [blockNode, proxyNode]
});
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
const nodes = wrapper.find(".node");
const root = nodes.at(0);
const onPropertiesLoaded = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
root.simulate("click");
await onPropertiesLoaded;
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
it("calls recordTelemetryEvent when expanding a node", async () => {
const recordTelemetryEvent = jest.fn();
const { wrapper, store } = mount(
{
recordTelemetryEvent
},
{
initialState: {
loadedProperties: new Map([
["root-1", gripPropertiesStubs.get("proto-properties-symbols")]
])
}
}
);
let nodes = wrapper.find(".node");
const root1 = nodes.at(0);
const root2 = nodes.at(1);
// Expanding a node calls recordTelemetryEvent.
root1.simulate("click");
expect(recordTelemetryEvent.mock.calls).toHaveLength(1);
expect(recordTelemetryEvent.mock.calls[0][0]).toEqual("object_expanded");
// Collapsing a node does not call recordTelemetryEvent.
root1.simulate("click");
expect(recordTelemetryEvent.mock.calls).toHaveLength(1);
// Expanding another node calls recordTelemetryEvent.
const onPropertiesLoaded = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
root2.simulate("click");
await onPropertiesLoaded;
expect(recordTelemetryEvent.mock.calls).toHaveLength(2);
expect(recordTelemetryEvent.mock.calls[1][0]).toEqual("object_expanded");
wrapper.update();
// Re-expanding a node calls recordTelemetryEvent.
root1.simulate("click");
expect(recordTelemetryEvent.mock.calls).toHaveLength(3);
expect(recordTelemetryEvent.mock.calls[2][0]).toEqual("object_expanded");
nodes = wrapper.find(".node");
const propNode = nodes.at(1);
const symbolNode = nodes.at(2);
const protoNode = nodes.at(3);
propNode.simulate("click");
symbolNode.simulate("click");
protoNode.simulate("click");
// The property and symbols have primitive values, and can't be expanded.
expect(recordTelemetryEvent.mock.calls).toHaveLength(4);
expect(recordTelemetryEvent.mock.calls[3][0]).toEqual("object_expanded");
});
it("expanding a getter returning a longString does not throw", async () => {
const { wrapper, store } = mount(
{
focusable: false
},
{
initialState: {
loadedProperties: new Map([
["root-1", gripPropertiesStubs.get("longs-string-safe-getter")]
])
}
}
);
wrapper
.find(".node")
.at(0)
.simulate("click");
wrapper.update();
const onPropertiesLoaded = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
wrapper
.find(".node")
.at(1)
.simulate("click");
await onPropertiesLoaded;
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,81 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { mountObjectInspector } = require("../test-utils");
const { MODE } = require("../../../reps/constants");
const { createNode } = require("../../utils/node");
const functionStubs = require("../../../reps/stubs/function");
const ObjectClient = require("../__mocks__/object-client");
function generateDefaults(overrides) {
return {
autoExpandDepth: 1,
...overrides
};
}
function mount(props) {
const client = { createObjectClient: grip => ObjectClient(grip) };
return mountObjectInspector({
client,
props: generateDefaults(props)
});
}
describe("ObjectInspector - functions", () => {
it("renders named function properties as expected", () => {
const stub = functionStubs.get("Named");
const { wrapper } = mount({
roots: [
createNode({
name: "fn",
contents: { value: stub }
})
]
});
const nodes = wrapper.find(".node");
const functionNode = nodes.first();
expect(functionNode.text()).toBe("fn:testName()");
});
it("renders anon function properties as expected", () => {
const stub = functionStubs.get("Anon");
const { wrapper } = mount({
roots: [
createNode({
name: "fn",
contents: { value: stub }
})
]
});
const nodes = wrapper.find(".node");
const functionNode = nodes.first();
// It should have the name of the property.
expect(functionNode.text()).toBe("fn()");
});
it("renders non-TINY mode functions as expected", () => {
const stub = functionStubs.get("Named");
const { wrapper } = mount({
autoExpandDepth: 0,
roots: [
{
path: "root",
name: "x",
contents: { value: stub }
}
],
mode: MODE.LONG
});
const nodes = wrapper.find(".node");
const functionNode = nodes.first();
// It should have the name of the property.
expect(functionNode.text()).toBe("x: function testName()");
});
});

Просмотреть файл

@ -0,0 +1,102 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { MODE } = require("../../../reps/constants");
const {
formatObjectInspector,
waitForLoadedProperties,
mountObjectInspector
} = require("../test-utils");
const { makeNodesForProperties } = require("../../utils/node");
const accessorStubs = require("../../../reps/stubs/accessor");
const ObjectClient = require("../__mocks__/object-client");
function generateDefaults(overrides) {
return {
autoExpandDepth: 1,
createObjectClient: grip => ObjectClient(grip),
mode: MODE.LONG,
...overrides
};
}
function mount(stub, propsOverride = {}) {
const client = { createObjectClient: grip => ObjectClient(grip) };
const root = { path: "root", name: "root" };
const nodes = makeNodesForProperties(
{
ownProperties: {
x: stub
}
},
root
);
root.contents = nodes;
return mountObjectInspector({
client,
props: generateDefaults({ roots: [root], ...propsOverride })
});
}
describe("ObjectInspector - getters & setters", () => {
it("renders getters as expected", async () => {
const { store, wrapper } = mount(accessorStubs.get("getter"));
await waitForLoadedProperties(store, ["root"]);
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
it("renders setters as expected", async () => {
const { store, wrapper } = mount(accessorStubs.get("setter"));
await waitForLoadedProperties(store, ["root"]);
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
it("renders getters and setters as expected", async () => {
const { store, wrapper } = mount(accessorStubs.get("getter setter"));
await waitForLoadedProperties(store, ["root"]);
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
it("onInvokeGetterButtonClick + getter", async () => {
const onInvokeGetterButtonClick = jest.fn();
const { store, wrapper } = mount(accessorStubs.get("getter"), {
onInvokeGetterButtonClick
});
await waitForLoadedProperties(store, ["root"]);
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
it("onInvokeGetterButtonClick + setter", async () => {
const onInvokeGetterButtonClick = jest.fn();
const { store, wrapper } = mount(accessorStubs.get("setter"), {
onInvokeGetterButtonClick
});
await waitForLoadedProperties(store, ["root"]);
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
it("onInvokeGetterButtonClick + getter & setter", async () => {
const onInvokeGetterButtonClick = jest.fn();
const { store, wrapper } = mount(accessorStubs.get("getter setter"), {
onInvokeGetterButtonClick
});
await waitForLoadedProperties(store, ["root"]);
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,82 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { mountObjectInspector } = require("../test-utils");
const repsPath = "../../../reps";
const { MODE } = require(`${repsPath}/constants`);
const { formatObjectInspector, waitForDispatch } = require("../test-utils");
const ObjectClient = require("../__mocks__/object-client");
const gripRepStubs = require(`${repsPath}/stubs/grip`);
function generateDefaults(overrides) {
return {
autoExpandDepth: 0,
mode: MODE.LONG,
...overrides
};
}
function mount(props) {
const client = { createObjectClient: grip => ObjectClient(grip) };
return mountObjectInspector({
client,
props: generateDefaults(props)
});
}
describe("ObjectInspector - keyboard navigation", () => {
it("works as expected", async () => {
const stub = gripRepStubs.get("testMaxProps");
const { wrapper, store } = mount({
roots: [{ path: "root", contents: { value: stub } }]
});
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
wrapper.simulate("focus");
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// Pressing right arrow key should expand the node and lod its properties.
const onPropertiesLoaded = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
simulateKeyDown(wrapper, "ArrowRight");
await onPropertiesLoaded;
wrapper.update();
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// The child node should be focused.
keyNavigate(wrapper, store, "ArrowDown");
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// The root node should be focused again.
keyNavigate(wrapper, store, "ArrowLeft");
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// The child node should be focused again.
keyNavigate(wrapper, store, "ArrowRight");
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// The root node should be focused again.
keyNavigate(wrapper, store, "ArrowUp");
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
wrapper.simulate("blur");
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
});
function keyNavigate(wrapper, store, key) {
simulateKeyDown(wrapper, key);
wrapper.update();
}
function simulateKeyDown(wrapper, key) {
wrapper.simulate("keydown", {
key,
preventDefault: () => {},
stopPropagation: () => {}
});
}

Просмотреть файл

@ -0,0 +1,155 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/* global jest */
const { mountObjectInspector } = require("../test-utils");
const gripRepStubs = require("../../../reps/stubs/grip");
const ObjectClient = require("../__mocks__/object-client");
const { formatObjectInspector } = require("../test-utils");
function generateDefaults(overrides) {
return {
autoExpandDepth: 0,
createObjectClient: grip => ObjectClient(grip),
...overrides
};
}
function getEnumPropertiesMock() {
return jest.fn(() => ({
iterator: {
slice: () => ({})
}
}));
}
function mount(props, { initialState } = {}) {
const enumProperties = getEnumPropertiesMock();
const client = {
createObjectClient: grip => ObjectClient(grip, { enumProperties })
};
const obj = mountObjectInspector({
client,
props: generateDefaults(props),
initialState
});
return { ...obj, enumProperties };
}
describe("ObjectInspector - properties", () => {
it("does not load properties if properties are already loaded", () => {
const stub = gripRepStubs.get("testMaxProps");
const { enumProperties } = mount(
{
autoExpandDepth: 1,
roots: [
{
path: "root",
contents: {
value: stub
}
}
]
},
{
initialState: {
objectInspector: {
loadedProperties: new Map([
["root", { ownProperties: stub.preview.ownProperties }]
]),
evaluations: new Map()
}
}
}
);
expect(enumProperties.mock.calls).toHaveLength(0);
});
it("calls enumProperties when expandable leaf is clicked", () => {
const stub = gripRepStubs.get("testMaxProps");
const { enumProperties, wrapper } = mount({
roots: [
{
path: "root",
contents: {
value: stub
}
}
],
createObjectClient: grip => ObjectClient(grip, { enumProperties })
});
const node = wrapper.find(".node");
node.simulate("click");
// The function is called twice, to get both non-indexed and indexed props.
expect(enumProperties.mock.calls).toHaveLength(2);
expect(enumProperties.mock.calls[0][0]).toEqual({
ignoreNonIndexedProperties: true
});
expect(enumProperties.mock.calls[1][0]).toEqual({
ignoreIndexedProperties: true
});
});
it("renders uninitialized bindings", () => {
const { wrapper } = mount({
roots: [
{
name: "someFoo",
path: "root/someFoo",
contents: {
value: {
uninitialized: true
}
}
}
]
});
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
it("renders unmapped bindings", () => {
const { wrapper } = mount({
roots: [
{
name: "someFoo",
path: "root/someFoo",
contents: {
value: {
unmapped: true
}
}
}
]
});
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
it("renders unscoped bindings", () => {
const { wrapper } = mount({
roots: [
{
name: "someFoo",
path: "root/someFoo",
contents: {
value: {
unscoped: true
}
}
}
]
});
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,115 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/* global jest */
const { mountObjectInspector } = require("../test-utils");
const { MODE } = require("../../../reps/constants");
const stub = require("../../../reps/stubs/grip").get("testProxy");
const { formatObjectInspector } = require("../test-utils");
const ObjectClient = require("../__mocks__/object-client");
function generateDefaults(overrides) {
return {
roots: [
{
path: "root",
contents: { value: stub }
}
],
autoExpandDepth: 1,
mode: MODE.LONG,
...overrides
};
}
function getEnumPropertiesMock() {
return jest.fn(() => ({
iterator: {
slice: () => ({})
}
}));
}
function mount(props, { initialState } = {}) {
const enumProperties = getEnumPropertiesMock();
const client = {
createObjectClient: grip => ObjectClient(grip, { enumProperties })
};
const obj = mountObjectInspector({
client,
props: generateDefaults(props),
initialState
});
return { ...obj, enumProperties };
}
describe("ObjectInspector - Proxy", () => {
it("renders Proxy as expected", () => {
const { wrapper, enumProperties } = mount(
{},
{
initialState: {
objectInspector: {
// Have the prototype already loaded so the component does not call
// enumProperties for the root's properties.
loadedProperties: new Map([["root", { prototype: {} }]]),
evaluations: new Map()
}
}
}
);
expect(formatObjectInspector(wrapper)).toMatchSnapshot();
// enumProperties should not have been called.
expect(enumProperties.mock.calls).toHaveLength(0);
});
it("calls enumProperties on <target> and <handler> clicks", () => {
const { wrapper, enumProperties } = mount(
{},
{
initialState: {
objectInspector: {
// Have the prototype already loaded so the component does not call
// enumProperties for the root's properties.
loadedProperties: new Map([["root", { prototype: {} }]]),
evaluations: new Map()
}
}
}
);
const nodes = wrapper.find(".node");
const targetNode = nodes.at(1);
const handlerNode = nodes.at(2);
targetNode.simulate("click");
// The function is called twice,
// to get both non-indexed and indexed properties.
expect(enumProperties.mock.calls).toHaveLength(2);
expect(enumProperties.mock.calls[0][0]).toEqual({
ignoreNonIndexedProperties: true
});
expect(enumProperties.mock.calls[1][0]).toEqual({
ignoreIndexedProperties: true
});
handlerNode.simulate("click");
// The function is called twice,
// to get both non-indexed and indexed properties.
expect(enumProperties.mock.calls).toHaveLength(4);
expect(enumProperties.mock.calls[2][0]).toEqual({
ignoreNonIndexedProperties: true
});
expect(enumProperties.mock.calls[3][0]).toEqual({
ignoreIndexedProperties: true
});
});
});

Просмотреть файл

@ -0,0 +1,107 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/* global jest */
const { mountObjectInspector } = require("../test-utils");
const repsPath = "../../../reps";
const gripRepStubs = require(`${repsPath}/stubs/grip`);
const ObjectClient = require("../__mocks__/object-client");
const stub = gripRepStubs.get("testMoreThanMaxProps");
const { waitForDispatch } = require("../test-utils");
function getEnumPropertiesMock() {
return jest.fn(() => ({
iterator: {
slice: () => ({})
}
}));
}
function generateDefaults(overrides) {
return {
autoExpandDepth: 0,
roots: [
{
path: "root",
contents: {
value: stub
}
}
],
...overrides
};
}
function mount(props, { initialState } = {}) {
const enumProperties = getEnumPropertiesMock();
const client = {
createObjectClient: grip => ObjectClient(grip, { enumProperties }),
releaseActor: jest.fn()
};
return mountObjectInspector({
client,
props: generateDefaults(props),
initialState: {
objectInspector: {
...initialState,
evaluations: new Map()
}
}
});
}
describe("release actors", () => {
it("calls release actors when unmount", () => {
const { wrapper, client } = mount(
{},
{
initialState: {
actors: new Set(["actor 1", "actor 2"])
}
}
);
wrapper.unmount();
expect(client.releaseActor.mock.calls).toHaveLength(2);
expect(client.releaseActor.mock.calls[0][0]).toBe("actor 1");
expect(client.releaseActor.mock.calls[1][0]).toBe("actor 2");
});
it.skip("calls release actors when the roots prop changed", async () => {
const { wrapper, store, client } = mount(
{
injectWaitService: true
},
{
initialState: {
actors: new Set(["actor 3", "actor 4"])
}
}
);
const onRootsChanged = waitForDispatch(store, "ROOTS_CHANGED");
wrapper.setProps({
roots: [
{
path: "root-2",
contents: {
value: gripRepStubs.get("testMaxProps")
}
}
]
});
wrapper.update();
//
await onRootsChanged;
//
expect(client.releaseActor.mock.calls).toHaveLength(2);
expect(client.releaseActor.mock.calls[0][0]).toBe("actor 3");
expect(client.releaseActor.mock.calls[1][0]).toBe("actor 4");
});
});

Просмотреть файл

@ -0,0 +1,88 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { createNode } = require("../../utils/node");
const { waitForDispatch, mountObjectInspector } = require("../test-utils");
const gripWindowStubs = require("../../../reps/stubs/window");
const ObjectClient = require("../__mocks__/object-client");
const windowNode = createNode({
name: "window",
contents: { value: gripWindowStubs.get("Window") }
});
const client = { createObjectClient: grip => ObjectClient(grip) };
function generateDefaults(overrides) {
return {
autoExpandDepth: 0,
roots: [windowNode],
...overrides
};
}
describe("ObjectInspector - dimTopLevelWindow", () => {
it("renders window as expected when dimTopLevelWindow is true", async () => {
const props = generateDefaults({
dimTopLevelWindow: true
});
const { wrapper, store } = mountObjectInspector({ client, props });
let nodes = wrapper.find(".node");
const node = nodes.at(0);
expect(nodes.at(0).hasClass("lessen")).toBeTruthy();
expect(wrapper).toMatchSnapshot();
const onPropertiesLoaded = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
node.simulate("click");
await onPropertiesLoaded;
wrapper.update();
nodes = wrapper.find(".node");
expect(nodes.at(0).hasClass("lessen")).toBeFalsy();
expect(wrapper).toMatchSnapshot();
});
it("renders collapsed top-level window when dimTopLevelWindow =false", () => {
// The window node should not have the "lessen" class when
// dimTopLevelWindow is falsy.
const props = generateDefaults();
const { wrapper } = mountObjectInspector({ client, props });
expect(wrapper.find(".node.lessen").exists()).toBeFalsy();
expect(wrapper).toMatchSnapshot();
});
it("renders sub-level window", async () => {
// The window node should not have the "lessen" class when it is not at
// top level.
const root = createNode({
name: "root",
contents: [windowNode]
});
const props = generateDefaults({
roots: [root],
dimTopLevelWindow: true,
injectWaitService: true
});
const { wrapper, store } = mountObjectInspector({ client, props });
let nodes = wrapper.find(".node");
const node = nodes.at(0);
const onPropertiesLoaded = waitForDispatch(store, "NODE_PROPERTIES_LOADED");
node.simulate("click");
await onPropertiesLoaded;
wrapper.update();
nodes = wrapper.find(".node");
const win = nodes.at(1);
// Make sure we target the window object.
expect(win.find(".objectBox-Window").exists()).toBeTruthy();
expect(win.hasClass("lessen")).toBeFalsy();
expect(wrapper).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,253 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
// @flow
import type { Store } from "../types";
const { mount } = require("enzyme");
const React = require("react");
const { createFactory } = React;
const { Provider } = require("react-redux");
const { combineReducers } = require("redux");
const { thunk } = require("../../shared/redux/middleware/thunk");
const {
waitUntilService
} = require("../../shared/redux/middleware/waitUntilService");
/**
* Redux store utils
* @module utils/create-store
*/
import { createStore, applyMiddleware } from "redux";
const objectInspector = require("../index");
const {
getLoadedProperties,
getLoadedPropertyKeys,
getExpandedPaths,
getExpandedPathKeys
} = require("../reducer");
const ObjectInspector = createFactory(objectInspector.ObjectInspector);
const {
WAIT_UNTIL_TYPE
} = require("../../shared/redux/middleware/waitUntilService");
/*
* Takes an Enzyme wrapper (obtained with mount/shallow/) and
* returns a stringified version of the ObjectInspector, e.g.
*
* Map { Symbol(a) "value-a", Symbol(b) "value-b" }
* | size : 2
* | <entries>
* | | 0 : Symbol(a) "value-a"
* | | | <key> : Symbol(a)
* | | | <value> : "value-a"
* | | 1 : Symbol(b) "value-b"
* | | | <key> : Symbol(b)
* | | | <value> : "value-b"
* | <prototype> : Object { }
*
*/
function formatObjectInspector(wrapper: Object) {
const hasFocusedNode = wrapper.find(".tree-node.focused").length > 0;
const textTree = wrapper
.find(".tree-node")
.map(node => {
const indentStr = "| ".repeat((node.prop("aria-level") || 1) - 1);
// Need to target .arrow or Enzyme will also match the ArrowExpander
// component.
const arrow = node.find(".arrow");
let arrowStr = " ";
if (arrow.exists()) {
arrowStr = arrow.hasClass("expanded") ? "▼ " : "▶︎ ";
} else {
arrowStr = " ";
}
const icon = node
.find(".node")
.first()
.hasClass("block")
? "☲ "
: "";
let text = `${indentStr}${arrowStr}${icon}${getSanitizedNodeText(node)}`;
if (node.find("button.invoke-getter").exists()) {
text = `${text}(>>)`;
}
if (!hasFocusedNode) {
return text;
}
return node.hasClass("focused") ? `[ ${text} ]` : ` ${text}`;
})
.join("\n");
// Wrap the text representation in new lines so it keeps alignment between
// tree nodes.
return `\n${textTree}\n`;
}
function getSanitizedNodeText(node) {
// Stripping off the invisible space used in the indent.
return node.text().replace(/^\u200B+/, "");
}
/**
* Wait for a specific action type to be dispatched.
*
* @param {Object} store: Redux store
* @param {String} type: type of the actin to wait for
* @return {Promise}
*/
function waitForDispatch(
store: Object,
type: string
): Promise<{ type: string }> {
return new Promise(resolve => {
store.dispatch({
type: WAIT_UNTIL_TYPE,
predicate: action => action.type === type,
run: (dispatch, getState, action) => {
resolve(action);
}
});
});
}
/**
* Wait until the condition evaluates to something truthy
* @param {function} condition: function that we need for returning something
* truthy.
* @param {int} interval: Time to wait before trying to evaluate condition again
* @param {int} maxTries: Number of evaluation to try.
*/
async function waitFor(
condition: any => any,
interval: number = 50,
maxTries: number = 100
) {
let res = condition();
while (!res) {
await new Promise(done => setTimeout(done, interval));
maxTries--;
if (maxTries <= 0) {
throw new Error("waitFor - maxTries limit hit");
}
res = condition();
}
return res;
}
/**
* Wait until the state has all the expected keys for the loadedProperties
* state prop.
* @param {Redux Store} store: function that we need for returning something
* truthy.
* @param {Array} expectedKeys: Array of stringified keys.
* @param {int} interval: Time to wait before trying to evaluate condition again
* @param {int} maxTries: Number of evaluation to try.
*/
function waitForLoadedProperties(
store: Store,
expectedKeys: Array<string>,
interval: number,
maxTries: number
): Promise<any> {
return waitFor(
() => storeHasLoadedPropertiesKeys(store, expectedKeys),
interval,
maxTries
);
}
function storeHasLoadedPropertiesKeys(
store: Store,
expectedKeys: Array<string>
) {
return expectedKeys.every(key => storeHasLoadedProperty(store, key));
}
function storeHasLoadedProperty(store: Store, key: string): boolean {
return getLoadedPropertyKeys(store.getState()).some(
k => k.toString() === key
);
}
function storeHasExactLoadedProperties(
store: Store,
expectedKeys: Array<string>
) {
return (
expectedKeys.length === getLoadedProperties(store.getState()).size &&
expectedKeys.every(key => storeHasLoadedProperty(store, key))
);
}
function storeHasExpandedPaths(store: Store, expectedKeys: Array<string>) {
return expectedKeys.every(key => storeHasExpandedPath(store, key));
}
function storeHasExpandedPath(store: Store, key: string): boolean {
return getExpandedPathKeys(store.getState()).some(k => k.toString() === key);
}
function storeHasExactExpandedPaths(store: Store, expectedKeys: Array<string>) {
return (
expectedKeys.length === getExpandedPaths(store.getState()).size &&
expectedKeys.every(key => storeHasExpandedPath(store, key))
);
}
function createOiStore(client: any, initialState: any = {}) {
const reducers = { objectInspector: objectInspector.reducer.default };
return configureStore({
thunkArgs: args => ({ ...args, client })
})(combineReducers(reducers), initialState);
}
const configureStore = (opts: ReduxStoreOptions = {}) => {
const middleware = [thunk(opts.thunkArgs), waitUntilService];
return applyMiddleware(...middleware)(createStore);
};
function mountObjectInspector({ props, client, initialState = {} }) {
if (initialState.objectInspector) {
initialState.objectInspector = {
expandedPaths: new Set(),
loadedProperties: new Map(),
actors: new Set(),
...initialState.objectInspector
};
}
const store = createOiStore(client, initialState);
const wrapper = mount(
createFactory(Provider)({ store }, ObjectInspector(props))
);
const tree = wrapper.find(".tree");
return { store, tree, wrapper, client };
}
module.exports = {
formatObjectInspector,
storeHasExpandedPaths,
storeHasExpandedPath,
storeHasExactExpandedPaths,
storeHasLoadedPropertiesKeys,
storeHasLoadedProperty,
storeHasExactLoadedProperties,
waitFor,
waitForDispatch,
waitForLoadedProperties,
mountObjectInspector,
createStore: createOiStore
};

Просмотреть файл

@ -0,0 +1,58 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`promises utils function makeNodesForPromiseProperties 1`] = `
Array [
Object {
"contents": Object {
"value": "rejected",
},
"meta": undefined,
"name": "<state>",
"parent": Object {
"contents": Object {
"value": Object {
"actor": "server2.conn2.child1/pausedobj36",
"class": "Promise",
"promiseState": Object {
"reason": Object {
"type": "3",
},
"state": "rejected",
},
"type": "object",
},
},
"path": "root",
},
"path": Symbol(root/<state>),
"type": Symbol(<state>),
},
Object {
"contents": Object {
"value": Object {
"type": "3",
},
},
"meta": undefined,
"name": "<reason>",
"parent": Object {
"contents": Object {
"value": Object {
"actor": "server2.conn2.child1/pausedobj36",
"class": "Promise",
"promiseState": Object {
"reason": Object {
"type": "3",
},
"state": "rejected",
},
"type": "object",
},
},
"path": "root",
},
"path": Symbol(root/<reason>),
"type": Symbol(<reason>),
},
]
`;

Просмотреть файл

@ -0,0 +1,87 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
const { createNode, NODE_TYPES } = require("../../utils/node");
describe("createNode", () => {
it("returns null when contents is undefined", () => {
expect(createNode({ name: "name" })).toBeNull();
});
it("does not return null when contents is null", () => {
expect(
createNode({
name: "name",
path: "path",
contents: null
})
).not.toBe(null);
});
it("returns the expected object when parent is undefined", () => {
const node = createNode({
name: "name",
path: "path",
contents: "contents"
});
expect(node).toEqual({
name: "name",
path: node.path,
contents: "contents",
type: NODE_TYPES.GRIP
});
});
it("returns the expected object when parent is not null", () => {
const root = createNode({ name: "name", contents: null });
const child = createNode({
parent: root,
name: "name",
path: "path",
contents: "contents"
});
expect(child.parent).toEqual(root);
});
it("returns the expected object when type is not undefined", () => {
const root = createNode({ name: "name", contents: null });
const child = createNode({
parent: root,
name: "name",
path: "path",
contents: "contents",
type: NODE_TYPES.BUCKET
});
expect(child.type).toEqual(NODE_TYPES.BUCKET);
});
it("uses the name property for the path when path is not provided", () => {
expect(
createNode({ name: "name", contents: "contents" }).path.toString()
).toBe("Symbol(name)");
});
it("wraps the path in a Symbol when provided", () => {
expect(
createNode({
name: "name",
path: "path",
contents: "contents"
}).path.toString()
).toBe("Symbol(path)");
});
it("uses parent path to compute its path", () => {
const root = createNode({ name: "root", contents: null });
expect(
createNode({
parent: root,
name: "name",
path: "path",
contents: "contents"
}).path.toString()
).toBe("Symbol(root/path)");
});
});

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше