diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /devtools/client/shared/components/test | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/shared/components/test')
177 files changed, 37316 insertions, 0 deletions
diff --git a/devtools/client/shared/components/test/browser/browser.toml b/devtools/client/shared/components/test/browser/browser.toml new file mode 100644 index 0000000000..46412aae90 --- /dev/null +++ b/devtools/client/shared/components/test/browser/browser.toml @@ -0,0 +1,14 @@ +[DEFAULT] +tags = "devtools" +subsuite = "devtools" +support-files = [ + "!/devtools/client/shared/test/shared-head.js", + "!/devtools/client/shared/test/telemetry-test-helpers.js", +] + +["browser_notification_box_basic.js"] + +["browser_reps_stubs.js"] +skip-if = [ + "!fission", # we need specific iframe targets for some evaluation +] diff --git a/devtools/client/shared/components/test/browser/browser_notification_box_basic.js b/devtools/client/shared/components/test/browser/browser_notification_box_basic.js new file mode 100644 index 0000000000..0103c677d2 --- /dev/null +++ b/devtools/client/shared/components/test/browser/browser_notification_box_basic.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", + this +); + +const TEST_URI = "data:text/html;charset=utf-8,Test page"; + +/** + * Basic test that checks existence of the Notification box. + */ +add_task(async function () { + info("Test Notification box basic started"); + + const toolbox = await openNewTabAndToolbox(TEST_URI, "webconsole"); + + // Append a notification + const notificationBox = toolbox.getNotificationBox(); + notificationBox.appendNotification( + "Info message", + "id1", + null, + notificationBox.PRIORITY_INFO_HIGH + ); + + // Verify existence of one notification. + const parentNode = toolbox.doc.getElementById("toolbox-notificationbox"); + const nodes = parentNode.querySelectorAll(".notification"); + is(nodes.length, 1, "There must be one notification"); +}); diff --git a/devtools/client/shared/components/test/browser/browser_reps_stubs.js b/devtools/client/shared/components/test/browser/browser_reps_stubs.js new file mode 100644 index 0000000000..29b56ab151 --- /dev/null +++ b/devtools/client/shared/components/test/browser/browser_reps_stubs.js @@ -0,0 +1,406 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", + this +); + +const TEST_URI = "data:text/html;charset=utf-8,stub generation"; +/** + * A Map keyed by filename, and for which the value is also a Map, with the key being the + * label for the stub, and the value the expression to evaluate to get the stub. + */ +const EXPRESSIONS_BY_FILE = { + "attribute.js": new Map([ + [ + "Attribute", + `{ + const a = document.createAttribute("class") + a.value = "autocomplete-suggestions"; + a; + }`, + ], + ]), + "comment-node.js": new Map([ + [ + "Comment", + `{ + document.createComment("test\\nand test\\nand test\\nand test\\nand test\\nand test\\nand test") + }`, + ], + ]), + "date-time.js": new Map([ + ["DateTime", `new Date(1459372644859)`], + ["InvalidDateTime", `new Date("invalid")`], + ]), + "infinity.js": new Map([ + ["Infinity", `Infinity`], + ["NegativeInfinity", `-Infinity`], + ]), + "nan.js": new Map([["NaN", `2 * document`]]), + "null.js": new Map([["Null", `null`]]), + "number.js": new Map([ + ["Int", `2 + 3`], + ["True", `true`], + ["False", `false`], + ["NegZeroGrip", `1 / -Infinity`], + ]), + "stylesheet.js": new Map([ + [ + "StyleSheet", + { + expression: ` + (async function() { + const link = document.createElement("link"); + link.setAttribute("rel", "stylesheet"); + link.type = "text/css"; + link.href = "https://example.com/styles.css"; + const onStylesheetHandled = new Promise(res => { + // The file does not exist so we'll get an error event, but it will + // still be put in document.styleSheets with its src, which is what we want. + link.addEventListener("error", () => res(), { once: true}); + }) + document.head.appendChild(link); + await onStylesheetHandled; + return document.styleSheets[0]; + })() + `, + async: true, + }, + ], + ]), + "symbol.js": new Map([ + ["Symbol", `Symbol("foo")`], + ["SymbolWithoutIdentifier", `Symbol()`], + ["SymbolWithLongString", `Symbol("aa".repeat(10000))`], + ]), + "text-node.js": new Map([ + [ + "testRendering", + `let tn = document.createTextNode("hello world"); + document.body.append(tn); + tn;`, + ], + ["testRenderingDisconnected", `document.createTextNode("hello world")`], + ["testRenderingWithEOL", `document.createTextNode("hello\\nworld")`], + ["testRenderingWithDoubleQuote", `document.createTextNode('hello"world')`], + [ + "testRenderingWithLongString", + `document.createTextNode("a\\n" + ("a").repeat(20000))`, + ], + ]), + "undefined.js": new Map([["Undefined", `undefined`]]), + "window.js": new Map([ + ["Window", `window`], + [ + "CrossOriginIframeContentWindow", + { + expression: ` + (async function() { + const iframe = document.createElement("iframe"); + const onLoaded = new Promise(resolve => + iframe.addEventListener("load", resolve, {once: true}) + ); + iframe.src = "http://example.org/document-builder.sjs?html=example.org"; + document.body.append(iframe); + await onLoaded; + return iframe.contentWindow; + })() + `, + async: true, + }, + ], + [ + "CrossOriginIframeTopWindow", + { + expression: `window.top`, + iframeUrlForExecution: + "https://example.net/document-builder.sjs?html=example.net", + }, + ], + ]), + // XXX: File a bug blocking Bug 1671400 for enabling automatic generation for one of + // the following file. + // "accessible.js", + // "accessor.js", + // "big-int.js", + // "document-type.js", + // "document.js", + // "element-node.js", + // "error.js", + // "event.js", + // "failure.js", + // "function.js", + // "grip-array.js", + // "grip-entry.js", + // "grip-map.js", + // "grip.js", + // "long-string.js", + // "object-with-text.js", + // "object-with-url.js", + // "promise.js", + // "regexp.js", +}; + +add_task(async function () { + const isStubsUpdate = Services.env.get(STUBS_UPDATE_ENV) == "true"; + + const tab = await addTab(TEST_URI); + const { + CommandsFactory, + } = require("devtools/shared/commands/commands-factory"); + const commands = await CommandsFactory.forTab(tab); + await commands.targetCommand.startListening(); + + let failed = false; + for (const stubFile of Object.keys(EXPRESSIONS_BY_FILE)) { + info(`${isStubsUpdate ? "Update" : "Check"} ${stubFile}`); + + const generatedStubs = await generateStubs(commands, stubFile); + if (isStubsUpdate) { + await writeStubsToFile(stubFile, generatedStubs); + ok(true, `${stubFile} was updated`); + continue; + } + + const existingStubs = getStubFile(stubFile); + if (generatedStubs.size !== existingStubs.size) { + failed = true; + continue; + } + + for (const [key, packet] of generatedStubs) { + const packetStr = getSerializedPacket(packet, { + sortKeys: true, + replaceActorIds: true, + }); + const grip = getSerializedPacket(existingStubs.get(key), { + sortKeys: true, + replaceActorIds: true, + }); + is(packetStr, grip, `"${key}" packet has expected value`); + failed = failed || packetStr !== grip; + } + } + + if (failed) { + ok( + false, + "The reps stubs need to be updated by running `" + + `mach test ${getCurrentTestFilePath()} --headless --setenv STUBS_UPDATE=true` + + "`" + ); + } else { + ok(true, "Stubs are up to date"); + } + + await removeTab(tab); +}); + +async function generateStubs(commands, stubFile) { + const stubs = new Map(); + + for (const [key, options] of EXPRESSIONS_BY_FILE[stubFile]) { + const expression = + typeof options == "string" ? options : options.expression; + const executeOptions = {}; + if (options.async === true) { + executeOptions.mapped = { await: true }; + } + if (options.iframeUrlForExecution) { + const { promise: onIframeTargetCreated, resolve } = + Promise.withResolvers(); + const onTargetAvailable = ({ targetFront }) => { + if (targetFront.url === options.iframeUrlForExecution) { + resolve(targetFront); + } + }; + await commands.targetCommand.watchTargets({ + types: [commands.targetCommand.TYPES.FRAME], + onAvailable: onTargetAvailable, + }); + + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [options.iframeUrlForExecution], + url => { + const iframe = content.document.createElement("iframe"); + iframe.src = url; + content.document.body.append(iframe); + } + ); + + const targetFront = await onIframeTargetCreated; + executeOptions.selectedTargetFront = targetFront; + + await commands.targetCommand.unwatchTargets({ + types: [commands.targetCommand.TYPES.FRAME], + onAvailable: onTargetAvailable, + }); + } + const { result } = await commands.scriptCommand.execute( + expression, + executeOptions + ); + stubs.set(key, getCleanedPacket(stubFile, key, result)); + } + + return stubs; +} + +function getCleanedPacket(stubFile, key, packet) { + // Remove the targetFront property that has a cyclical reference and that we don't need + // in our node tests. + delete packet.targetFront; + + const existingStubs = getStubFile(stubFile); + if (!existingStubs) { + return packet; + } + + // Strip escaped characters. + const safeKey = key + .replace(/\\n/g, "\n") + .replace(/\\r/g, "\r") + .replace(/\\\"/g, `\"`) + .replace(/\\\'/g, `\'`); + if (!existingStubs.has(safeKey)) { + return packet; + } + + // If the stub already exist, we want to ignore irrelevant properties (generated id, timer, …) + // that might changed and "pollute" the diff resulting from this stub generation. + const existingPacket = existingStubs.get(safeKey); + + // copy existing contentDomReference + if ( + packet._grip?.contentDomReference?.id && + existingPacket._grip?.contentDomReference?.id + ) { + packet._grip.contentDomReference = existingPacket._grip.contentDomReference; + } + + // `window`'s properties count can vary from OS to OS, so we clean `ownPropertyLength`. + if ( + existingPacket && + packet._grip?.class === "Window" && + typeof packet._grip.ownPropertyLength == + typeof existingPacket._grip.ownPropertyLength + ) { + packet._grip.ownPropertyLength = existingPacket._grip.ownPropertyLength; + } + + return packet; +} + +// HELPER + +const CHROME_PREFIX = "chrome://mochitests/content/browser/"; +const STUBS_FOLDER = "devtools/client/shared/components/test/node/stubs/reps/"; +const STUBS_UPDATE_ENV = "STUBS_UPDATE"; + +/** + * Write stubs to a given file + * + * @param {String} fileName: The file to write the stubs in. + * @param {Map} packets: A Map of the packets. + */ +async function writeStubsToFile(fileName, packets) { + const mozRepo = Services.env.get("MOZ_DEVELOPER_REPO_DIR"); + const filePath = `${mozRepo}/${STUBS_FOLDER + fileName}`; + + const stubs = Array.from(packets.entries()).map(([key, packet]) => { + const stringifiedPacket = getSerializedPacket(packet); + return `stubs.set(\`${key}\`, ${stringifiedPacket});`; + }); + + const fileContent = `/* 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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +${stubs.join("\n\n")} + +module.exports = stubs; +`; + + const textEncoder = new TextEncoder(); + await IOUtils.write(filePath, textEncoder.encode(fileContent)); +} + +function getStubFile(fileName) { + return require(CHROME_PREFIX + STUBS_FOLDER + fileName); +} + +function sortObjectKeys(obj) { + const isArray = Array.isArray(obj); + const isObject = Object.prototype.toString.call(obj) === "[object Object]"; + const isFront = obj?._grip; + + if (isObject && !isFront) { + // Reorder keys for objects, but skip fronts to avoid infinite recursion. + const sortedKeys = Object.keys(obj).sort((k1, k2) => k1.localeCompare(k2)); + const withSortedKeys = {}; + sortedKeys.forEach(k => { + withSortedKeys[k] = k !== "stacktrace" ? sortObjectKeys(obj[k]) : obj[k]; + }); + return withSortedKeys; + } else if (isArray) { + return obj.map(item => sortObjectKeys(item)); + } + return obj; +} + +/** + * @param {Object} packet + * The packet to serialize. + * @param {Object} options + * @param {Boolean} options.sortKeys + * Pass true to sort all keys alphabetically in the packet before serialization. + * For instance stub comparison should not fail if the order of properties changed. + * @param {Boolean} options.replaceActorIds + * Pass true to replace actorIDs with a fake one so it's easier to compare stubs + * that includes grips. + */ +function getSerializedPacket( + packet, + { sortKeys = false, replaceActorIds = false } = {} +) { + if (sortKeys) { + packet = sortObjectKeys(packet); + } + + const actorIdPlaceholder = "XXX"; + + return JSON.stringify( + packet, + function (key, value) { + // The message can have fronts that we need to serialize + if (value && value._grip) { + return { + _grip: value._grip, + actorID: replaceActorIds ? actorIdPlaceholder : value.actorID, + }; + } + + if ( + replaceActorIds && + (key === "actor" || key === "actorID" || key === "sourceId") && + typeof value === "string" + ) { + return actorIdPlaceholder; + } + + return value; + }, + 2 + ); +} diff --git a/devtools/client/shared/components/test/chrome/accordion.snapshots.js b/devtools/client/shared/components/test/chrome/accordion.snapshots.js new file mode 100644 index 0000000000..0f649b52d6 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/accordion.snapshots.js @@ -0,0 +1,176 @@ +/* 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/. */ +"use strict"; + +window._snapshots = { + "Accordion basic render.": { + type: "ul", + props: { + className: "accordion", + tabIndex: -1, + }, + children: [ + { + type: "li", + props: { + id: "accordion-item-1", + className: "accordion-item", + "aria-labelledby": "accordion-item-1-header", + }, + children: [ + { + type: "h2", + props: { + id: "accordion-item-1-header", + className: "accordion-header", + tabIndex: 0, + "aria-expanded": false, + "aria-label": "Test Accordion Item 1", + onKeyDown: "event => this.onHeaderKeyDown(event, item)", + onClick: "event => this.onHeaderClick(event, item)", + }, + children: [ + { + type: "span", + props: { + className: "theme-twisty", + role: "presentation", + }, + children: null, + }, + { + type: "span", + props: { className: "accordion-header-label" }, + children: ["Test Accordion Item 1"], + }, + ], + }, + { + type: "div", + props: { + className: "accordion-content", + hidden: true, + role: "presentation", + }, + children: null, + }, + ], + }, + { + type: "li", + props: { + id: "accordion-item-2", + className: "accordion-item", + "aria-labelledby": "accordion-item-2-header", + }, + children: [ + { + type: "h2", + props: { + id: "accordion-item-2-header", + className: "accordion-header", + tabIndex: 0, + "aria-expanded": false, + "aria-label": "Test Accordion Item 2", + onKeyDown: "event => this.onHeaderKeyDown(event, item)", + onClick: "event => this.onHeaderClick(event, item)", + }, + children: [ + { + type: "span", + props: { + className: "theme-twisty", + role: "presentation", + }, + children: null, + }, + { + type: "span", + props: { className: "accordion-header-label" }, + children: ["Test Accordion Item 2"], + }, + { + type: "span", + props: { + className: "accordion-header-buttons", + role: "presentation", + }, + children: [ + { + type: "button", + props: {}, + children: null, + }, + ], + }, + ], + }, + { + type: "div", + props: { + className: "accordion-content", + hidden: true, + role: "presentation", + }, + children: null, + }, + ], + }, + { + type: "li", + props: { + id: "accordion-item-3", + className: "accordion-item accordion-open", + "aria-labelledby": "accordion-item-3-header", + }, + children: [ + { + type: "h2", + props: { + id: "accordion-item-3-header", + className: "accordion-header", + tabIndex: 0, + "aria-expanded": true, + "aria-label": "Test Accordion Item 3", + onKeyDown: "event => this.onHeaderKeyDown(event, item)", + onClick: "event => this.onHeaderClick(event, item)", + }, + children: [ + { + type: "span", + props: { + className: "theme-twisty open", + role: "presentation", + }, + children: null, + }, + { + type: "span", + props: { + className: "accordion-header-label", + }, + children: ["Test Accordion Item 3"], + }, + ], + }, + { + type: "div", + props: { + className: "accordion-content", + hidden: false, + role: "presentation", + }, + children: [ + { + type: "div", + props: {}, + children: null, + }, + ], + }, + ], + }, + ], + }, +}; diff --git a/devtools/client/shared/components/test/chrome/chrome.toml b/devtools/client/shared/components/test/chrome/chrome.toml new file mode 100644 index 0000000000..ff9a5f1883 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/chrome.toml @@ -0,0 +1,87 @@ +[DEFAULT] +support-files = [ + "head.js", + "accordion.snapshots.js", +] + +["test_GridElementWidthResizer.html"] + +["test_GridElementWidthResizer_RTL.html"] + +["test_HSplitBox_01.html"] + +["test_accordion.html"] + +["test_frame_01.html"] + +["test_frame_02.html"] + +["test_list.html"] + +["test_list_keyboard.html"] + +["test_notification_box_01.html"] + +["test_notification_box_02.html"] + +["test_notification_box_03.html"] + +["test_notification_box_04.html"] + +["test_notification_box_05.html"] + +["test_searchbox-with-autocomplete.html"] + +["test_searchbox.html"] + +["test_sidebar_toggle.html"] + +["test_smart-trace-grouping.html"] + +["test_smart-trace-source-maps.html"] + +["test_smart-trace.html"] + +["test_stack-trace-source-maps.html"] + +["test_stack-trace.html"] + +["test_tabs_accessibility.html"] + +["test_tabs_menu.html"] + +["test_tree-view_01.html"] + +["test_tree-view_02.html"] + +["test_tree_01.html"] + +["test_tree_02.html"] + +["test_tree_03.html"] + +["test_tree_04.html"] + +["test_tree_05.html"] + +["test_tree_06.html"] + +["test_tree_07.html"] + +["test_tree_08.html"] + +["test_tree_09.html"] + +["test_tree_10.html"] + +["test_tree_11.html"] + +["test_tree_12.html"] + +["test_tree_13.html"] + +["test_tree_14.html"] + +["test_tree_15.html"] + +["test_tree_16.html"] diff --git a/devtools/client/shared/components/test/chrome/head.js b/devtools/client/shared/components/test/chrome/head.js new file mode 100644 index 0000000000..7abe54942f --- /dev/null +++ b/devtools/client/shared/components/test/chrome/head.js @@ -0,0 +1,379 @@ +/* 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/. */ +/* eslint no-unused-vars: [2, {"vars": "local"}] */ + +/* global _snapshots */ + +"use strict"; + +var { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" +); +var { Assert } = ChromeUtils.importESModule( + "resource://testing-common/Assert.sys.mjs" +); +var { gDevTools } = require("resource://devtools/client/framework/devtools.js"); +var { BrowserLoader } = ChromeUtils.import( + "resource://devtools/shared/loader/browser-loader.js" +); +var { + DevToolsServer, +} = require("resource://devtools/server/devtools-server.js"); +var { + DevToolsClient, +} = require("resource://devtools/client/devtools-client.js"); +var DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js"); +var { Toolbox } = require("resource://devtools/client/framework/toolbox.js"); + +var { require: browserRequire } = BrowserLoader({ + baseURI: "resource://devtools/client/shared/", + window, +}); + +const React = browserRequire("devtools/client/shared/vendor/react"); +const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); +const dom = browserRequire("devtools/client/shared/vendor/react-dom-factories"); +const TestUtils = browserRequire( + "devtools/client/shared/vendor/react-dom-test-utils" +); + +const ShallowRenderer = browserRequire( + "devtools/client/shared/vendor/react-test-renderer-shallow" +); +const TestRenderer = browserRequire( + "devtools/client/shared/vendor/react-test-renderer" +); + +var EXAMPLE_URL = "https://example.com/browser/browser/devtools/shared/test/"; + +SimpleTest.registerCleanupFunction(() => { + window._snapshots = null; +}); + +function forceRender(comp) { + return setState(comp, {}).then(() => setState(comp, {})); +} + +// All tests are asynchronous. +SimpleTest.waitForExplicitFinish(); + +function onNextAnimationFrame(fn) { + return () => requestAnimationFrame(() => requestAnimationFrame(fn)); +} + +function setState(component, newState) { + return new Promise(resolve => { + component.setState(newState, onNextAnimationFrame(resolve)); + }); +} + +function dumpn(msg) { + dump(`SHARED-COMPONENTS-TEST: ${msg}\n`); +} + +/** + * Tree View + */ + +const TEST_TREE_VIEW = { + A: { label: "A", value: "A" }, + B: { label: "B", value: "B" }, + C: { label: "C", value: "C" }, + D: { label: "D", value: "D" }, + E: { label: "E", value: "E" }, + F: { label: "F", value: "F" }, + G: { label: "G", value: "G" }, + H: { label: "H", value: "H" }, + I: { label: "I", value: "I" }, + J: { label: "J", value: "J" }, + K: { label: "K", value: "K" }, + L: { label: "L", value: "L" }, +}; + +TEST_TREE_VIEW.children = { + A: [TEST_TREE_VIEW.B, TEST_TREE_VIEW.C, TEST_TREE_VIEW.D], + B: [TEST_TREE_VIEW.E, TEST_TREE_VIEW.F, TEST_TREE_VIEW.G], + C: [TEST_TREE_VIEW.H, TEST_TREE_VIEW.I], + D: [TEST_TREE_VIEW.J], + E: [TEST_TREE_VIEW.K, TEST_TREE_VIEW.L], + F: [], + G: [], + H: [], + I: [], + J: [], + K: [], + L: [], +}; + +const TEST_TREE_VIEW_INTERFACE = { + provider: { + getChildren: x => TEST_TREE_VIEW.children[x.label], + hasChildren: x => !!TEST_TREE_VIEW.children[x.label].length, + getLabel: x => x.label, + getValue: x => x.value, + getKey: x => x.label, + getType: () => "string", + }, + object: TEST_TREE_VIEW.A, + columns: [{ id: "default" }, { id: "value" }], +}; + +/** + * Tree + */ + +var TEST_TREE_INTERFACE = { + getParent: x => TEST_TREE.parent[x], + getChildren: x => TEST_TREE.children[x], + renderItem: (x, depth, focused) => + "-".repeat(depth) + x + ":" + focused + "\n", + getRoots: () => ["A", "M"], + getKey: x => "key-" + x, + itemHeight: 1, + onExpand: x => TEST_TREE.expanded.add(x), + onCollapse: x => TEST_TREE.expanded.delete(x), + isExpanded: x => TEST_TREE.expanded.has(x), +}; + +function isRenderedTree(actual, expectedDescription, msg) { + const expected = expectedDescription.map(x => x + "\n").join(""); + dumpn(`Expected tree:\n${expected}`); + dumpn(`Actual tree:\n${actual}`); + is(actual, expected, msg); +} + +function isAccessibleTree(tree, options = {}) { + const treeNode = tree.refs.tree; + is(treeNode.getAttribute("tabindex"), "0", "Tab index is set"); + is(treeNode.getAttribute("role"), "tree", "Tree semantics is present"); + if (options.hasActiveDescendant) { + ok( + treeNode.hasAttribute("aria-activedescendant"), + "Tree has an active descendant set" + ); + } + + const treeNodes = [...treeNode.querySelectorAll(".tree-node")]; + for (const node of treeNodes) { + ok(node.id, "TreeNode has an id"); + is(node.getAttribute("role"), "treeitem", "Tree item semantics is present"); + is( + parseInt(node.getAttribute("aria-level"), 10), + parseInt(node.getAttribute("data-depth"), 10) + 1, + "Aria level attribute is set correctly" + ); + } +} + +// 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", + }, + expanded: new Set(), +}; + +/** + * Frame + */ +function checkFrameString({ + el, + file, + line, + column, + source, + functionName, + shouldLink, + tooltip, + locationPrefix, +}) { + const $ = selector => el.querySelector(selector); + + const $func = $(".frame-link-function-display-name"); + const $source = $(".frame-link-source"); + const $locationPrefix = $(".frame-link-prefix"); + const $filename = $(".frame-link-filename"); + const $line = $(".frame-link-line"); + + is($filename.textContent, file, "Correct filename"); + is( + el.getAttribute("data-line"), + line ? `${line}` : null, + "Expected `data-line` found" + ); + is( + el.getAttribute("data-column"), + column ? `${column}` : null, + "Expected `data-column` found" + ); + is($source.getAttribute("title"), tooltip, "Correct tooltip"); + is($source.tagName, shouldLink ? "A" : "SPAN", "Correct linkable status"); + if (shouldLink) { + is($source.getAttribute("href"), source, "Correct source"); + } + + if (line != null) { + let lineText = `:${line}`; + if (column != null) { + lineText += `:${column}`; + } + + is($line.textContent, lineText, "Correct line number"); + } else { + ok(!$line, "Should not have an element for `line`"); + } + + if (functionName != null) { + is($func.textContent, functionName, "Correct function name"); + } else { + ok(!$func, "Should not have an element for `functionName`"); + } + + if (locationPrefix != null) { + is($locationPrefix.textContent, locationPrefix, "Correct prefix"); + } else { + ok(!$locationPrefix, "Should not have an element for `locationPrefix`"); + } +} + +function checkSmartFrameString({ el, location, functionName, tooltip }) { + const $ = selector => el.querySelector(selector); + + const $func = $(".title"); + const $location = $(".location"); + + is($location.textContent, location, "Correct filename"); + is(el.getAttribute("title"), tooltip, "Correct tooltip"); + if (functionName != null) { + is($func.textContent, functionName, "Correct function name"); + } else { + ok(!$func, "Should not have an element for `functionName`"); + } +} + +function renderComponent(component, props) { + const el = React.createElement(component, props, {}); + // By default, renderIntoDocument() won't work for stateless components, but + // it will work if the stateless component is wrapped in a stateful one. + // See https://github.com/facebook/react/issues/4839 + const wrappedEl = dom.span({}, [el]); + const renderedComponent = TestUtils.renderIntoDocument(wrappedEl); + return ReactDOM.findDOMNode(renderedComponent).children[0]; +} + +function shallowRenderComponent(component, props) { + const el = React.createElement(component, props); + const renderer = new ShallowRenderer(); + renderer.render(el, {}); + return renderer.getRenderOutput(); +} + +/** + * Creates a React Component for testing + * + * @param {string} factory - factory object of the component to be created + * @param {object} props - React props for the component + * @returns {object} - container Node, Object with React component + * and querySelector function with $ as name. + */ +async function createComponentTest(factory, props) { + const container = document.createElement("div"); + document.body.appendChild(container); + + const component = ReactDOM.render(factory(props), container); + await forceRender(component); + + return { + container, + component, + $: s => container.querySelector(s), + }; +} + +async function waitFor(condition = () => true, delay = 50) { + do { + const res = condition(); + if (res) { + return res; + } + await new Promise(resolve => setTimeout(resolve, delay)); + } while (true); +} + +/** + * Matches a component tree rendererd using TestRenderer to a given expected JSON + * snapshot. + * @param {String} name + * Name of the function derived from a test [step] name. + * @param {Object} el + * React element to be rendered using TestRenderer. + */ +function matchSnapshot(name, el) { + if (!_snapshots) { + is(false, "No snapshots were loaded into test."); + } + + const snapshot = _snapshots[name]; + if (snapshot === undefined) { + is(false, `Snapshot for "${name}" not found.`); + } + + const renderer = TestRenderer.create(el, {}); + const tree = renderer.toJSON(); + + is( + JSON.stringify(tree, (key, value) => + typeof value === "function" ? value.toString() : value + ), + JSON.stringify(snapshot), + name + ); +} diff --git a/devtools/client/shared/components/test/chrome/test_GridElementWidthResizer.html b/devtools/client/shared/components/test/chrome/test_GridElementWidthResizer.html new file mode 100644 index 0000000000..cf30255141 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_GridElementWidthResizer.html @@ -0,0 +1,209 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> + <!-- Basic tests for the GridElementWidthResizer component. --> + <head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link href="chrome://mochikit/content/tests/SimpleTest/test.css" rel="stylesheet" type="text/css"/> + <link href="chrome://devtools/skin/splitters.css" rel="stylesheet" type="text/css"/> + <link href="chrome://devtools/content/shared/components/splitter/GridElementResizer.css" rel="stylesheet" type="text/css"/> + <style> + * { + box-sizing: border-box; + } + + html { + --theme-splitter-color: red; + } + + main { + display: grid; + grid-template-columns: auto 1fr auto; + grid-template-rows: 20px 20px 20px; + grid-gap: 10px; + } + + .a, + .b, + .c, + .d { + border: 1px solid green; + } + + header { + grid-column: 1 / -1; + } + .a { + grid-column: 1 / 2; + grid-row: 2 / -1; + min-width: 100px; + } + .b { + grid-column: 2 / 3; + grid-row: 2 / -1; + } + + .c { + grid-column: 3 / 4; + grid-row: 2 / 3; + } + + .d { + grid-column: 3 / 4; + grid-row: 3 / 4; + min-width: 150px; + } + + .resizer-a { + grid-column: 1 / 2; + grid-row: 2 / -1; + } + + .resizer-d { + grid-column: 3 / 4; + grid-row: 2 / -1; + } + </style> + </head> + <body> + <main></main> + <pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +const FUDGE_FACTOR = .5; +function aboutEq(a, b) { + dumpn(`Checking ${a} ~= ${b}`); + return Math.abs(a - b) < FUDGE_FACTOR; +} + +window.onload = async function () { + try { + const React = browserRequire("devtools/client/shared/vendor/react"); + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const dom = require("devtools/client/shared/vendor/react-dom-factories"); + + const GridElementWidthResizer = React.createFactory(browserRequire("devtools/client/shared/components/splitter/GridElementWidthResizer")); + ok(GridElementWidthResizer, "Should get GridElementWidthResizer"); + + const resizedA = []; + const resizedD = []; + + ReactDOM.render([ + dom.header({}, "header"), + dom.aside({className: "a"}, "A"), + GridElementWidthResizer({ + getControlledElementNode: () => a, + enabled: true, + position: "end", + className: "resizer-a", + onResizeEnd: width => resizedA.push(width), + }), + dom.section({className: "b"}, "B"), + GridElementWidthResizer({ + getControlledElementNode: () => window.document.querySelector(".b"), + enabled: false, + position: "start", + className: "resizer-disabled", + }), + dom.aside({className: "c"}, "C"), + dom.aside({className: "d"}, "D"), + GridElementWidthResizer({ + getControlledElementNode: () => d, + enabled: true, + position: "start", + className: "resizer-d", + onResizeEnd: width => resizedD.push(width), + }), + ], window.document.querySelector("main")); + + // wait for a bit as we may not have everything setup yet. + await new Promise(res => setTimeout(res, 10)); + + const a = window.document.querySelector(".a"); + const d = window.document.querySelector(".d"); + + // Test that we properly rendered our two resizers, and not the disabled one. + const resizers = window.document.querySelectorAll(".grid-element-width-resizer"); + is(resizers.length, 2, "The 2 enabled resizers are rendered"); + + const [resizerA, resizerD] = resizers; + + ok(resizerA.classList.contains("resizer-a") + && resizerA.classList.contains("end"), "resizerA has expected classes"); + ok(resizerD.classList.contains("resizer-d") + && resizerD.classList.contains("start"), "resizerD has expected classes"); + + const aBoundingRect = a.getBoundingClientRect(); + const aOriginalWidth = aBoundingRect.width; + + info("Resize element A"); + await resize(resizerA, aBoundingRect.right + 20); + + is(resizedA.length, 1, "onResizeEnd was called once"); + is(resizedD.length, 0, "resizerD was not impacted"); + let aWidth = a.getBoundingClientRect().width; + is(resizedA[0], aWidth, "onResizeEnd gives the width of the controlled element"); + ok(aboutEq(aWidth, aOriginalWidth + 20), + "controlled element was resized to the expected size"); + + info("Resize element A below its min width"); + await resize(resizerA, [aBoundingRect.left + 10]); + aWidth = a.getBoundingClientRect().width; + ok(aboutEq(aWidth, 100), "controlled element was resized to its min width"); + + info("Resize element D below its min width"); + const dBoundingRect = d.getBoundingClientRect(); + const dOriginalWidth = dBoundingRect.width; + + await resize(resizerD, dBoundingRect.left + 100); + is(resizedD.length, 1, "onResizeEnd was called once for d"); + is(resizedA.length, 2, "onResizeEnd wasn't called for a"); + let dWidth = d.getBoundingClientRect().width; + is(resizedD[0], dWidth, "onResizeEnd gives the width of the controlled element"); + ok(aboutEq(dWidth, 150), "controlled element wasn't resized below its min-width"); + + info("Resize element D"); + await resize(resizerD, dBoundingRect.left - 15); + dWidth = d.getBoundingClientRect().width; + is(resizedD[1], dWidth, "onResizeEnd gives the width of the controlled element"); + ok(aboutEq(dWidth, dOriginalWidth + 15), "element was resized"); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } + + async function resize(resizer, clientX) { + info("Mouse down to start dragging"); + synthesizeMouseAtCenter(resizer, { button: 0, type: "mousedown" }, window); + await SimpleTest.promiseWaitForCondition( + () => document.firstElementChild.classList.contains("dragging"), + "dragging class is never set on the root element" + ); + ok(true, "The dragging class is set on the root element"); + + const event = new MouseEvent("mousemove", { clientX }); + resizer.dispatchEvent(event); + + info("Mouse up to stop resizing"); + synthesizeMouseAtCenter(document.body, { button: 0, type: "mouseup" }, window); + + await SimpleTest.promiseWaitForCondition( + () => !document.firstElementChild.classList.contains("dragging"), + "dragging class is never removed from the root element" + ); + ok(true, "The dragging class is removed from the root element"); + } +}; +</script> +</pre> + </body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_GridElementWidthResizer_RTL.html b/devtools/client/shared/components/test/chrome/test_GridElementWidthResizer_RTL.html new file mode 100644 index 0000000000..3768ecf0a0 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_GridElementWidthResizer_RTL.html @@ -0,0 +1,210 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> + <!-- Basic tests for the GridElementWidthResizer component. --> + <head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link href="chrome://mochikit/content/tests/SimpleTest/test.css" rel="stylesheet" type="text/css"/> + <link href="chrome://devtools/skin/splitters.css" rel="stylesheet" type="text/css"/> + <link href="chrome://devtools/content/shared/components/splitter/GridElementResizer.css" rel="stylesheet" type="text/css"/> + <style> + * { + box-sizing: border-box; + } + + html { + --theme-splitter-color: red; + } + + main { + display: grid; + grid-template-columns: auto 1fr auto; + grid-template-rows: 20px 20px 20px; + grid-gap: 10px; + direction: rtl; + } + + .a, + .b, + .c, + .d { + border: 1px solid green; + } + + header { + grid-column: 1 / -1; + } + .a { + grid-column: 1 / 2; + grid-row: 2 / -1; + min-width: 100px; + } + .b { + grid-column: 2 / 3; + grid-row: 2 / -1; + } + + .c { + grid-column: 3 / 4; + grid-row: 2 / 3; + } + + .d { + grid-column: 3 / 4; + grid-row: 3 / 4; + min-width: 150px; + } + + .resizer-a { + grid-column: 1 / 2; + grid-row: 2 / -1; + } + + .resizer-d { + grid-column: 3 / 4; + grid-row: 2 / -1; + } + </style> + </head> + <body> + <main></main> + <pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +const FUDGE_FACTOR = .5; +function aboutEq(a, b) { + dumpn(`Checking ${a} ~= ${b}`); + return Math.abs(a - b) < FUDGE_FACTOR; +} + +window.onload = async function () { + try { + const React = browserRequire("devtools/client/shared/vendor/react"); + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const dom = require("devtools/client/shared/vendor/react-dom-factories"); + + const GridElementWidthResizer = React.createFactory(browserRequire("devtools/client/shared/components/splitter/GridElementWidthResizer")); + ok(GridElementWidthResizer, "Should get GridElementWidthResizer"); + + const resizedA = []; + const resizedD = []; + + ReactDOM.render([ + dom.header({}, "header"), + dom.aside({className: "a"}, "A"), + GridElementWidthResizer({ + getControlledElementNode: () => a, + enabled: true, + position: "end", + className: "resizer-a", + onResizeEnd: width => resizedA.push(width), + }), + dom.section({className: "b"}, "B"), + GridElementWidthResizer({ + getControlledElementNode: () => window.document.querySelector(".b"), + enabled: false, + position: "start", + className: "resizer-disabled", + }), + dom.aside({className: "c"}, "C"), + dom.aside({className: "d"}, "D"), + GridElementWidthResizer({ + getControlledElementNode: () => d, + enabled: true, + position: "start", + className: "resizer-d", + onResizeEnd: width => resizedD.push(width), + }), + ], window.document.querySelector("main")); + + // wait for a bit as we may not have everything setup yet. + await new Promise(res => setTimeout(res, 10)); + + const a = window.document.querySelector(".a"); + const d = window.document.querySelector(".d"); + + // Test that we properly rendered our two resizers, and not the disabled one. + const resizers = window.document.querySelectorAll(".grid-element-width-resizer"); + is(resizers.length, 2, "The 2 enabled resizers are rendered"); + + const [resizerA, resizerD] = resizers; + + ok(resizerA.classList.contains("resizer-a") + && resizerA.classList.contains("end"), "resizerA has expected classes"); + ok(resizerD.classList.contains("resizer-d") + && resizerD.classList.contains("start"), "resizerD has expected classes"); + + const aBoundingRect = a.getBoundingClientRect(); + const aOriginalWidth = aBoundingRect.width; + + info("Resize element A"); + await resize(resizerA, aBoundingRect.left - 20); + + is(resizedA.length, 1, "onResizeEnd was called once"); + is(resizedD.length, 0, "resizerD was not impacted"); + let aWidth = a.getBoundingClientRect().width; + is(resizedA[0], aWidth, "onResizeEnd gives the width of the controlled element"); + ok(aboutEq(aWidth, aOriginalWidth + 20), + "controlled element was resized to the expected size"); + + info("Resize element A below its min width"); + await resize(resizerA, [aBoundingRect.right - 10]); + aWidth = a.getBoundingClientRect().width; + ok(aboutEq(aWidth, 100), "controlled element was resized to its min width"); + + info("Resize element D below its min width"); + const dBoundingRect = d.getBoundingClientRect(); + const dOriginalWidth = dBoundingRect.width; + + await resize(resizerD, dBoundingRect.right - 100); + is(resizedD.length, 1, "onResizeEnd was called once for d"); + is(resizedA.length, 2, "onResizeEnd wasn't called for a"); + let dWidth = d.getBoundingClientRect().width; + is(resizedD[0], dWidth, "onResizeEnd gives the width of the controlled element"); + ok(aboutEq(dWidth, 150), "controlled element wasn't resized below its min-width"); + + info("Resize element D"); + await resize(resizerD, dBoundingRect.right + 15); + dWidth = d.getBoundingClientRect().width; + is(resizedD[1], dWidth, "onResizeEnd gives the width of the controlled element"); + ok(aboutEq(dWidth, dOriginalWidth + 15), "element was resized"); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } + + async function resize(resizer, clientX) { + info("Mouse down to start dragging"); + synthesizeMouseAtCenter(resizer, { button: 0, type: "mousedown" }, window); + await SimpleTest.promiseWaitForCondition( + () => document.firstElementChild.classList.contains("dragging"), + "dragging class is never set on the root element" + ); + ok(true, "The dragging class is set on the root element"); + + const event = new MouseEvent("mousemove", { clientX }); + resizer.dispatchEvent(event); + + info("Mouse up to stop resizing"); + synthesizeMouseAtCenter(document.body, { button: 0, type: "mouseup" }, window); + + await SimpleTest.promiseWaitForCondition( + () => !document.firstElementChild.classList.contains("dragging"), + "dragging class is never removed from the root element" + ); + ok(true, "The dragging class is removed from the root element"); + } +}; +</script> +</pre> + </body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_HSplitBox_01.html b/devtools/client/shared/components/test/chrome/test_HSplitBox_01.html new file mode 100644 index 0000000000..09da5dac6b --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_HSplitBox_01.html @@ -0,0 +1,140 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Basic tests for the HSplitBox component. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <link rel="stylesheet" href="chrome://devtools/skin/splitters.css" type="text/css"/> + <link rel="stylesheet" href="chrome://devtools/skin/components-h-split-box.css" type="text/css"/> + <style> + html { + --theme-splitter-color: black; + } + </style> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +const FUDGE_FACTOR = .1; +function aboutEq(a, b) { + dumpn(`Checking ${a} ~= ${b}`); + return Math.abs(a - b) < FUDGE_FACTOR; +} + +window.onload = async function () { + try { + const React = browserRequire("devtools/client/shared/vendor/react"); + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + + const HSplitBox = React.createFactory(browserRequire("devtools/client/shared/components/HSplitBox")); + ok(HSplitBox, "Should get HSplitBox"); + + const newSizes = []; + + async function renderBox(props) { + const boxProps = Object.assign({ + start: "hello!", + end: "world!", + startWidth: .5, + onResize(newSize) { + newSizes.push(newSize); + } + }, props); + const el = ReactDOM.render(HSplitBox(boxProps), window.document.body); + // wait until the element is rendered. + await SimpleTest.promiseWaitForCondition( + () => document.querySelector(".devtools-side-splitter") + ); + return el; + } + + await renderBox(); + + // Test that we properly rendered our two panes. + + let panes = document.querySelectorAll(".h-split-box-pane"); + is(panes.length, 2, "Should get two panes"); + is(panes[0].style.flexGrow, "0.5", "Each pane should have .5 width"); + is(panes[1].style.flexGrow, "0.5", "Each pane should have .5 width"); + is(panes[0].textContent.trim(), "hello!", "First pane should be hello"); + is(panes[1].textContent.trim(), "world!", "Second pane should be world"); + + // Now change the left width and assert that the changes are reflected. + + await renderBox({ startWidth: .25 }); + panes = document.querySelectorAll(".h-split-box-pane"); + is(panes.length, 2, "Should still have two panes"); + is(panes[0].style.flexGrow, "0.25", "First pane's width should be .25"); + is(panes[1].style.flexGrow, "0.75", "Second pane's width should be .75"); + + // Mouse moves without having grabbed the splitter should have no effect. + + const container = document.querySelector(".h-split-box"); + ok(container, "Should get our container .h-split-box"); + + const { left, top, width } = container.getBoundingClientRect(); + const middle = left + width / 2; + const oneQuarter = left + width / 4; + const threeQuarters = left + 3 * width / 4; + + synthesizeMouse(container, middle, top, { type: "mousemove" }, window); + is(newSizes.length, 0, "Mouse moves without dragging the splitter should have no effect"); + + // Send a mouse down on the splitter, and then move the mouse a couple + // times. Now we should get resizes. + + const splitter = document.querySelector(".devtools-side-splitter"); + ok(splitter, "Should get our splitter"); + + synthesizeMouseAtCenter(splitter, { button: 0, type: "mousedown" }, window); + + function mouseMove(clientX) { + const event = new MouseEvent("mousemove", { clientX }); + document.defaultView.top.dispatchEvent(event); + } + + mouseMove(middle); + is(newSizes.length, 1, "Should get 1 resize"); + ok(aboutEq(newSizes[0], .5), "New size should be ~.5"); + + mouseMove(left); + is(newSizes.length, 2, "Should get 2 resizes"); + ok(aboutEq(newSizes[1], 0), "New size should be ~0"); + + mouseMove(oneQuarter); + is(newSizes.length, 3, "Sould get 3 resizes"); + ok(aboutEq(newSizes[2], .25), "New size should be ~.25"); + + mouseMove(threeQuarters); + is(newSizes.length, 4, "Should get 4 resizes"); + ok(aboutEq(newSizes[3], .75), "New size should be ~.75"); + + synthesizeMouseAtCenter(splitter, { button: 0, type: "mouseup" }, window); + + // Now that we have let go of the splitter, mouse moves should not result in resizes. + + synthesizeMouse(container, middle, top, { type: "mousemove" }, window); + is(newSizes.length, 4, "Should still have 4 resizes"); + + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_accordion.html b/devtools/client/shared/components/test/chrome/test_accordion.html new file mode 100644 index 0000000000..60d179be6f --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_accordion.html @@ -0,0 +1,141 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that Accordion renders correctly. +--> +<head> + <meta charset="utf-8"> + <title>Accordion component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="resource://testing-common/sinon-7.2.7.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script src="accordion.snapshots.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +/* global sinon */ + +window.onload = async function() { + try { + const { button, div } = require("devtools/client/shared/vendor/react-dom-factories"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const { + Simulate, + renderIntoDocument, + findAllInRenderedTree, + } = browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const Accordion = + browserRequire("devtools/client/shared/components/Accordion"); + + const testItems = [ + { + header: "Test Accordion Item 1", + id: "accordion-item-1", + component: div({}), + opened: false, + onToggle: sinon.spy(), + }, + { + header: "Test Accordion Item 2", + id: "accordion-item-2", + component: div({}), + buttons: button({}), + opened: false, + onToggle: sinon.spy(), + }, + { + header: "Test Accordion Item 3", + id: "accordion-item-3", + component: div({}), + opened: true, + onToggle: sinon.spy(), + }, + ]; + + // Accordion basic render + const accordion = React.createElement(Accordion, { items: testItems }); + + matchSnapshot("Accordion basic render.", accordion); + + const tree = renderIntoDocument(accordion); + const headers = findAllInRenderedTree(tree, c => c.className === "accordion-header"); + + Simulate.click(headers[0]); + ok(testItems[0].onToggle.calledWith(true), "Handle toggling with click."); + ok(testItems[1].onToggle.notCalled, + "onToggle wasn't called on element we didn't click on."); + + isDeeply( + tree.state, + { + everOpened: { + "accordion-item-1": true, + "accordion-item-2": false, + "accordion-item-3": true, + }, + opened: { + "accordion-item-1": true, + "accordion-item-2": false, + "accordion-item-3": true, + }, + }, + "State updated correctly" + ); + + Simulate.keyDown(headers[0], { key: "Enter" }); + ok(testItems[0].onToggle.calledWith(false), "Handle toggling with Enter key."); + isDeeply( + tree.state, + { + everOpened: { + "accordion-item-1": true, + "accordion-item-2": false, + "accordion-item-3": true, + }, + opened: { + "accordion-item-1": false, + "accordion-item-2": false, + "accordion-item-3": true, + }, + }, + "State updated correctly" + ); + + Simulate.keyDown(headers[1], { key: " " }); + ok(testItems[1].onToggle.calledWith(true), "Handle toggling with Space key."); + isDeeply( + tree.state, + { + everOpened: { + "accordion-item-1": true, + "accordion-item-2": true, + "accordion-item-3": true, + }, + opened: { + "accordion-item-1": false, + "accordion-item-2": true, + "accordion-item-3": true, + }, + }, + "State updated correctly" + ); + + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_frame_01.html b/devtools/client/shared/components/test/chrome/test_frame_01.html new file mode 100644 index 0000000000..f763b21395 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_frame_01.html @@ -0,0 +1,361 @@ +<!-- 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/. --> +<!DOCTYPE html> +<html> + <!-- +Test the formatting of the file name, line and columns are correct in frame components, +with optional columns, unknown and non-URL sources. +--> + <head> + <meta charset="utf-8" /> + <title>Frame component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link + rel="stylesheet" + type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" + /> + </head> + <body> + <pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const Frame = React.createFactory(browserRequire("devtools/client/shared/components/Frame")); + ok(Frame, "Should get Frame"); + + // Check when there's a column + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + line: 55, + column: 10, + } + }, { + file: "mahscripts.js", + line: 55, + column: 10, + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js:55:10", + }); + + // Check when there's no column + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + line: 55, + } + }, { + file: "mahscripts.js", + line: 55, + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js:55", + }); + + // Check when column === 0 + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + line: 55, + column: 0, + } + }, { + file: "mahscripts.js", + line: 55, + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js:55", + }); + + // Check when there's an error in CSS (View source in Style Editor) + await checkFrameComponent({ + frame: { + source: "https://myfile.com/cafebabe.css", + line: 13, + }, + messageSource: "css", + }, { + file: "cafebabe.css", + line: 13, + shouldLink: true, + tooltip: "View source in Style Editor → https://myfile.com/cafebabe.css:13", + }); + + + // Check when there's no parseable URL source; + // should not link but should render line/columns + await checkFrameComponent({ + frame: { + source: "self-hosted", + line: 1, + } + }, { + file: "self-hosted", + line: "1", + shouldLink: false, + tooltip: "self-hosted:1", + }); + await checkFrameComponent({ + frame: { + source: "self-hosted", + line: 1, + column: 10, + } + }, { + file: "self-hosted", + line: "1", + column: "10", + shouldLink: false, + tooltip: "self-hosted:1:10", + }); + + // Check when there's no source; + // should not link but should render line/columns + await checkFrameComponent({ + frame: { + line: 1, + } + }, { + file: "(unknown)", + line: "1", + shouldLink: false, + tooltip: "(unknown):1", + }); + await checkFrameComponent({ + frame: { + line: 1, + column: 10, + } + }, { + file: "(unknown)", + line: "1", + column: "10", + shouldLink: false, + tooltip: "(unknown):1:10", + }); + + // Check when there's a column, but no line; + // no line/column info should render + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + column: 55, + } + }, { + file: "mahscripts.js", + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js", + }); + + // Check when line is 0; this should be an invalid + // line option, so don't render line/column + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + line: 0, + column: 55, + } + }, { + file: "mahscripts.js", + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js", + }); + + // Check that line and column can be strings + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + line: "10", + column: "55", + } + }, { + file: "mahscripts.js", + line: 10, + column: 55, + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js:10:55", + }); + + // Check that line and column can be strings, + // and that the `0` rendering rules apply when they are strings as well + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + line: "0", + column: "55", + } + }, { + file: "mahscripts.js", + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js", + }); + + // Check that the showFullSourceUrl option works correctly + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + line: 0, + }, + showFullSourceUrl: true + }, { + file: "https://myfile.com/mahscripts.js", + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js", + }); + + // Check that the showFunctionName option works correctly + await checkFrameComponent({ + frame: { + functionDisplayName: "myfun", + source: "https://myfile.com/mahscripts.js", + line: 0, + } + }, { + functionName: null, + file: "mahscripts.js", + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js", + }); + + await checkFrameComponent({ + frame: { + functionDisplayName: "myfun", + source: "https://myfile.com/mahscripts.js", + line: 0, + }, + showFunctionName: true + }, { + functionName: "myfun", + file: "mahscripts.js", + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js", + }); + + // Check that anonymous function name is not displayed unless explicitly enabled + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + line: 0, + }, + showFunctionName: true + }, { + functionName: null, + file: "mahscripts.js", + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js", + }); + + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + line: 0, + }, + showFunctionName: true, + showAnonymousFunctionName: true + }, { + functionName: "<anonymous>", + file: "mahscripts.js", + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js", + }); + + // Check if file is rendered with "/" for root documents when showEmptyPathAsHost is false + await checkFrameComponent({ + frame: { + source: "https://www.cnn.com/", + line: "1", + }, + showEmptyPathAsHost: false, + }, { + file: "/", + line: "1", + shouldLink: true, + tooltip: "View source in Debugger → https://www.cnn.com/:1", + }); + + // Check if file is rendered with hostname for root documents when showEmptyPathAsHost is true + await checkFrameComponent({ + frame: { + source: "https://www.cnn.com/", + line: "1", + }, + showEmptyPathAsHost: true, + }, { + file: "www.cnn.com", + line: "1", + shouldLink: true, + tooltip: "View source in Debugger → https://www.cnn.com/:1", + }); + + const resolvedLocation = { + sourceId: "whatever", + line: 23, + sourceUrl: "https://bugzilla.mozilla.org/original.js", + }; + const mockSourceMapURLService = { + subscribeByLocation (loc, callback) { + // Resolve immediately. + callback({ + url: resolvedLocation.sourceUrl, + line: resolvedLocation.line, + column: undefined, + }); + return () => {}; + }, + }; + await checkFrameComponent({ + frame: { + line: 97, + source: "https://bugzilla.mozilla.org/bundle.js", + }, + sourceMapURLService: mockSourceMapURLService, + }, { + file: "original.js", + line: resolvedLocation.line, + shouldLink: true, + tooltip: "View source in Debugger → https://bugzilla.mozilla.org/original.js:23", + source: "https://bugzilla.mozilla.org/original.js", + }); + + // Check when a message comes from a logPoint, + // a prefix should render before source + await checkFrameComponent({ + frame: { + source: "https://myfile.com/mahscripts.js", + line: 55, + column: 10, + options: { logPoint: true }, + } + }, { + file: "mahscripts.js", + line: 55, + column: 10, + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js:55:10", + }); + + function checkFrameComponent(input, expected) { + const props = Object.assign({ onClick: () => {} }, input); + const frame = ReactDOM.render(Frame(props), window.document.body); + const el = ReactDOM.findDOMNode(frame); + const { source } = input.frame; + checkFrameString(Object.assign({ el, source }, expected)); + ReactDOM.unmountComponentAtNode(window.document.body); + } + + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> + </body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_frame_02.html b/devtools/client/shared/components/test/chrome/test_frame_02.html new file mode 100644 index 0000000000..dd4bf9c2b7 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_frame_02.html @@ -0,0 +1,103 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that the frame component reacts to source-map pref changse. +--> +<head> + <meta charset="utf-8"> + <title>Frame component source-map test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const Frame = React.createFactory(browserRequire("devtools/client/shared/components/Frame")); + + const resolvedLocation = { + sourceId: "whatever", + line: 23, + sourceUrl: "https://bugzilla.mozilla.org/original.js", + }; + const mockSourceMapURLService = { + _update () { + this._callback(Services.prefs.getBoolPref(PREF) + ? { + url: resolvedLocation.sourceUrl, + line: resolvedLocation.line, + column: undefined, + } + : null); + }, + subscribeByLocation (loc, callback) { + this._callback = callback; + // Resolve immediately. + this._update(); + + return () => {}; + }, + }; + + const props = { + onClick: () => {}, + frame: { + line: 97, + source: "https://bugzilla.mozilla.org/bundle.js", + }, + sourceMapURLService: mockSourceMapURLService, + }; + + const PREF = "devtools.source-map.client-service.enabled"; + Services.prefs.setBoolPref(PREF, false); + + const frame = ReactDOM.render(Frame(props), window.document.body); + const el = ReactDOM.findDOMNode(frame); + const { source } = props.frame; + + const expectedOriginal = { + file: "original.js", + line: resolvedLocation.line, + shouldLink: true, + tooltip: "View source in Debugger → https://bugzilla.mozilla.org/original.js:23", + source: "https://bugzilla.mozilla.org/original.js", + }; + const expectedGenerated = { + file: "bundle.js", + line: 97, + shouldLink: true, + tooltip: "View source in Debugger → https://bugzilla.mozilla.org/bundle.js:97", + source: "https://bugzilla.mozilla.org/bundle.js", + }; + + checkFrameString(Object.assign({ el, source }, expectedGenerated)); + + Services.prefs.setBoolPref(PREF, true); + mockSourceMapURLService._update(); + checkFrameString(Object.assign({ el, source }, expectedOriginal)); + + Services.prefs.setBoolPref(PREF, false); + mockSourceMapURLService._update(); + checkFrameString(Object.assign({ el, source }, expectedGenerated)); + + Services.prefs.clearUserPref(PREF); + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_list.html b/devtools/client/shared/components/test/chrome/test_list.html new file mode 100644 index 0000000000..8be8907b5d --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_list.html @@ -0,0 +1,127 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that List renders correctly. +--> +<head> + <meta charset="utf-8"> + <title>List component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script src="list.snapshots.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = async function() { + try { + const { div } = require("devtools/client/shared/vendor/react-dom-factories"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const { + Simulate, + renderIntoDocument, + findRenderedDOMComponentWithClass, + scryRenderedDOMComponentsWithTag, + scryRenderedComponentsWithType, + } = browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const { List, ListItem } = + browserRequire("devtools/client/shared/components/List"); + + const testItems = [ + { + component: div({ className: "item-1" }, "Test List Item 1"), + className: "list-item-1", + key: "list-item-1", + }, + { + component: div({ className: "item-2" }, "Test List Item 2"), + className: "list-item-2", + key: "list-item-2", + }, + { + component: div({ className: "item-3" }, "Test List Item 3"), + className: "list-item-3", + key: "list-item-3", + }, + ]; + + const listReactEl = React.createElement(List, { + items: testItems, + labelledBy: "test-labelledby", + }); + + const list = renderIntoDocument(listReactEl); + const listEl = findRenderedDOMComponentWithClass(list, "list"); + const items = scryRenderedComponentsWithType(list, ListItem); + const itemEls = scryRenderedDOMComponentsWithTag(list, "li"); + + function testCurrent(index) { + is(list.state.current, index, "Correct current item."); + is(listEl.getAttribute("aria-activedescendant"), testItems[index].key, + "Correct active descendant."); + } + + is(items.length, 3, "Correct number of list item components in tree."); + is(itemEls.length, 3, "Correct number of list items is rendered."); + info("Testing initial tree properties."); + for (let index = 0; index < items.length; index++) { + const item = items[index]; + const itemEl = itemEls[index]; + const { active, current, item: itemProp } = item.props; + const content = itemEl.querySelector(".list-item-content"); + + is(active, false, "Correct active state."); + is(current, false, "Correct current state."); + is(itemProp, testItems[index], "Correct rendered item."); + is(item.contentRef.current, content, "Correct content ref."); + + is(itemEl.className, testItems[index].className, "Correct list item class."); + is(itemEl.id, testItems[index].key, "Correct list item it."); + is(content.getAttribute("role"), "presentation", "Correct content role."); + + is(content.innerHTML, + `<div class="item-${index + 1}">Test List Item ${index + 1}</div>`, + "Content rendered correctly."); + } + + is(list.state.current, null, "Current item is not set by default."); + is(list.state.active, null, "Active item is not set by default."); + is(list.listRef.current, listEl, "Correct list ref."); + + is(listEl.className, "list", "Correct list class."); + is(listEl.tabIndex, 0, "List is focusable."); + ok(!listEl.hasAttribute("aria-label"), "List has no label."); + is(listEl.getAttribute("aria-labelledby"), "test-labelledby", + "Correct list labelled by attribute."); + ok(!listEl.hasAttribute("aria-activedescendant"), + "No active descendant set by default."); + + Simulate.focus(listEl); + testCurrent(0); + + Simulate.click(itemEls[2]); + testCurrent(2); + + Simulate.blur(listEl); + testCurrent(2); + + Simulate.focus(listEl); + testCurrent(2); + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_list_keyboard.html b/devtools/client/shared/components/test/chrome/test_list_keyboard.html new file mode 100644 index 0000000000..7558404ed2 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_list_keyboard.html @@ -0,0 +1,283 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that List component has working keyboard interactions. +--> +<head> + <meta charset="utf-8"> + <title>List component keyboard test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = function() { + try { + const { a, button, div } = + require("devtools/client/shared/vendor/react-dom-factories"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const { + Simulate, + findRenderedDOMComponentWithClass, + findRenderedDOMComponentWithTag, + scryRenderedDOMComponentsWithTag, + } = browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const { List } = + browserRequire("devtools/client/shared/components/List"); + + const testItems = [ + { + component: div({}, "Test List Item 1"), + className: "list-item-1", + key: "list-item-1", + }, + { + component: div({}, + "Test List Item 2", + a({ href: "#" }, "Focusable 1"), + button({ }, "Focusable 2")), + className: "list-item-2", + key: "list-item-2", + }, + { + component: div({}, "Test List Item 3"), + className: "list-item-3", + key: "list-item-3", + }, + ]; + + const list = React.createElement(List, { + items: testItems, + labelledby: "test-labelledby", + }); + + const tree = ReactDOM.render(list, document.body); + const listEl = findRenderedDOMComponentWithClass(tree, "list"); + scryRenderedDOMComponentsWithTag(tree, "li"); + const defaultFocus = listEl.ownerDocument.body; + + function blurEl(el) { + // Simulate.blur does not seem to update the activeElement. + el.blur(); + } + + function focusEl(el) { + // Simulate.focus does not seem to update the activeElement. + el.focus(); + } + + const tests = [{ + name: "Test default List state. Keyboard focus is set to document body by default.", + state: { current: null, active: null }, + activeElement: defaultFocus, + }, { + name: "Current item must be set to the first list item on initial focus. " + + "Keyboard focus should be set on list's conatiner (<ul>).", + action: () => focusEl(listEl), + activeElement: listEl, + state: { current: 0 }, + }, { + name: "Current item should remain set even when the list is blured. " + + "Keyboard focus should be set back to document body.", + action: () => blurEl(listEl), + state: { current: 0 }, + activeElement: defaultFocus, + }, { + name: "Unset list's current state.", + action: () => tree.setState({ current: null }), + state: { current: null }, + }, { + name: "Current item must be re-set again to the first list item on initial " + + "focus. Keyboard focus should be set on list's conatiner (<ul>).", + action: () => focusEl(listEl), + activeElement: listEl, + state: { current: 0 }, + }, { + name: "Current item should be updated to next on ArrowDown.", + event: { type: "keyDown", el: listEl, options: { key: "ArrowDown" }}, + state: { current: 1 }, + }, { + name: "Current item should be updated to last on ArrowDown.", + event: { type: "keyDown", el: listEl, options: { key: "ArrowDown" }}, + state: { current: 2 }, + }, { + name: "Current item should remain on last on ArrowDown.", + event: { type: "keyDown", el: listEl, options: { key: "ArrowDown" }}, + state: { current: 2 }, + }, { + name: "Current item should be updated to previous on ArrowUp.", + event: { type: "keyDown", el: listEl, options: { key: "ArrowUp" }}, + state: { current: 1 }, + }, { + name: "Current item should be updated to first on ArrowUp.", + event: { type: "keyDown", el: listEl, options: { key: "ArrowUp" }}, + state: { current: 0 }, + }, { + name: "Current item should remain on first on ArrowUp.", + event: { type: "keyDown", el: listEl, options: { key: "ArrowUp" }}, + state: { current: 0 }, + }, { + name: "Current item should be updated to last on End.", + event: { type: "keyDown", el: listEl, options: { key: "End" }}, + state: { current: 2 }, + }, { + name: "Current item should be updated to first on Home.", + event: { type: "keyDown", el: listEl, options: { key: "Home" }}, + state: { current: 0 }, + }, { + name: "Current item should be set as active on Enter.", + event: { type: "keyDown", el: listEl, options: { key: "Enter" }}, + state: { current: 0, active: 0 }, + activeElement: listEl, + }, { + name: "Active item should be unset on Escape.", + event: { type: "keyDown", el: listEl, options: { key: "Escape" }}, + state: { current: 0, active: null }, + }, { + name: "Current item should be set as active on Space.", + event: { type: "keyDown", el: listEl, options: { key: " " }}, + state: { current: 0, active: 0 }, + activeElement: listEl, + }, { + name: "Current item should unset when focus leaves the list.", + action: () => blurEl(listEl), + state: { current: 0, active: null }, + activeElement: defaultFocus, + }, { + name: "Keyboard focus should be set on list's conatiner (<ul>) on focus.", + action: () => focusEl(listEl), + activeElement: listEl, + }, { + name: "Current item should be updated to next on ArrowDown.", + event: { type: "keyDown", el: listEl, options: { key: "ArrowDown" }}, + state: { current: 1, active: null }, + }, { + name: "Current item should be set as active on Enter. Keyboard focus should be " + + "set on the first focusable element inside the list item, if available.", + event: { type: "keyDown", el: listEl, options: { key: "Enter" }}, + state: { current: 1, active: 1 }, + get activeElement() { + // When list item becomes active/inactive, it is replaced with a newly rendered + // one. + return findRenderedDOMComponentWithTag(tree, "a"); + }, + }, { + name: "Keyboard focus should be set to next tabbable element inside the active " + + "list item on Tab.", + action() { + synthesizeKey("KEY_Tab"); + }, + state: { current: 1, active: 1 }, + get activeElement() { + // When list item becomes active/inactive, it is replaced with a newly rendered + // one. + return findRenderedDOMComponentWithTag(tree, "button"); + }, + }, { + name: "Keyboard focus should wrap inside the list item when focused on last " + + "tabbable element.", + action() { + synthesizeKey("KEY_Tab"); + }, + state: { current: 1, active: 1 }, + get activeElement() { + return findRenderedDOMComponentWithTag(tree, "a"); + }, + }, { + name: "Keyboard focus should wrap inside the list item when focused on first " + + "tabbable element.", + action() { + synthesizeKey("KEY_Tab", { shiftKey: true }); + }, + state: { current: 1, active: 1 }, + get activeElement() { + return findRenderedDOMComponentWithTag(tree, "button"); + }, + }, { + name: "Active item should be unset on Escape. Focus should move back to the " + + "list container.", + event: { type: "keyDown", el: listEl, options: { key: "Escape" }}, + state: { current: 1, active: null }, + activeElement: listEl, + }, { + name: "Current item should be set as active on Space. Keyboard focus should be " + + "set on the first focusable element inside the list item, if available.", + event: { type: "keyDown", el: listEl, options: { key: " " }}, + state: { current: 1, active: 1 }, + get activeElement() { + // When list item becomes active/inactive, it is replaced with a newly rendered + // one. + return findRenderedDOMComponentWithTag(tree, "a"); + }, + }, { + name: "Current item should remain set even when the list is blured. " + + "Keyboard focus should be set back to document body.", + action: () => listEl.ownerDocument.activeElement.blur(), + state: { current: 1, active: null, }, + activeElement: defaultFocus, + }, { + name: "Keyboard focus should be set on list's conatiner (<ul>) on focus.", + action: () => focusEl(listEl), + state: { current: 1, active: null }, + activeElement: listEl, + }, { + name: "Current item should be updated to previous on ArrowUp.", + event: { type: "keyDown", el: listEl, options: { key: "ArrowUp" }}, + state: { current: 0, active: null }, + }, { + name: "Current item should be set as active on Enter.", + event: { type: "keyDown", el: listEl, options: { key: "Enter" }}, + state: { current: 0, active: 0 }, + activeElement: listEl, + }, { + name: "Keyboard focus should move to another focusable element outside of the " + + "list when there's nothing to focus on inside the list item.", + action() { + synthesizeKey("KEY_Tab", { shiftKey: true }); + }, + state: { current: 0, active: null }, + activeElement: listEl.ownerDocument.documentElement, + }]; + + for (const test of tests) { + const { action, event, state, name } = test; + + is(listEl, findRenderedDOMComponentWithClass(tree, "list"), "Sanity check"); + + info(name); + if (event) { + const { type, options, el } = event; + Simulate[type](el, options); + } else if (action) { + action(); + } + + if (test.activeElement) { + is(listEl.ownerDocument.activeElement, test.activeElement, + "Focus is set correctly."); + } + + for (const key in state) { + is(tree.state[key], state[key], `${key} state is correct.`); + } + } + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_notification_box_01.html b/devtools/client/shared/components/test/chrome/test_notification_box_01.html new file mode 100644 index 0000000000..2921d607c3 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_notification_box_01.html @@ -0,0 +1,136 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test for Notification Box. The test is checking: +* Basic rendering +* Appending correct classname on wrapping +* Appending a notification +* Notification priority +* Closing notification +--> +<head> + <meta charset="utf-8"> + <title>Notification Box</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const { NotificationBox, PriorityLevels } = browserRequire("devtools/client/shared/components/NotificationBox"); + + const renderedBox = shallowRenderComponent(NotificationBox, {}); + is(renderedBox.type, "div", "NotificationBox is rendered as <div>"); + + info("Test rendering NotificationBox with default props"); + const boxElement = React.createElement(NotificationBox); + const notificationBox = TestUtils.renderIntoDocument(boxElement); + const notificationNode = ReactDOM.findDOMNode(notificationBox); + + ok(notificationNode.classList.contains("notificationbox"), + "NotificationBox has expected class"); + ok(notificationNode.classList.contains("border-bottom"), + "NotificationBox has expected class"); + is(notificationNode.textContent, "", + "Empty NotificationBox has no text content"); + + checkNumberOfNotifications(notificationBox, 0); + + // Append a notification + notificationBox.appendNotification( + "Info message", + "id1", + null, + PriorityLevels.PRIORITY_INFO_HIGH + ); + + is (notificationNode.textContent, "Info message", + "The box must display notification message"); + checkNumberOfNotifications(notificationBox, 1); + + // Append more important notification + notificationBox.appendNotification( + "Critical message", + "id2", + null, + PriorityLevels.PRIORITY_CRITICAL_BLOCK + ); + + checkNumberOfNotifications(notificationBox, 1); + + is (notificationNode.textContent, "Critical message", + "The box must display more important notification message"); + + // Append less important notification + notificationBox.appendNotification( + "Warning message", + "id3", + null, + PriorityLevels.PRIORITY_WARNING_HIGH + ); + + checkNumberOfNotifications(notificationBox, 1); + + is (notificationNode.textContent, "Critical message", + "The box must still display the more important notification"); + + ok(notificationBox.getCurrentNotification(), + "There must be current notification"); + + notificationBox.getNotificationWithValue("id1").close(); + checkNumberOfNotifications(notificationBox, 1); + + notificationBox.getNotificationWithValue("id2").close(); + checkNumberOfNotifications(notificationBox, 1); + + notificationBox.getNotificationWithValue("id3").close(); + checkNumberOfNotifications(notificationBox, 0); + + info(`Check "wrapping" prop works as expected`); + // Append wrapping classname to the dom element when passing wrapping prop + const boxElementWrapped = React.createElement(NotificationBox, {wrapping: true}); + const notificationBoxWrapped = TestUtils.renderIntoDocument(boxElementWrapped); + const wrappedNotificationNode = ReactDOM.findDOMNode(notificationBoxWrapped); + + ok(wrappedNotificationNode.classList.contains("wrapping"), + "Wrapped notificationBox has expected class"); + + info(`Check "displayBorderTop/displayBorderBottom" props work as expected`); + const element = React.createElement(NotificationBox, { + displayBorderTop: true, + displayBorderBottom: false, + }); + const renderedElement = TestUtils.renderIntoDocument(element); + const elementNode = ReactDOM.findDOMNode(renderedElement); + + ok(elementNode.classList.contains("border-top"), + "truthy displayBorderTop render a border-top className"); + ok(!elementNode.classList.contains("border-bottom"), + "falsy displayBorderBottom does not render a border-bottom className"); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; + +function checkNumberOfNotifications(notificationBox, expected) { + is(TestUtils.scryRenderedDOMComponentsWithClass( + notificationBox, "notification").length, expected, + "The notification box must have expected number of notifications"); +} +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_notification_box_02.html b/devtools/client/shared/components/test/chrome/test_notification_box_02.html new file mode 100644 index 0000000000..f74194d128 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_notification_box_02.html @@ -0,0 +1,73 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test for Notification Box. The test is checking: +* Using custom callback in a notification +--> +<head> + <meta charset="utf-8"> + <title>Notification Box</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const { NotificationBox, PriorityLevels } = browserRequire("devtools/client/shared/components/NotificationBox"); + + // Test rendering + const boxElement = React.createElement(NotificationBox); + const notificationBox = TestUtils.renderIntoDocument(boxElement); + const notificationNode = ReactDOM.findDOMNode(notificationBox); + + let callbackExecuted = false; + + // Append a notification. + notificationBox.appendNotification( + "Info message", + "id1", + null, + PriorityLevels.PRIORITY_INFO_LOW, + undefined, + (reason) => { + callbackExecuted = true; + is(reason, "removed", "The reason must be expected string"); + } + ); + + is(TestUtils.scryRenderedDOMComponentsWithClass( + notificationBox, "notification").length, 1, + "There must be one notification"); + + const closeButton = notificationNode.querySelector( + ".messageCloseButton"); + + // Click the close button to close the notification. + TestUtils.Simulate.click(closeButton); + + is(TestUtils.scryRenderedDOMComponentsWithClass( + notificationBox, "notification").length, 0, + "The notification box must be empty now"); + + ok(callbackExecuted, "Event callback must be executed."); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_notification_box_03.html b/devtools/client/shared/components/test/chrome/test_notification_box_03.html new file mode 100644 index 0000000000..816456edd3 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_notification_box_03.html @@ -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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test for Notification Box. The test is checking: +* Using custom buttons in a notification +--> +<head> + <meta charset="utf-8"> + <title>Notification Box</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const { NotificationBox, PriorityLevels } = browserRequire("devtools/client/shared/components/NotificationBox"); + + // Test rendering + const boxElement = React.createElement(NotificationBox); + const notificationBox = TestUtils.renderIntoDocument(boxElement); + const notificationNode = ReactDOM.findDOMNode(notificationBox); + + let buttonCallbackExecuted = false; + const buttons = [{ + label: "Button1", + callback: () => { + buttonCallbackExecuted = true; + + // Do not close the notification + return true; + }, + }, { + label: "Button2", + callback: () => { + // Close the notification (return value undefined) + }, + }]; + + // Append a notification with buttons. + notificationBox.appendNotification( + "Info message", + "id1", + null, + PriorityLevels.PRIORITY_INFO_LOW, + buttons + ); + + const buttonNodes = notificationNode.querySelectorAll( + ".notificationButton"); + + is(buttonNodes.length, 2, "There must be two buttons"); + + // Click the first button + TestUtils.Simulate.click(buttonNodes[0]); + ok(buttonCallbackExecuted, "Button callback must be executed."); + + is(TestUtils.scryRenderedDOMComponentsWithClass( + notificationBox, "notification").length, 1, + "There must be one notification"); + + // Click the second button (closing the notification) + TestUtils.Simulate.click(buttonNodes[1]); + + is(TestUtils.scryRenderedDOMComponentsWithClass( + notificationBox, "notification").length, 0, + "The notification box must be empty now"); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_notification_box_04.html b/devtools/client/shared/components/test/chrome/test_notification_box_04.html new file mode 100644 index 0000000000..07ad9af25c --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_notification_box_04.html @@ -0,0 +1,67 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test for Notification Box. The test is checking: +* Adding a mdnLink to a notification +--> +<head> + <meta charset="utf-8"> + <title>Notification Box</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const { NotificationBox, PriorityLevels } = browserRequire("devtools/client/shared/components/NotificationBox"); + + // Render notification + const boxElement = React.createElement(NotificationBox); + const notificationBox = TestUtils.renderIntoDocument(boxElement); + const notificationNode = ReactDOM.findDOMNode(notificationBox); + + const mdnLink = "https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors" + + const mdnLinkButton = {mdnUrl: mdnLink, label: "learn more about error" } + + // Append a notification with a learn-more link + notificationBox.appendNotification( + "Info message", + "id1", + null, + PriorityLevels.PRIORITY_INFO_LOW, + [mdnLinkButton], + (e) => false, + ); + + const linkNode = notificationNode.querySelector( + "a.learn-more-link"); + + ok(linkNode, "Link is present"); + + is(linkNode.title, "learn more about error", "link has correct title"); + ok(linkNode.classList.contains("devtools-button"), "link has correct class") + + + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_notification_box_05.html b/devtools/client/shared/components/test/chrome/test_notification_box_05.html new file mode 100644 index 0000000000..b3a4e96378 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_notification_box_05.html @@ -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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test for Notification Box. The test is checking: +* the close button is not present when displayCloseButton is false +--> +<head> + <meta charset="utf-8"> + <title>Notification Box</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const { NotificationBox, PriorityLevels } = browserRequire("devtools/client/shared/components/NotificationBox"); + + // Test rendering with close button disabled + const boxElement = React.createElement(NotificationBox, {displayCloseButton: false}); + const notificationBox = TestUtils.renderIntoDocument(boxElement); + const notificationNode = ReactDOM.findDOMNode(notificationBox); + + + + // Append a notification. + notificationBox.appendNotification( + "Info message", + "id1", + null, + PriorityLevels.PRIORITY_INFO_LOW, + [], + (e) => false, + ); + + // Ensure close button is not present + const linkNode = notificationNode.querySelector( + ".messageCloseButton"); + + ok(!linkNode, "Close button is not present"); + + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_searchbox-with-autocomplete.html b/devtools/client/shared/components/test/chrome/test_searchbox-with-autocomplete.html new file mode 100644 index 0000000000..110d5640c1 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_searchbox-with-autocomplete.html @@ -0,0 +1,301 @@ +<!-- 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/. --> +<!DOCTYPE html> +<html> +<!-- +Test the searchbox and autocomplete-popup components +--> +<head> + <meta charset="utf-8"> + <title>SearchBox component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<script src="head.js"></script> +<script> +"use strict"; +window.onload = async function () { + /** + * Takes a DOMNode with its children as list items, + * Typically UL > LI and each item's text value is + * compared with the reference item's value as a test + * + * @params {Node} - Node to be compared + * @reference {array} - Reference array for comparison. The selected index is + * highlighted as a single element array ie. ["[abc]", "ab", "abcPQR"], + * Here the element "abc" is highlighted + */ + function compareAutocompleteList(list, reference) { + const delimiter = " - "; + const observedList = [...list.children].map(el => { + return el.classList.contains("autocomplete-selected") + ? `[${el.textContent}]` + : el.textContent + }); + is(observedList.join(delimiter), reference.join(delimiter), + "Autocomplete items are rendered as expected"); + } + + function compareCursorPosition(initialElement) { + const initialPosition = initialElement.selectionStart; + return (element) => { + is(element.selectionStart, initialPosition, "Input cursor position is not changed"); + } + } + + const React = browserRequire("devtools/client/shared/vendor/react"); + const SearchBox = React.createFactory( + browserRequire("devtools/client/shared/components/SearchBox") + ); + const { component, $ } = await createComponentTest(SearchBox, { + type: "search", + autocompleteProvider: (filter) => { + const baseList = [ + "foo", + "BAR", + "baZ", + "abc", + "pqr", + "xyz", + "ABC", + "a1", + "a2", + "a3", + "a4", + "a5", + ]; + if (!filter) { + return []; + } + + const tokens = filter.split(/\s+/g); + const lastToken = tokens[tokens.length - 1]; + const previousTokens = tokens.slice(0, tokens.length - 1); + + if (!lastToken) { + return []; + } + + return baseList + .filter((item) => { + return item.toLowerCase().startsWith(lastToken.toLowerCase()) + && item.toLowerCase() !== lastToken.toLowerCase(); + }) + .sort() + .map(item => ({ + value: [...previousTokens, item].join(" "), + displayValue: item, + })); + }, + onChange: () => null, + }); + + async function testSearchBoxWithAutocomplete() { + ok(!$(".devtools-autocomplete-popup"), "Autocomplete list not visible"); + + $(".devtools-searchinput").focus(); + await forceRender(component); // Wait for state update + ok(!$(".devtools-autocomplete-popup"), "Autocomplete list not visible"); + + sendString("a"); + await forceRender(component); + + compareAutocompleteList($(".devtools-autocomplete-listbox"), [ + "[ABC]", + "a1", + "a2", + "a3", + "a4", + "a5", + "abc", + ]); + + // Blur event + $(".devtools-searchinput").blur(); + await forceRender(component); + ok(!component.state.focused, "focused state was properly set"); + ok(!$(".devtools-autocomplete-popup"), "Autocomplete list removed from DOM"); + } + + async function testKeyEventsWithAutocomplete() { + // Clear the initial input + $(".devtools-searchinput").focus(); + const cursorPositionIsNotChanged = compareCursorPosition($(".devtools-searchinput")); + + // ArrowDown + synthesizeKey("KEY_ArrowDown"); + await forceRender(component); + compareAutocompleteList($(".devtools-autocomplete-listbox"), [ + "ABC", + "[a1]", + "a2", + "a3", + "a4", + "a5", + "abc", + ]); + ok($(".devtools-autocomplete-listbox .autocomplete-item:nth-child(2)") + .className.includes("autocomplete-selected"), + "Selection class applied"); + + // A double ArrowUp should roll back to the bottom of the list + synthesizeKey("KEY_ArrowUp"); + synthesizeKey("KEY_ArrowUp"); + await forceRender(component); + compareAutocompleteList($(".devtools-autocomplete-listbox"), [ + "ABC", + "a1", + "a2", + "a3", + "a4", + "a5", + "[abc]", + ]); + cursorPositionIsNotChanged($(".devtools-searchinput")); + + // PageDown should take -5 places up + synthesizeKey("KEY_PageUp"); + await forceRender(component); + compareAutocompleteList($(".devtools-autocomplete-listbox"), [ + "ABC", + "[a1]", + "a2", + "a3", + "a4", + "a5", + "abc", + ]); + cursorPositionIsNotChanged($(".devtools-searchinput")); + + // PageDown should take +5 places down + synthesizeKey("KEY_PageDown"); + await forceRender(component); + compareAutocompleteList($(".devtools-autocomplete-listbox"), [ + "ABC", + "a1", + "a2", + "a3", + "a4", + "a5", + "[abc]", + ]); + cursorPositionIsNotChanged($(".devtools-searchinput")); + + // Home should take to the top of the list + synthesizeKey("KEY_Home"); + await forceRender(component); + compareAutocompleteList($(".devtools-autocomplete-listbox"), [ + "[ABC]", + "a1", + "a2", + "a3", + "a4", + "a5", + "abc", + ]); + cursorPositionIsNotChanged($(".devtools-searchinput")); + + // End should take to the bottom of the list + synthesizeKey("KEY_End"); + await forceRender(component); + compareAutocompleteList($(".devtools-autocomplete-listbox"), [ + "ABC", + "a1", + "a2", + "a3", + "a4", + "a5", + "[abc]", + ]); + cursorPositionIsNotChanged($(".devtools-searchinput")); + + // Key down in existing state should rollover to the top + synthesizeKey("KEY_ArrowDown"); + await forceRender(component); + // Tab should select the component and hide popup + synthesizeKey("KEY_Tab"); + await forceRender(component); + is(component.state.value, "ABC", "Tab hit selects the item"); + ok(!$(".devtools-autocomplete-popup"), "Tab hit hides the popup"); + + // Activate popup by removing a key + synthesizeKey("KEY_Backspace"); + await forceRender(component); + ok($(".devtools-autocomplete-popup"), "Popup is up"); + compareAutocompleteList($(".devtools-autocomplete-listbox"), [ + "[ABC]", + "abc" + ]); + + // Enter key selection + synthesizeKey("KEY_ArrowUp"); + await forceRender(component); + synthesizeKey("KEY_Enter"); + is(component.state.value, "abc", "Enter selection"); + ok(!$(".devtools-autocomplete-popup"), "Enter/Return hides the popup"); + + // Escape should remove the autocomplete component + synthesizeKey("KEY_Backspace"); + await forceRender(component); + synthesizeKey("KEY_Escape"); + await forceRender(component); + ok(!$(".devtools-autocomplete-popup"), + "Autocomplete list removed from DOM on Escape"); + } + + async function testMouseEventsWithAutocomplete() { + $(".devtools-searchinput").focus(); + await setState(component, { + value: "", + focused: true, + }); + await forceRender(component); + + // ArrowDown + synthesizeKey("KEY_ArrowDown"); + await forceRender(component); + synthesizeMouseAtCenter($(".devtools-searchinput"), {}, window); + await forceRender(component); + is(component.state.focused, true, "Component should now be focused"); + + sendString("pq"); + await forceRender(component); + synthesizeMouseAtCenter( + $(".devtools-autocomplete-listbox .autocomplete-item:nth-child(1)"), + {}, window + ); + await forceRender(component); + is(component.state.value, "pqr", "Mouse click selects the item."); + ok(!$(".devtools-autocomplete-popup"), "Mouse click on item hides the popup"); + } + + async function testTokenizedAutocomplete() { + // Test for string "pqr ab" which should show list of ABC, abc + sendString(" ab"); + await forceRender(component); + compareAutocompleteList($(".devtools-autocomplete-listbox"), [ + "[ABC]", + "abc" + ]); + + // Select the first element, value now should be "pqr ABC" + synthesizeMouseAtCenter( + $(".devtools-autocomplete-listbox .autocomplete-item:nth-child(1)"), + {}, window + ); + is(component.state.value, "pqr ABC", "Post Tokenization value selection"); + } + + add_task(async function () { + await testSearchBoxWithAutocomplete(); + await testKeyEventsWithAutocomplete(); + await testMouseEventsWithAutocomplete(); + await testTokenizedAutocomplete(); + }); +}; +</script> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_searchbox.html b/devtools/client/shared/components/test/chrome/test_searchbox.html new file mode 100644 index 0000000000..8e2f76c1b9 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_searchbox.html @@ -0,0 +1,74 @@ +<!-- 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/. --> +<!DOCTYPE html> +<html> +<!-- +Test the searchbox component +--> +<head> + <meta charset="utf-8"> + <title>SearchBox component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<script src="head.js"></script> +<script> +"use strict"; +window.onload = function () { + const React = browserRequire("devtools/client/shared/vendor/react"); + const SearchBox = React.createFactory( + browserRequire("devtools/client/shared/components/SearchBox") + ); + ok(SearchBox, "Got the SearchBox factory"); + + async function testSimpleSearchBox() { + // Test initial state + const { component, $ } = await createComponentTest(SearchBox, { + type: "search", + keyShortcut: "CmdOrCtrl+F", + placeholder: "crazy placeholder", + }); + + is(component.state.value, "", "Initial value is blank"); + ok(!component.state.focused, "Input isn't initially focused"); + ok($(".devtools-searchinput-clear").hidden, "Clear button hidden"); + is($(".devtools-searchinput").placeholder, "crazy placeholder", + "Placeholder is properly set"); + + synthesizeKey("f", { accelKey: true }); + await forceRender(component); // Wait for state update + ok(component.state.focused, "Shortcut key focused the input box"); + + $(".devtools-searchinput").blur(); + await forceRender(component); + ok(!component.state.focused, "`focused` state set to false after blur"); + + // Test changing value in state + await setState(component, { + value: "foo", + }); + + is(component.state.value, "foo", "value was properly set on state"); + is($(".devtools-searchinput").value, "foo", "value was properly set on element"); + + // Filling input should show clear button + ok(!$(".devtools-searchinput-clear").hidden, "Clear button shown"); + + // Clearing value should hide clear button + await setState(component, { + value: "", + }); + await forceRender(component); + ok($(".devtools-searchinput-clear").hidden, "Clear button was hidden"); + } + + add_task(async function () { + await testSimpleSearchBox(); + }); +}; +</script> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_sidebar_toggle.html b/devtools/client/shared/components/test/chrome/test_sidebar_toggle.html new file mode 100644 index 0000000000..0a31037a84 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_sidebar_toggle.html @@ -0,0 +1,59 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test sidebar toggle button +--> +<head> + <meta charset="utf-8"> + <title>Sidebar toggle button test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + const SidebarToggle = browserRequire("devtools/client/shared/components/SidebarToggle.js"); + + try { + await test(); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } + + function test() { + const output1 = shallowRenderComponent(SidebarToggle, { + collapsed: false, + collapsePaneTitle: "Expand", + expandPaneTitle: "Collapse" + }); + + is(output1.type, "button", "Output is a button element"); + is(output1.props.title, "Expand", "Proper title is set"); + is(output1.props.className.indexOf("pane-collapsed"), -1, + "Proper class name is set"); + + const output2 = shallowRenderComponent(SidebarToggle, { + collapsed: true, + collapsePaneTitle: "Expand", + expandPaneTitle: "Collapse" + }); + + is(output2.props.title, "Collapse", "Proper title is set"); + ok(output2.props.className.includes("pane-collapsed"), + "Proper class name is set"); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_smart-trace-grouping.html b/devtools/client/shared/components/test/chrome/test_smart-trace-grouping.html new file mode 100644 index 0000000000..174d0f87b4 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_smart-trace-grouping.html @@ -0,0 +1,141 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test the rendering of a stack trace +--> +<head> + <meta charset="utf-8"> + <title>StackTrace component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<script src="head.js"></script> +<script> +"use strict"; + +window.onload = function() { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const SmartTrace = React.createFactory( + browserRequire("devtools/client/shared/components/SmartTrace")); + ok(SmartTrace, "Got the SmartTrace factory"); + + add_task(async function() { + const REACT_FRAMES_COUNT = 10; + + const stacktrace = [ + { + filename: "https://myfile.com/mahscripts.js", + lineNumber: 55, + columnNumber: 10, + functionName: null, + }, + // Simulated Redux frame + { + functionName: "rootReducer", + filename: "https://myfile.com/loader.js -> https://myfile.com/redux.js", + lineNumber: 2, + }, + { + functionName: "loadFunc", + filename: "https://myfile.com/loader.js -> https://myfile.com/loadee.js", + lineNumber: 10, + }, + // Simulated react frames + ...(Array.from({length: REACT_FRAMES_COUNT}, (_, i) => ({ + functionName: "internalReact" + (REACT_FRAMES_COUNT - i), + filename: "https://myfile.com/loader.js -> https://myfile.com/react.js", + lineNumber: Number(i.toString().repeat(2)), + }))), + { + filename: "https://myfile.com/mahscripts.js", + lineNumber: 10, + columnNumber: 3, + functionName: "onClick", + }, + ]; + + const props = { + stacktrace, + onViewSourceInDebugger: () => {}, + }; + + const trace = ReactDOM.render(SmartTrace(props), window.document.body); + await forceRender(trace); + + const traceEl = ReactDOM.findDOMNode(trace); + ok(traceEl, "Rendered SmartTrace has an element"); + + isDeeply(getStacktraceText(traceEl), [ + `<anonymous> https://myfile.com/mahscripts.js:55`, + `rootReducer Redux`, + `loadFunc https://myfile.com/loadee.js:10`, + `▶︎ React 10`, + `onClick https://myfile.com/mahscripts.js:10`, + ], "React frames are grouped - Redux frame is not"); + + info("Expand React group"); + let onReactGroupExpanded = waitFor(() => + traceEl.querySelector(".frames-group.expanded")); + traceEl.querySelector(".group").click(); + await onReactGroupExpanded; + + isDeeply(getStacktraceText(traceEl), [ + `<anonymous> https://myfile.com/mahscripts.js:55`, + `rootReducer Redux`, + `loadFunc https://myfile.com/loadee.js:10`, + `▼ React 10`, + `| internalReact10`, + `| internalReact9`, + `| internalReact8`, + `| internalReact7`, + `| internalReact6`, + `| internalReact5`, + `| internalReact4`, + `| internalReact3`, + `| internalReact2`, + `| internalReact1`, + `onClick https://myfile.com/mahscripts.js:10`, + ], "React frames can be expanded"); + + info("Collapse React group"); + onReactGroupExpanded = waitFor(() => + !traceEl.querySelector(".frames-group.expanded")); + traceEl.querySelector(".group").click(); + await onReactGroupExpanded; + + isDeeply(getStacktraceText(traceEl), [ + `<anonymous> https://myfile.com/mahscripts.js:55`, + `rootReducer Redux`, + `loadFunc https://myfile.com/loadee.js:10`, + `▶︎ React 10`, + `onClick https://myfile.com/mahscripts.js:10`, + ], "React frames can be collapsed"); + }); + + function getStacktraceText(traceElement) { + return Array.from(traceElement.querySelectorAll(".frame, .frames-group")).map(el => { + // If it's a group, we want to append an arrow representing the group state + if (el.classList.contains("frames-group")) { + const arrow = el.classList.contains("expanded") ? "▼" : "▶︎"; + const content = el.querySelector(".group").textContent.trim(); + return `${arrow} ${content}`; + } + + const title = el.querySelector(".title"); + if (el.closest(".frames-group")) { + return `| ${title.textContent}`; + } + + const location = el.querySelector(".location"); + return `${title.textContent} ${location.textContent}`; + }); + } +}; +</script> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_smart-trace-source-maps.html b/devtools/client/shared/components/test/chrome/test_smart-trace-source-maps.html new file mode 100644 index 0000000000..1beade0c0c --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_smart-trace-source-maps.html @@ -0,0 +1,290 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test the rendering of a stack trace +--> +<head> + <meta charset="utf-8"> + <title>StackTrace component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<script src="head.js"></script> +<script> +"use strict"; + +window.onload = function() { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const SmartTrace = React.createFactory( + browserRequire("devtools/client/shared/components/SmartTrace")); + ok(SmartTrace, "Got the SmartTrace factory"); + + add_task(async function testHappyPath() { + const stacktrace = [ + { + filename: "https://myfile.com/bundle.js", + lineNumber: 1, + columnNumber: 10, + }, + { + functionName: "loadFunc", + filename: "https://myfile.com/bundle.js", + lineNumber: 2, + }, + ]; + + let onReadyCount = 0; + const props = { + stacktrace, + initialRenderDelay: 2000, + onViewSourceInDebugger: () => {}, + onReady: () => { + onReadyCount++; + }, + // A mock source map service. + sourceMapURLService: { + subscribeByLocation ({ line, column }, callback) { + const newLine = Number(line.toString().repeat(2)); + // Resolve immediately. + callback({ + url: "https://bugzilla.mozilla.org/original.js", + line: newLine, + column, + }); + return () => {}; + }, + }, + }; + + const trace = ReactDOM.render(SmartTrace(props), + window.document.body.querySelector("#s1")); + await forceRender(trace); + + const traceEl = ReactDOM.findDOMNode(trace); + ok(traceEl, "Rendered SmartTrace has an element"); + + const frameEls = Array.from(traceEl.querySelectorAll(".frame")); + ok(frameEls, "Rendered SmartTrace has frames"); + is(frameEls.length, 2, "SmartTrace has 2 frames"); + + checkSmartFrameString({ + el: frameEls[0], + functionName: "<anonymous>", + location: "original.js:11", + tooltip: "View source in Debugger → https://bugzilla.mozilla.org/original.js:11", + }); + + checkSmartFrameString({ + el: frameEls[1], + functionName: "loadFunc", + location: "original.js:22", + tooltip: "View source in Debugger → https://bugzilla.mozilla.org/original.js:22", + }); + + is(onReadyCount, 1, "onReady was called once"); + }); + + add_task(async function testSlowSourcemapService() { + const stacktrace = [ + { + filename: "https://myfile.com/bundle.js", + functionName: "last", + lineNumber: 1, + columnNumber: 10, + }, + { + filename: "https://myfile.com/bundle.js", + functionName: "first", + lineNumber: 2, + columnNumber: 10, + }, + ]; + + const sourcemapTimeout = 2000; + const initialRenderDelay = 300; + let onReadyCount = 0; + + const props = { + stacktrace, + initialRenderDelay, + onViewSourceInDebugger: () => {}, + onReady: () => { + onReadyCount++; + }, + // A mock source map service. + sourceMapURLService: { + subscribeByLocation ({ line, column }, callback) { + // Resolve after a while. + setTimeout(() => { + const newLine = Number(line.toString().repeat(2)); + callback({ + url: "https://myfile.com/react.js", + line: newLine, + column, + }); + }, sourcemapTimeout) + + return () => {}; + }, + }, + }; + + const trace = ReactDOM.render(SmartTrace(props), + window.document.body.querySelector("#s2")); + + let traceEl = ReactDOM.findDOMNode(trace); + ok(!traceEl, "Nothing was rendered at first"); + is(onReadyCount, 0, "onReady isn't called if SmartTrace isn't rendered"); + + info("Wait for the initial delay to be over"); + await new Promise(res => setTimeout(res, initialRenderDelay)); + + traceEl = ReactDOM.findDOMNode(trace); + ok(traceEl, "The trace was rendered"); + + let frameEls = Array.from(traceEl.querySelectorAll(".frame")); + ok(frameEls, "Rendered SmartTrace has frames"); + is(frameEls.length, 2, "SmartTrace has 2 frames"); + + info("Check that the original frames are displayed after the initial delay"); + checkSmartFrameString({ + el: frameEls[0], + functionName: "last", + location: "https://myfile.com/bundle.js:1", + tooltip: "View source in Debugger → https://myfile.com/bundle.js:1", + }); + + checkSmartFrameString({ + el: frameEls[1], + functionName: "first", + location: "https://myfile.com/bundle.js:2", + tooltip: "View source in Debugger → https://myfile.com/bundle.js:2", + }); + + is(onReadyCount, 1, "onReady was called once"); + + info("Check the the sourcemapped version is rendered after the sourcemapTimeout"); + await waitFor(() => !!traceEl.querySelector(".group")); + + frameEls = Array.from(traceEl.querySelectorAll(".frame")); + is(frameEls.length, 0, "SmartTrace has no frame"); + + const groups = Array.from(traceEl.querySelectorAll(".group")); + is(groups.length, 1, "SmartTrace has a group"); + is(groups[0].textContent.trim(), "React 2", "A collapsed React group is displayed"); + + is(onReadyCount, 1, "onReady was only called once"); + }); + + add_task(async function testFlakySourcemapService() { + const stacktrace = [ + { + filename: "https://myfile.com/bundle.js", + functionName: "last", + lineNumber: 1, + columnNumber: 10, + }, + { + filename: "https://myfile.com/bundle.js", + functionName: "pending", + lineNumber: 2, + columnNumber: 10, + }, + { + filename: "https://myfile.com/bundle.js", + functionName: "first", + lineNumber: 3, + columnNumber: 10, + }, + ]; + + const initialRenderDelay = 300; + const onSourceMapResultDebounceDelay = 50; + let onReadyCount = 0; + + const props = { + stacktrace, + initialRenderDelay, + onSourceMapResultDebounceDelay, + onViewSourceInDebugger: () => {}, + onReady: () => { + onReadyCount++; + }, + // A mock source map service. + sourceMapURLService: { + subscribeByLocation ({ line, column }, callback) { + // Don't call the callback for the second frame to simulate a flaky sourcemap + // service request. + if (line === 2) { + return () => {}; + } + + const newLine = Number(line.toString().repeat(2)); + callback({ + url: `https://myfile.com/file-${line}.js`, + line: newLine, + column, + }); + return () => {}; + }, + }, + }; + + const trace = ReactDOM.render(SmartTrace(props), + window.document.body.querySelector("#s3")); + + let traceEl = ReactDOM.findDOMNode(trace); + ok(!traceEl, "Nothing was rendered at first"); + is(onReadyCount, 0, "onReady isn't called if SmartTrace isn't rendered"); + + info("Wait for the initial delay + debounce to be over"); + await waitFor(() => { + const el = ReactDOM.findDOMNode(trace) + return el && el.textContent.includes("file-1.js"); + }); + + traceEl = ReactDOM.findDOMNode(trace); + ok(traceEl, "The trace was rendered"); + + const frameEls = Array.from(traceEl.querySelectorAll(".frame")); + ok(frameEls, "Rendered SmartTrace has frames"); + is(frameEls.length, 3, "SmartTrace has 3 frames"); + + info("Check that the original frames are displayed even if there's no sourcemap " + + "response for some frames"); + checkSmartFrameString({ + el: frameEls[0], + functionName: "last", + location: "file-1.js:11", + tooltip: "View source in Debugger → https://myfile.com/file-1.js:11", + }); + + checkSmartFrameString({ + el: frameEls[1], + functionName: "pending", + location: "bundle.js:2", + tooltip: "View source in Debugger → https://myfile.com/bundle.js:2", + }); + + checkSmartFrameString({ + el: frameEls[2], + functionName: "first", + location: "file-3.js:33", + tooltip: "View source in Debugger → https://myfile.com/file-3.js:33", + }); + + is(onReadyCount, 1, "onReady was only called once"); + }); + +}; +</script> +<section id=s1></section> +<section id=s2></section> +<section id=s3></section> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_smart-trace.html b/devtools/client/shared/components/test/chrome/test_smart-trace.html new file mode 100644 index 0000000000..eedb72cc13 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_smart-trace.html @@ -0,0 +1,172 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test the rendering of a stack trace +--> +<head> + <meta charset="utf-8"> + <title>StackTrace component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> + <section id=s1></section> + <section id=s2></section> + <section id=s3></section> + <section id=s4></section> +<script src="head.js"></script> +<script> +"use strict"; + +window.onload = function() { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const SmartTrace = React.createFactory( + browserRequire("devtools/client/shared/components/SmartTrace")); + ok(SmartTrace, "Got the SmartTrace factory"); + + const stacktrace = [ + { + filename: "https://myfile.com/mahscripts.js", + lineNumber: 55, + columnNumber: 10, + functionName: null, + }, + { + functionName: "loadFunc", + filename: "https://myfile.com/loader.js -> https://myfile.com/loadee.js", + lineNumber: 10, + }, + ]; + + add_task(async function testBasic() { + info("Check basic rendering"); + let onReadyCount = 0; + const props = { + stacktrace, + onViewSourceInDebugger: () => {}, + onReady: () => { + onReadyCount++; + }, + }; + await renderSmartTraceAndAssertContent( + window.document.body.querySelector("#s1"), + props + ); + is(onReadyCount, 1, "onReady was called once"); + }); + + add_task(async function testZeroDelay() { + info("Check rendering with source map service and 0 initial delay"); + let onReadyCount = 0; + const props = { + stacktrace, + onViewSourceInDebugger: () => {}, + onReady: () => { + onReadyCount++; + }, + initialRenderDelay: 0, + sourceMapURLService: { + subscribeByLocation: () => {} + }, + }; + await renderSmartTraceAndAssertContent( + window.document.body.querySelector("#s2"), + props + ); + is(onReadyCount, 1, "onReady was called once"); + }); + + add_task(async function testNullDelay() { + info("Check rendering with source map service and null initial delay"); + let onReadyCount = 0; + const props = { + stacktrace, + onViewSourceInDebugger: () => {}, + onReady: () => { + onReadyCount++; + }, + initialRenderDelay: 0, + sourceMapURLService: { + subscribeByLocation: () => {} + }, + }; + await renderSmartTraceAndAssertContent( + window.document.body.querySelector("#s3"), + props + ); + is(onReadyCount, 1, "onReady was called once"); + }); + + add_task(async function testDelay() { + info("Check rendering with source map service and initial delay"); + let onReadyCount = 0; + const props = { + stacktrace, + onViewSourceInDebugger: () => {}, + onReady: () => { + onReadyCount++; + }, + initialRenderDelay: 500, + sourceMapURLService: { + subscribeByLocation: () => {} + }, + }; + const el = window.document.body.querySelector("#s4"); + await renderSmartTraceAndAssertContent( + el, + props, + false + ); + is(onReadyCount, 0, "onReady wasn't called at first"); + info(`Wait for ${props.initialRenderDelay}ms so the stacktrace should be rendered`) + await new Promise(res => setTimeout(res, props.initialRenderDelay)) + is(onReadyCount, 1, "onReady was called after waiting for the initial delay"); + assertRenderedElementContent(el); + }); + + async function renderSmartTraceAndAssertContent(el, props, shouldBeRendered = true) { + let trace; + await new Promise(resolve => { + trace = ReactDOM.render(SmartTrace(props), el, resolve); + }); + + const traceEl = ReactDOM.findDOMNode(trace); + + if (!shouldBeRendered) { + ok(!traceEl, "SmartTrace wasn't rendered initially"); + return; + } + + ok(traceEl, "Rendered SmartTrace has an element"); + assertRenderedElementContent(traceEl); + } + + function assertRenderedElementContent(el) { + const frameEls = Array.from(el.querySelectorAll(".frame")); + ok(frameEls, "Rendered SmartTrace has frames"); + is(frameEls.length, 2, "SmartTrace has 2 frames"); + + checkSmartFrameString({ + el: frameEls[0], + functionName: "<anonymous>", + location: "https://myfile.com/mahscripts.js:55", + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js:55", + }); + + // Check the third frame, the source should be parsed into a valid source URL + checkSmartFrameString({ + el: frameEls[1], + functionName: "loadFunc", + location: "https://myfile.com/loadee.js:10", + tooltip: "View source in Debugger → https://myfile.com/loadee.js:10", + }); + } + +}; +</script> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_stack-trace-source-maps.html b/devtools/client/shared/components/test/chrome/test_stack-trace-source-maps.html new file mode 100644 index 0000000000..7535d4d2df --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_stack-trace-source-maps.html @@ -0,0 +1,98 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test the rendering of a stack trace with source maps +--> +<head> + <meta charset="utf-8"> + <title>StackTrace component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<script src="head.js"></script> +<script> +"use strict"; + +window.onload = function () { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const StackTrace = React.createFactory( + browserRequire("devtools/client/shared/components/StackTrace") + ); + ok(StackTrace, "Got the StackTrace factory"); + + add_task(async function () { + const stacktrace = [ + { + filename: "https://bugzilla.mozilla.org/bundle.js", + lineNumber: 99, + columnNumber: 10 + }, + { + functionName: "loadFunc", + filename: "https://bugzilla.mozilla.org/bundle.js", + lineNumber: 108, + } + ]; + + const props = { + stacktrace, + onViewSourceInDebugger: () => {}, + // A mock source map service. + sourceMapURLService: { + subscribeByLocation ({ line, column }, callback) { + const newLine = line === 99 ? 1 : 7; + // Resolve immediately. + callback({ + url: "https://bugzilla.mozilla.org/original.js", + line: newLine, + column, + }); + + return () => {} + }, + }, + }; + + const trace = ReactDOM.render(StackTrace(props), window.document.body); + await forceRender(trace); + + const traceEl = ReactDOM.findDOMNode(trace); + ok(traceEl, "Rendered StackTrace has an element"); + + // Get the child nodes and filter out the text-only whitespace ones + const frameEls = Array.from(traceEl.childNodes) + .filter(n => n.className && n.className.includes("frame")); + ok(frameEls, "Rendered StackTrace has frames"); + is(frameEls.length, 2, "StackTrace has 2 frames"); + + checkFrameString({ + el: frameEls[0], + functionName: "<anonymous>", + source: "https://bugzilla.mozilla.org/original.js", + file: "original.js", + line: 1, + column: 10, + shouldLink: true, + tooltip: "View source in Debugger → https://bugzilla.mozilla.org/original.js:1:10", + }); + + checkFrameString({ + el: frameEls[1], + functionName: "loadFunc", + source: "https://bugzilla.mozilla.org/original.js", + file: "original.js", + line: 7, + column: null, + shouldLink: true, + tooltip: "View source in Debugger → https://bugzilla.mozilla.org/original.js:7", + }); + }); +}; +</script> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_stack-trace.html b/devtools/client/shared/components/test/chrome/test_stack-trace.html new file mode 100644 index 0000000000..56d9288f06 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_stack-trace.html @@ -0,0 +1,100 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test the rendering of a stack trace +--> +<head> + <meta charset="utf-8"> + <title>StackTrace component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<script src="head.js"></script> +<script> +"use strict"; + +window.onload = function() { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const StackTrace = React.createFactory( + browserRequire("devtools/client/shared/components/StackTrace") + ); + ok(StackTrace, "Got the StackTrace factory"); + + add_task(async function() { + const stacktrace = [ + { + filename: "https://myfile.com/mahscripts.js", + lineNumber: 55, + columnNumber: 10, + }, + { + asyncCause: "because", + functionName: "loadFunc", + filename: "https://myfile.com/loadee.js", + lineNumber: 10, + }, + ]; + + const props = { + stacktrace, + onViewSourceInDebugger: () => {}, + }; + + const trace = ReactDOM.render(StackTrace(props), window.document.body); + await forceRender(trace); + + const traceEl = ReactDOM.findDOMNode(trace); + ok(traceEl, "Rendered StackTrace has an element"); + + // Get the child nodes and filter out the text-only whitespace ones + const frameEls = Array.from(traceEl.childNodes) + .filter(n => n.className && n.className.includes("frame")); + ok(frameEls, "Rendered StackTrace has frames"); + is(frameEls.length, 3, "StackTrace has 3 frames"); + + // Check the top frame, function name should be anonymous + checkFrameString({ + el: frameEls[0], + functionName: "<anonymous>", + source: "https://myfile.com/mahscripts.js", + file: "https://myfile.com/mahscripts.js", + line: 55, + column: 10, + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/mahscripts.js:55:10", + }); + + // Check the async cause node + is(frameEls[1].className, "frame-link-async-cause", + "Async cause has the right class"); + is(frameEls[1].textContent, "(Async: because)", "Async cause has the right label"); + + // Check the third frame, the source should be parsed into a valid source URL + checkFrameString({ + el: frameEls[2], + functionName: "loadFunc", + source: "https://myfile.com/loadee.js", + file: "https://myfile.com/loadee.js", + line: 10, + column: null, + shouldLink: true, + tooltip: "View source in Debugger → https://myfile.com/loadee.js:10", + }); + + // Check the tabs and newlines in the stack trace textContent + const traceText = traceEl.textContent; + const traceLines = traceText.split("\n"); + ok(!!traceLines.length, "There are newlines in the stack trace text"); + is(traceLines.pop(), "", "There is a newline at the end of the stack trace text"); + is(traceLines.length, 3, "The stack trace text has 3 lines"); + ok(traceLines.every(l => l[0] == "\t"), "Every stack trace line starts with tab"); + }); +}; +</script> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tabs_accessibility.html b/devtools/client/shared/components/test/chrome/test_tabs_accessibility.html new file mode 100644 index 0000000000..4d0ea6ef96 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tabs_accessibility.html @@ -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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test tabs accessibility. +--> +<head> + <meta charset="utf-8"> + <title>Tabs component accessibility test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const InspectorTabPanel = createFactory(browserRequire("devtools/client/inspector/components/InspectorTabPanel")); + const Tabbar = + createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar")); + const tabbar = Tabbar(); + const tabbarReact = ReactDOM.render(tabbar, window.document.body); + const tabbarEl = ReactDOM.findDOMNode(tabbarReact); + + // Setup for InspectorTabPanel + const tabpanels = document.createElement("div"); + tabpanels.id = "tabpanels"; + document.body.appendChild(tabpanels); + + await addTabWithPanel(0); + await addTabWithPanel(1); + + const tabAnchors = tabbarEl.querySelectorAll("li.tabs-menu-item a"); + + is(tabAnchors[0].parentElement.getAttribute("role"), "presentation", "li role is set correctly"); + is(tabAnchors[0].getAttribute("role"), "tab", "Anchor role is set correctly"); + is(tabAnchors[0].getAttribute("aria-selected"), "true", "Anchor aria-selected is set correctly by default"); + is(tabAnchors[0].getAttribute("aria-controls"), "sidebar-0-panel", "Anchor aria-controls is set correctly"); + is(tabAnchors[1].parentElement.getAttribute("role"), "presentation", "li role is set correctly"); + is(tabAnchors[1].getAttribute("role"), "tab", "Anchor role is set correctly"); + is(tabAnchors[1].getAttribute("aria-selected"), "false", "Anchor aria-selected is set correctly by default"); + is(tabAnchors[1].getAttribute("aria-controls"), "sidebar-1-panel", "Anchor aria-controls is set correctly"); + + await setState(tabbarReact, Object.assign({}, tabbarReact.state, { + activeTab: 1 + })); + + is(tabAnchors[0].getAttribute("aria-selected"), "false", "Anchor aria-selected is reset correctly"); + is(tabAnchors[1].getAttribute("aria-selected"), "true", "Anchor aria-selected is reset correctly"); + + function addTabWithPanel(tabId) { + // Setup for InspectorTabPanel + const panel = document.createElement("div"); + panel.id = `sidebar-${tabId}`; + document.body.appendChild(panel); + + return setState(tabbarReact, Object.assign({}, tabbarReact.state, { + tabs: tabbarReact.state.tabs.concat({ + id: `sidebar-${tabId}`, + title: `tab-${tabId}`, + panel: InspectorTabPanel + }), + })); + } + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tabs_menu.html b/devtools/client/shared/components/test/chrome/test_tabs_menu.html new file mode 100644 index 0000000000..cc4638e05a --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tabs_menu.html @@ -0,0 +1,84 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html class="theme-light"> +<!-- +Test all-tabs menu. +--> +<head> + <meta charset="utf-8"> + <title>Tabs component All-tabs menu test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" type="text/css" href="chrome://devtools/skin/variables.css"> + <link rel="stylesheet" type="text/css" href="chrome://devtools/skin/common.css"> + <link rel="stylesheet" type="text/css" href="chrome://devtools/content/shared/components/tabs/Tabs.css"> + <link rel="stylesheet" type="text/css" href="chrome://devtools/content/inspector/components/InspectorTabPanel.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { Component, createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const dom = require("devtools/client/shared/vendor/react-dom-factories"); + const Tabbar = createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar")); + + // Create container for the TabBar. Set smaller width + // to ensure that tabs won't fit and the all-tabs menu + // needs to appear. + const tabBarBox = document.createElement("div"); + tabBarBox.style.width = "200px"; + tabBarBox.style.height = "200px"; + tabBarBox.style.border = "1px solid lightgray"; + document.body.appendChild(tabBarBox); + + // Render the tab-bar. + const tabbar = Tabbar({ + showAllTabsMenu: true, + }); + + const tabbarReact = ReactDOM.render(tabbar, tabBarBox); + + class TabPanelClass extends Component { + render() { + return dom.div({}, "content"); + } + } + + // Test panel. + const TabPanel = createFactory(TabPanelClass); + + // Create a few panels. + await addTabWithPanel(1); + await addTabWithPanel(2); + await addTabWithPanel(3); + await addTabWithPanel(4); + await addTabWithPanel(5); + + // Make sure the all-tabs menu is there. + const allTabsMenu = tabBarBox.querySelector(".all-tabs-menu"); + ok(allTabsMenu, "All-tabs menu must be rendered"); + + function addTabWithPanel(tabId) { + return setState(tabbarReact, Object.assign({}, tabbarReact.state, { + tabs: tabbarReact.state.tabs.concat({id: `${tabId}`, + title: `tab-${tabId}`, panel: TabPanel}), + })); + } + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree-view_01.html b/devtools/client/shared/components/test/chrome/test_tree-view_01.html new file mode 100644 index 0000000000..0acae4c1dc --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree-view_01.html @@ -0,0 +1,290 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that TreeView component has working keyboard interactions. +--> +<head> + <meta charset="utf-8"> + <title>TreeView component keyboard test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = function() { + try { + const { a, button, div } = + require("devtools/client/shared/vendor/react-dom-factories"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const { + Simulate, + findRenderedDOMComponentWithClass, + findRenderedDOMComponentWithTag, + scryRenderedDOMComponentsWithClass, + } = browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const TreeView = + browserRequire("devtools/client/shared/components/tree/TreeView"); + + const _props = { + ...TEST_TREE_VIEW_INTERFACE, + renderValue: props => { + return (props.value === "C" ? + div({}, + props.value + " ", + a({ href: "#" }, "Focusable 1"), + button({ }, "Focusable 2")) : + props.value + "" + ); + }, + }; + const treeView = React.createElement(TreeView, _props); + const tree = ReactDOM.render(treeView, document.body); + const treeViewEl = findRenderedDOMComponentWithClass(tree, "treeTable"); + const rows = scryRenderedDOMComponentsWithClass(tree, "treeRow"); + const defaultFocus = treeViewEl.ownerDocument.body; + + function blurEl(el) { + // Simulate.blur does not seem to update the activeElement. + el.blur(); + } + + function focusEl(el) { + // Simulate.focus does not seem to update the activeElement. + el.focus(); + } + + const tests = [{ + name: "Test default TreeView state. Keyboard focus is set to document " + + "body by default.", + state: { selected: null, active: null }, + activeElement: defaultFocus, + }, { + name: "Selected row must be set to the first row on initial focus. " + + "Keyboard focus should be set on TreeView's conatiner.", + action: () => { + focusEl(treeViewEl); + Simulate.click(rows[0]); + }, + activeElement: treeViewEl, + state: { selected: "/B" }, + }, { + name: "Selected row should remain set even when the treeView is " + + "blured. Keyboard focus should be set back to document body.", + action: () => blurEl(treeViewEl), + state: { selected: "/B" }, + activeElement: defaultFocus, + }, { + name: "Selected row must be re-set again to the first row on initial " + + "focus. Keyboard focus should be set on treeView's conatiner.", + action: () => focusEl(treeViewEl), + activeElement: treeViewEl, + state: { selected: "/B" }, + }, { + name: "Selected row should be updated to next on ArrowDown.", + event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowDown" }}, + state: { selected: "/C" }, + }, { + name: "Selected row should be updated to last on ArrowDown.", + event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowDown" }}, + state: { selected: "/D" }, + }, { + name: "Selected row should remain on last on ArrowDown.", + event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowDown" }}, + state: { selected: "/D" }, + }, { + name: "Selected row should be updated to previous on ArrowUp.", + event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowUp" }}, + state: { selected: "/C" }, + }, { + name: "Selected row should be updated to first on ArrowUp.", + event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowUp" }}, + state: { selected: "/B" }, + }, { + name: "Selected row should remain on first on ArrowUp.", + event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowUp" }}, + state: { selected: "/B" }, + }, { + name: "Selected row should move to the next matching row with first letter navigation.", + event: { type: "keyDown", el: treeViewEl, options: { key: "C" }}, + state: { selected: "/C" }, + }, { + name: "Selected row should not change when there are no more visible nodes matching first letter navigation.", + event: { type: "keyDown", el: treeViewEl, options: { key: "C" }}, + state: { selected: "/C" }, + }, { + name: "Selected row should be updated to last on End.", + event: { type: "keyDown", el: treeViewEl, options: { key: "End" }}, + state: { selected: "/D" }, + }, { + name: "Selected row should be updated to first on Home.", + event: { type: "keyDown", el: treeViewEl, options: { key: "Home" }}, + state: { selected: "/B" }, + }, { + name: "Selected row should be set as active on Enter.", + event: { type: "keyDown", el: treeViewEl, options: { key: "Enter" }}, + state: { selected: "/B", active: "/B" }, + activeElement: treeViewEl, + }, { + name: "Active row should be unset on Escape.", + event: { type: "keyDown", el: treeViewEl, options: { key: "Escape" }}, + state: { selected: "/B", active: null }, + }, { + name: "Selected row should be set as active on Space.", + event: { type: "keyDown", el: treeViewEl, options: { key: " " }}, + state: { selected: "/B", active: "/B" }, + activeElement: treeViewEl, + }, { + name: "Selected row should unset when focus leaves the treeView.", + action: () => blurEl(treeViewEl), + state: { selected: "/B", active: null }, + activeElement: defaultFocus, + }, { + name: "Keyboard focus should be set on treeView's conatiner on focus.", + action: () => focusEl(treeViewEl), + activeElement: treeViewEl, + }, { + name: "Selected row should be updated to next on ArrowDown.", + event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowDown" }}, + state: { selected: "/C", active: null }, + }, { + name: "Selected row should be set as active on Enter. Keyboard focus " + + "should be set on the first focusable element inside the row, if " + + "available.", + event: { type: "keyDown", el: treeViewEl, options: { key: "Enter" }}, + state: { selected: "/C", active: "/C" }, + get activeElement() { + // When row becomes active/inactive, it is replaced with a newly + // rendered one. + return findRenderedDOMComponentWithTag(tree, "a"); + }, + }, { + name: "Keyboard focus should be set to next tabbable element inside " + + "the active row on Tab.", + action() { + synthesizeKey("KEY_Tab"); + }, + state: { selected: "/C", active: "/C" }, + get activeElement() { + // When row becomes active/inactive, it is replaced with a newly + // rendered one. + return findRenderedDOMComponentWithTag(tree, "button"); + }, + }, { + name: "Keyboard focus should wrap inside the row when focused on last " + + "tabbable element.", + action() { + synthesizeKey("KEY_Tab"); + }, + state: { selected: "/C", active: "/C" }, + get activeElement() { + return findRenderedDOMComponentWithTag(tree, "a"); + }, + }, { + name: "Keyboard focus should wrap inside the row when focused on first " + + "tabbable element.", + action() { + synthesizeKey("KEY_Tab", { shiftKey: true }); + }, + state: { selected: "/C", active: "/C" }, + get activeElement() { + return findRenderedDOMComponentWithTag(tree, "button"); + }, + }, { + name: "Active row should be unset on Escape. Focus should move back to " + + "the treeView container.", + event: { type: "keyDown", el: treeViewEl, options: { key: "Escape" }}, + state: { selected: "/C", active: null }, + activeElement: treeViewEl, + }, { + name: "Selected row should be set as active on Space. Keyboard focus " + + "should be set on the first focusable element inside the row, if " + + "available.", + event: { type: "keyDown", el: treeViewEl, options: { key: " " }}, + state: { selected: "/C", active: "/C" }, + get activeElement() { + // When row becomes active/inactive, it is replaced with a newly + // rendered one. + return findRenderedDOMComponentWithTag(tree, "a"); + }, + }, { + name: "Selected row should remain set even when the treeView is " + + "blured. Keyboard focus should be set back to document body.", + action: () => treeViewEl.ownerDocument.activeElement.blur(), + state: { selected: "/C", active: null }, + activeElement: defaultFocus, + }, { + name: "Keyboard focus should be set on treeView's conatiner on focus.", + action: () => focusEl(treeViewEl), + state: { selected: "/C", active: null }, + activeElement: treeViewEl, + }, { + name: "Selected row should be set as active on Space. Keyboard focus " + + "should be set on the first focusable element inside the row, if " + + "available.", + event: { type: "keyDown", el: treeViewEl, options: { key: " " }}, + state: { selected: "/C", active: "/C" }, + get activeElement() { + // When row becomes active/inactive, it is replaced with a newly + // rendered one. + return findRenderedDOMComponentWithTag(tree, "a"); + }, + }, { + name: "Selected row should be updated to previous on ArrowUp.", + event: { type: "keyDown", el: treeViewEl, options: { key: "ArrowUp" }}, + state: { selected: "/B", active: null }, + activeElement: treeViewEl, + }, { + name: "Selected row should be set as active on Enter.", + event: { type: "keyDown", el: treeViewEl, options: { key: "Enter" }}, + state: { selected: "/B", active: "/B" }, + activeElement: treeViewEl, + }, { + name: "Keyboard focus should move to another focusable element outside " + + "of the treeView when there's nothing to focus on inside the row.", + action() { + synthesizeKey("KEY_Tab", { shiftKey: true }); + }, + state: { selected: "/B", active: null }, + activeElement: treeViewEl.ownerDocument.documentElement, + }]; + + for (const test of tests) { + const { action, event, state, name } = test; + + info(name); + if (event) { + const { type, options, el } = event; + Simulate[type](el, options); + } else if (action) { + action(); + } + + if (test.activeElement) { + is(treeViewEl.ownerDocument.activeElement, test.activeElement, + "Focus is set correctly."); + } + + for (const key in state) { + is(tree.state[key], state[key], `${key} state is correct.`); + } + } + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree-view_02.html b/devtools/client/shared/components/test/chrome/test_tree-view_02.html new file mode 100644 index 0000000000..77c5934a66 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree-view_02.html @@ -0,0 +1,136 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that TreeView component filtering works with keyboard. +--> +<head> + <meta charset="utf-8"> + <title>TreeView component filtering keyboard test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> + <link rel="stylesheet" href="chrome://devtools/content/shared/components/tree/TreeView.css" type="text/css"> + <style> + .treeRow.hide { + display: none; + } + </style> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = function() { + try { + const React = browserRequire("devtools/client/shared/vendor/react"); + const { + Simulate, + findRenderedDOMComponentWithClass, + scryRenderedDOMComponentsWithClass, + } = browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const TreeView = + browserRequire("devtools/client/shared/components/tree/TreeView"); + + function testKeyboardInteraction(tree, treeViewEl, rows) { + // Expected tree when filtered (C is filtered) + // + // A + // |-- B + // `-- D + is(window.getComputedStyle(rows[1]).getPropertyValue("display"), "none", + "Row C must be hidden by default."); + + const tests = [{ + name: "Selected row must be set to the first row on initial focus. " + + "Keyboard focus must be set on TreeView's conatiner.", + action: () => { + Simulate.click(rows[0]); + }, + activeElement: treeViewEl, + state: { selected: "/B" }, + }, { + name: "Selecting next row must skip hidden row on ArrowDown.", + event: { + type: "keyDown", + el: treeViewEl, + options: { key: "ArrowDown" }, + }, + state: { selected: "/D" }, + }, { + name: "Selecting previous row must be skip hidden row on ArrowUp.", + event: { + type: "keyDown", + el: treeViewEl, + options: { key: "ArrowUp" }, + }, + state: { selected: "/B" }, + }]; + + for (const test of tests) { + const { action, event, state, name } = test; + + info(name); + if (event) { + const { type, options, el } = event; + Simulate[type](el, options); + } else if (action) { + action(); + } + + for (const key in state) { + is(tree.state[key], state[key], `${key} state is correct.`); + } + } + } + + info("Test hiding rows via decorator."); + const props = { + ...TEST_TREE_VIEW_INTERFACE, + decorator: { + getRowClass: ({ label }) => { + if (label === "C") { + return ["hide"]; + } + return []; + } + } + }; + let treeView = React.createElement(TreeView, props); + let tree = ReactDOM.render(treeView, document.body); + let treeViewEl = findRenderedDOMComponentWithClass(tree, "treeTable"); + let rows = scryRenderedDOMComponentsWithClass(tree, "treeRow"); + + testKeyboardInteraction(tree, treeViewEl, rows); + + // Remove TreeView component. + ReactDOM.unmountComponentAtNode(document.body); + + info("Test hiding rows via onFilter."); + props.decorator = null; + props.onFilter = ({ label }) => { + console.log(`onFILTER ${label !== "C"}`) + return label !== "C"; + }; + treeView = React.createElement(TreeView, props); + tree = ReactDOM.render(treeView, document.body); + treeViewEl = findRenderedDOMComponentWithClass(tree, "treeTable"); + rows = scryRenderedDOMComponentsWithClass(tree, "treeRow"); + + testKeyboardInteraction(tree, treeViewEl, rows); + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_01.html b/devtools/client/shared/components/test/chrome/test_tree_01.html new file mode 100644 index 0000000000..0740c8957e --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_01.html @@ -0,0 +1,68 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test trees get displayed with the items in correct order and at the correct +depth. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const Tree = React.createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + ok(React, "Should get React"); + ok(Tree, "Should get Tree"); + + const t = Tree(TEST_TREE_INTERFACE); + ok(t, "Should be able to create Tree instances"); + + const tree = ReactDOM.render(t, window.document.body); + ok(tree, "Should be able to mount Tree instances"); + isAccessibleTree(tree); + + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "Should get the items rendered and indented as expected"); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_02.html b/devtools/client/shared/components/test/chrome/test_tree_02.html new file mode 100644 index 0000000000..f538965572 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_02.html @@ -0,0 +1,49 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that collapsed subtrees aren't rendered. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const Tree = React.createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + const tree = ReactDOM.render(Tree(TEST_TREE_INTERFACE), window.document.body); + + isAccessibleTree(tree); + TEST_TREE.expanded = new Set("MNO".split("")); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "M:false", + "-N:false", + "--O:false", + ], "Collapsed subtrees shouldn't be rendered"); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_03.html b/devtools/client/shared/components/test/chrome/test_tree_03.html new file mode 100644 index 0000000000..6ebefa1fb7 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_03.html @@ -0,0 +1,50 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test Tree's autoExpandDepth. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const Tree = React.createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + const tree = ReactDOM.render(Tree(Object.assign({}, TEST_TREE_INTERFACE, { + autoExpandDepth: 1 + })), window.document.body); + + isAccessibleTree(tree); + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "-C:false", + "-D:false", + "M:false", + "-N:false", + ], "Tree should be auto expanded one level"); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_04.html b/devtools/client/shared/components/test/chrome/test_tree_04.html new file mode 100644 index 0000000000..2213f72497 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_04.html @@ -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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that we only render visible tree items. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + function getSpacerHeights() { + return { + top: document.querySelector(".tree > div:first-of-type").clientHeight, + bottom: document.querySelector(".tree > div:last-of-type").clientHeight, + }; + } + + const ITEM_HEIGHT = 3; + + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const Tree = React.createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + const tree = ReactDOM.render( + Tree(Object.assign({}, TEST_TREE_INTERFACE, { itemHeight: ITEM_HEIGHT })), + window.document.body); + + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + + await setState(tree, { + height: 3 * ITEM_HEIGHT, + scroll: 1 * ITEM_HEIGHT + }); + + isAccessibleTree(tree); + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + ], "Tree should show the 2nd, 3rd, and 4th items + buffer of 1 item at each end"); + + let spacers = getSpacerHeights(); + is(spacers.top, 0, "Top spacer has the correct height"); + is(spacers.bottom, 10 * ITEM_HEIGHT, "Bottom spacer has the correct height"); + + await setState(tree, { + height: 2 * ITEM_HEIGHT, + scroll: 3 * ITEM_HEIGHT + }); + + isAccessibleTree(tree); + isRenderedTree(document.body.textContent, [ + "--E:false", + "---K:false", + "---L:false", + "--F:false", + ], "Tree should show the 4th and 5th item + buffer of 1 item at each end"); + + spacers = getSpacerHeights(); + is(spacers.top, 2 * ITEM_HEIGHT, "Top spacer has the correct height"); + is(spacers.bottom, 9 * ITEM_HEIGHT, "Bottom spacer has the correct height"); + + // Set height to 2 items + 1 pixel at each end, scroll so that 4 items are visible + // (2 fully, 2 partially with 1 visible pixel) + await setState(tree, { + height: 2 * ITEM_HEIGHT + 2, + scroll: 3 * ITEM_HEIGHT - 1 + }); + + isRenderedTree(document.body.textContent, [ + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + ], "Tree should show the 4 visible items + buffer of 1 item at each end"); + + spacers = getSpacerHeights(); + is(spacers.top, 1 * ITEM_HEIGHT, "Top spacer has the correct height"); + is(spacers.bottom, 8 * ITEM_HEIGHT, "Bottom spacer has the correct height"); + + await setState(tree, { + height: 20 * ITEM_HEIGHT, + scroll: 0 + }); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "Tree should show all rows"); + + spacers = getSpacerHeights(); + is(spacers.top, 0, "Top spacer has zero height"); + is(spacers.bottom, 0, "Bottom spacer has zero height"); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_05.html b/devtools/client/shared/components/test/chrome/test_tree_05.html new file mode 100644 index 0000000000..5427a1bd8d --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_05.html @@ -0,0 +1,195 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test focusing with the Tree component. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const { Simulate } = + browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const Tree = + createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + function renderTree(props) { + const treeProps = Object.assign({}, + TEST_TREE_INTERFACE, + { onFocus: x => renderTree({ focused: x }) }, + props + ); + return ReactDOM.render(Tree(treeProps), window.document.body); + } + + const tree = renderTree(); + const treeElem = document.querySelector(".tree"); + + isAccessibleTree(tree); + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + + renderTree({ focused: "G" }); + isAccessibleTree(tree, { hasActiveDescendant: true }); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:true", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "G should be focused"); + + // When tree gets focus by means other than mouse, do not set first node as + // focused node when there is already a focused node. + Simulate.focus(treeElem); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:true", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "G should remain focused"); + + // Click the first tree node + document.querySelector(".tree-node").click(); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:true", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "A should be focused"); + + // Mouse down and mouse up events set tree "mouseDown" state correctly. + ok(!tree.state.mouseDown, "Mouse down state is not set."); + Simulate.mouseDown(document.querySelector(".tree-node")); + ok(tree.state.mouseDown, "Mouse down state is set."); + Simulate.mouseUp(document.querySelector(".tree-node")); + ok(!tree.state.mouseDown, "Mouse down state is reset."); + + // Unset focused tree state. + renderTree({ focused: null }); + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "No node should be focused"); + + // When tree gets focus while mouse is down, do not set first node as + // focused node. + Simulate.mouseDown(document.querySelector(".tree-node")); + Simulate.focus(treeElem); + Simulate.mouseUp(document.querySelector(".tree-node")); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "No node should have been focused"); + + // When tree gets focus by means other than mouse, set first node as focused + // node if no nodes are focused. + Simulate.focus(treeElem); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:true", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "A should be focused"); + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_06.html b/devtools/client/shared/components/test/chrome/test_tree_06.html new file mode 100644 index 0000000000..c8d1aa5e9f --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_06.html @@ -0,0 +1,340 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test keyboard navigation with the Tree component. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const { Simulate } = + browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const Tree = + createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + function renderTree(props) { + const treeProps = Object.assign({}, + TEST_TREE_INTERFACE, + { onFocus: x => renderTree({ focused: x }) }, + props + ); + return ReactDOM.render(Tree(treeProps), window.document.body); + } + + const tree = renderTree(); + + isAccessibleTree(tree); + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + + // UP ---------------------------------------------------------------------- + + info("Up to the previous sibling."); + renderTree({ focused: "L" }); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowUp" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:true", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the UP, K should be focused."); + + info("Up to the parent."); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowUp" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:true", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the UP, E should be focused."); + + info("Try and navigate up, past the first item."); + renderTree({ focused: "A" }); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowUp" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:true", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the UP, A should be focused and we shouldn't have overflowed past it."); + + // DOWN -------------------------------------------------------------------- + + info("Down to next sibling."); + renderTree({ focused: "K" }); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowDown" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:true", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the DOWN, L should be focused."); + + info("Down to parent's next sibling."); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowDown" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:true", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the DOWN, F should be focused."); + + info("Try and go down past the last item."); + renderTree({ focused: "O" }); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowDown" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:true", + ], "After the DOWN, O should still be focused " + + "and we shouldn't have overflowed past it."); + + // LEFT -------------------------------------------------------------------- + + info("Left to go to parent."); + renderTree({ focused: "L" }); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowLeft" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:true", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the LEFT, E should be focused."); + + info("Left to collapse children."); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowLeft" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:true", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the LEFT, E's children should be collapsed."); + + // RIGHT ------------------------------------------------------------------- + + info("Right to expand children."); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowRight" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:true", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the RIGHT, E's children should be expanded again."); + + info("Right on already expanded node."); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowRight" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:true", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the RIGHT on already expanded node, E should remain focused."); + + info("Right when preventNavigationOnArrowRight is unset to go to next item."); + renderTree({ focused: "E", preventNavigationOnArrowRight: false }); + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowRight" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:true", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the RIGHT, K should be focused."); + + // Check that keys are ignored if any modifier is present. + const keysWithModifier = [ + { key: "ArrowDown", altKey: true }, + { key: "ArrowDown", ctrlKey: true }, + { key: "ArrowDown", metaKey: true }, + { key: "ArrowDown", shiftKey: true }, + ]; + await forceRender(tree); + + for (const key of keysWithModifier) { + Simulate.keyDown(document.querySelector(".tree"), key); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:true", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After DOWN + (alt|ctrl|meta|shift), K should remain focused."); + } + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_07.html b/devtools/client/shared/components/test/chrome/test_tree_07.html new file mode 100644 index 0000000000..2e763aaf20 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_07.html @@ -0,0 +1,69 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that arrows get the open attribute when their item's children are expanded. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const React = browserRequire("devtools/client/shared/vendor/react"); + const dom = require("devtools/client/shared/vendor/react-dom-factories"); + const Tree = + React.createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + const treeProps = Object.assign({}, TEST_TREE_INTERFACE, { + renderItem: (item, depth, focused, arrow) => { + return dom.div( + { + id: item, + style: { marginLeft: depth * 16 + "px" } + }, + arrow, + item + ); + } + }); + const tree = ReactDOM.render(Tree(treeProps), window.document.body); + + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + await forceRender(tree); + + let arrows = document.querySelectorAll(".arrow"); + for (const a of arrows) { + ok(a.classList.contains("open"), "Every arrow should be open."); + } + + TEST_TREE.expanded = new Set(); + await forceRender(tree); + + arrows = document.querySelectorAll(".arrow"); + for (const a of arrows) { + ok(!a.classList.contains("open"), "Every arrow should be closed."); + } + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_08.html b/devtools/client/shared/components/test/chrome/test_tree_08.html new file mode 100644 index 0000000000..cfdff8090d --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_08.html @@ -0,0 +1,61 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that when an item in the Tree component is clicked, it steals focus from +other inputs. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const Tree = createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + function renderTree(props) { + const treeProps = Object.assign({}, + TEST_TREE_INTERFACE, + { onFocus: x => renderTree({ focused: x }) }, + props + ); + return ReactDOM.render(Tree(treeProps), window.document.body); + } + + const tree = renderTree(); + + const input = document.createElement("input"); + document.body.appendChild(input); + + input.focus(); + is(document.activeElement, input, "The text input should be focused."); + + document.querySelector(".tree-node").click(); + await forceRender(tree); + + isnot(document.activeElement, input, + "The input should have had it's focus stolen by clicking on a tree item."); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_09.html b/devtools/client/shared/components/test/chrome/test_tree_09.html new file mode 100644 index 0000000000..4d6a1010b5 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_09.html @@ -0,0 +1,85 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that when an item in the Tree component is expanded or collapsed the appropriate event handler fires. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const { Simulate } = browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const Tree = createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + let numberOfExpands = 0; + let lastExpandedItem = null; + + let numberOfCollapses = 0; + let lastCollapsedItem = null; + + function renderTree(props) { + const treeProps = Object.assign({}, + TEST_TREE_INTERFACE, + { + autoExpandDepth: 0, + onExpand: item => { + lastExpandedItem = item; + numberOfExpands++; + TEST_TREE.expanded.add(item); + }, + onCollapse: item => { + lastCollapsedItem = item; + numberOfCollapses++; + TEST_TREE.expanded.delete(item); + }, + onFocus: item => renderTree({ focused: item }) + }, + props + ); + return ReactDOM.render(Tree(treeProps), window.document.body); + } + + const tree = renderTree({ focused: "A" }); + + is(lastExpandedItem, null); + is(lastCollapsedItem, null); + + // Expand "A" via the keyboard and then let the component re-render. + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowRight" }); + await forceRender(tree); + + is(lastExpandedItem, "A", "Our onExpand callback should have been fired."); + is(numberOfExpands, 1); + + // Now collapse "A" via the keyboard and then let the component re-render. + Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowLeft" }); + await forceRender(tree); + + is(lastCollapsedItem, "A", "Our onCollapsed callback should have been fired."); + is(numberOfCollapses, 1); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_10.html b/devtools/client/shared/components/test/chrome/test_tree_10.html new file mode 100644 index 0000000000..7cda9e4348 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_10.html @@ -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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that when an item in the Tree component is expanded or collapsed the appropriate event handler fires. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const Tree = createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + function renderTree(props) { + const treeProps = Object.assign({}, + TEST_TREE_INTERFACE, + { autoExpandDepth: 1 }, + props + ); + return ReactDOM.render(Tree(treeProps), window.document.body); + } + + renderTree({ focused: "A" }); + + isRenderedTree(document.body.textContent, [ + "A:true", + "-B:false", + "-C:false", + "-D:false", + "M:false", + "-N:false", + ], "Should have auto-expanded one level."); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_11.html b/devtools/client/shared/components/test/chrome/test_tree_11.html new file mode 100644 index 0000000000..612a851018 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_11.html @@ -0,0 +1,100 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that when an item in the Tree component is focused by arrow key, the view is scrolled. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> + <style> + .tree { + height: 30px; + overflow: auto; + display: block; + } + + .tree-node { + font-size: 10px; + height: 10px; + } + </style> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +'use strict' + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const { Simulate } = browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const Tree = createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + + function renderTree(props) { + const treeProps = Object.assign({}, + TEST_TREE_INTERFACE, + { + itemHeight: 10, + onFocus: item => renderTree({ focused: item }) + }, + props + ); + return ReactDOM.render(Tree(treeProps), window.document.body); + } + + const tree = renderTree({ focused: "K" }); + + tree.setState({ scroll: 10 }); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:true", + "---L:false", + ], "Should render initial correctly"); + + await new Promise(resolve => { + const treeElem = document.querySelector(".tree"); + treeElem.addEventListener("scroll", function onScroll() { + dumpn("Got scroll event"); + treeElem.removeEventListener("scroll", onScroll); + resolve(); + }); + + dumpn("Sending ArrowDown key"); + Simulate.keyDown(treeElem, { key: "ArrowDown" }); + }); + + dumpn("Forcing re-render"); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "-B:false", + "--E:false", + "---K:false", + "---L:true", + "--F:false", + ], "Should have scrolled down one"); + + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_12.html b/devtools/client/shared/components/test/chrome/test_tree_12.html new file mode 100644 index 0000000000..4bcf7ef705 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_12.html @@ -0,0 +1,146 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test keyboard navigation/activation with the VirtualizedTree component. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const { Simulate } = + browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const Tree = + createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + function renderTree(props) { + const treeProps = { + ...TEST_TREE_INTERFACE, + onFocus: x => renderTree({ focused: x }), + ...props + }; + + return ReactDOM.render(Tree(treeProps), window.document.body); + } + + const tree = renderTree(); + + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + + // Test Home key ----------------------------------------------------------- + + info("Press Home to move to the first node."); + renderTree({ focused: "L" }); + Simulate.keyDown(document.querySelector(".tree"), { key: "Home" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:true", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the Home key, A should be focused."); + + info("Press Home again when already on first node."); + Simulate.keyDown(document.querySelector(".tree"), { key: "Home" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:true", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the Home key again, A should still be focused."); + + // Test End key ------------------------------------------------------------ + + info("Press End to move to the last node."); + Simulate.keyDown(document.querySelector(".tree"), { key: "End" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:true", + ], "After the End key, O should be focused."); + + info("Press End again when already on last node."); + Simulate.keyDown(document.querySelector(".tree"), { key: "End" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:true", + ], "After the End key again, O should still be focused."); + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_13.html b/devtools/client/shared/components/test/chrome/test_tree_13.html new file mode 100644 index 0000000000..183e144c82 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_13.html @@ -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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test trees have the correct scroll position when they are resized. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <style> + .tree { + height: 50px; + overflow: auto; + display: block; + } + + .tree-node { + font-size: 10px; + height: 10px; + } + </style> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = async function() { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const { Simulate } = + browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const Tree = createFactory( + browserRequire("devtools/client/shared/components/VirtualizedTree")); + const ITEM_HEIGHT = 10; + + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + + function renderTree(props) { + const treeProps = { + ...TEST_TREE_INTERFACE, + itemHeight: ITEM_HEIGHT, + onFocus: item => renderTree({ focused: item }), + ...props + }; + return ReactDOM.render(Tree(treeProps), document.body); + } + + const tree = renderTree({ focused: "L" }); + const treeEl = tree.refs.tree; + + is(tree.state.scroll, 0, "Scroll position should be 0 by default"); + is(treeEl.scrollTop, 0, "Tree scrollTop should be 0 by default"); + + info(`Focus on the next node and scroll by ${ITEM_HEIGHT}`); + Simulate.keyDown(treeEl, { key: "ArrowDown" }); + await forceRender(tree); + + is(tree.state.scroll, ITEM_HEIGHT, `Scroll position should now be ${ITEM_HEIGHT}`); + is(treeEl.scrollTop, ITEM_HEIGHT, + `Tree scrollTop should now be ${ITEM_HEIGHT}`); + + info("Simulate window resize along with scroll back to top"); + treeEl.scrollTo({ left: 0, top: 0 }); + window.dispatchEvent(new Event("resize")); + await forceRender(tree); + + is(tree.state.scroll, ITEM_HEIGHT, + `Scroll position should remain at ${ITEM_HEIGHT}`); + is(treeEl.scrollTop, ITEM_HEIGHT, + `Tree scrollTop should remain at ${ITEM_HEIGHT}`); + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_14.html b/devtools/client/shared/components/test/chrome/test_tree_14.html new file mode 100644 index 0000000000..d68d87d6c5 --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_14.html @@ -0,0 +1,245 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test that Tree component has working keyboard interactions. +--> +<head> + <meta charset="utf-8"> + <title>Tree component keyboard test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = async function() { + try { + const { a, button, div } = + require("devtools/client/shared/vendor/react-dom-factories"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const { + Simulate, + findRenderedDOMComponentWithClass, + findRenderedDOMComponentWithTag, + } = browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const Tree = createFactory( + browserRequire("devtools/client/shared/components/VirtualizedTree")); + + let gTree, gFocused, gActive; + function renderTree(props = {}) { + let toggle = true; + const treeProps = { + ...TEST_TREE_INTERFACE, + onFocus: x => { + gFocused = x; + renderTree({ focused: gFocused, active: gActive }); + }, + onActivate: x => { + gActive = x; + renderTree({ focused: gFocused, active: gActive }); + }, + renderItem: (x, depth, focused) => { + toggle = !toggle; + return toggle ? + (div( + {}, + `${"-".repeat(depth)}${x}:${focused}`, + a({ href: "#" }, "Focusable 1"), + button({ }, "Focusable 2"), + "\n", + ) + ) : `${"-".repeat(depth)}${x}:${focused}`; + }, + ...props + }; + + gTree = ReactDOM.render(Tree(treeProps), document.body); + } + + renderTree(); + const els = { + get tree() { + // React will replace the tree via renderTree. + return findRenderedDOMComponentWithClass(gTree, "tree"); + }, + get anchor() { + // When tree node becomes active/inactive, it is replaced with a newly rendered + // one. + return findRenderedDOMComponentWithTag(gTree, "a"); + }, + get button() { + // When tree node becomes active/inactive, it is replaced with a newly rendered + // one. + return findRenderedDOMComponentWithTag(gTree, "button"); + }, + }; + + const tests = [{ + name: "Test default Tree props. Keyboard focus is set to document body by default.", + props: { focused: undefined, active: undefined }, + activeElement: document.body, + }, { + name: "Focused props must be set to the first node on initial focus. " + + "Keyboard focus should be set on the tree.", + action: () => els.tree.focus(), + activeElement: "tree", + props: { focused: "A" }, + }, { + name: "Focused node should remain set even when the tree is blured. " + + "Keyboard focus should be set back to document body.", + action: () => els.tree.blur(), + props: { focused: "A" }, + activeElement: document.body, + }, { + name: "Unset tree's focused prop.", + action: () => renderTree({ focused: null }), + props: { focused: null }, + }, { + name: "Focused node must be re-set again to the first tree node on initial " + + "focus. Keyboard focus should be set on tree's conatiner.", + action: () => els.tree.focus(), + activeElement: "tree", + props: { focused: "A" }, + }, { + name: "Focused node should be set as active on Enter.", + event: { type: "keyDown", el: "tree", options: { key: "Enter" }}, + props: { focused: "A", active: "A" }, + activeElement: "tree", + }, { + name: "Active node should be unset on Escape.", + event: { type: "keyDown", el: "tree", options: { key: "Escape" }}, + props: { focused: "A", active: null }, + }, { + name: "Focused node should be set as active on Space.", + event: { type: "keyDown", el: "tree", options: { key: " " }}, + props: { focused: "A", active: "A" }, + activeElement: "tree", + }, { + name: "Active node should unset when focus leaves the tree.", + action: () => els.tree.blur(), + props: { focused: "A", active: null }, + activeElement: document.body, + }, { + name: "Keyboard focus should be set on tree's conatiner on focus.", + action: () => els.tree.focus(), + activeElement: "tree", + }, { + name: "Focused node should be updated to next on ArrowDown.", + event: { type: "keyDown", el: "tree", options: { key: "ArrowDown" }}, + props: { focused: "M", active: null }, + }, { + name: "Focused item should be set as active on Enter. Keyboard focus should be " + + "set on the first focusable element inside the tree node, if available.", + event: { type: "keyDown", el: "tree", options: { key: "Enter" }}, + props: { focused: "M", active: "M" }, + activeElement: "anchor", + }, { + name: "Keyboard focus should be set to next tabbable element inside the active " + + "node on Tab.", + action() { + synthesizeKey("KEY_Tab"); + }, + props: { focused: "M", active: "M" }, + activeElement: "button", + }, { + name: "Keyboard focus should wrap inside the tree node when focused on last " + + "tabbable element.", + action() { + synthesizeKey("KEY_Tab"); + }, + props: { focused: "M", active: "M" }, + activeElement: "anchor", + }, { + name: "Keyboard focus should wrap inside the tree node when focused on first " + + "tabbable element.", + action() { + synthesizeKey("KEY_Tab", { shiftKey: true }); + }, + props: { focused: "M", active: "M" }, + activeElement: "button", + }, { + name: "Active tree node should be unset on Escape. Focus should move back to the " + + "tree container.", + event: { type: "keyDown", el: "tree", options: { key: "Escape" }}, + props: { focused: "M", active: null }, + activeElement: "tree", + }, { + name: "Focused node should be set as active on Space. Keyboard focus should be " + + "set on the first focusable element inside the tree node, if available.", + event: { type: "keyDown", el: "tree", options: { key: " " }}, + props: { focused: "M", active: "M" }, + activeElement: "anchor", + }, { + name: "Focused tree node should remain set even when the tree is blured. " + + "Keyboard focus should be set back to document body.", + action: () => document.activeElement.blur(), + props: { focused: "M", active: null, }, + activeElement: document.body, + }, { + name: "Keyboard focus should be set on tree's conatiner on focus.", + action: () => els.tree.focus(), + props: { focused: "M", active: null }, + activeElement: "tree", + }, { + name: "Focused tree node should be updated to previous on ArrowUp.", + event: { type: "keyDown", el: "tree", options: { key: "ArrowUp" }}, + props: { focused: "A", active: null }, + }, { + name: "Focused item should be set as active on Enter.", + event: { type: "keyDown", el: "tree", options: { key: "Enter" }}, + props: { focused: "A", active: "A" }, + activeElement: "tree", + }, { + name: "Keyboard focus should move to another focusable element outside of the " + + "tree when there's nothing to focus on inside the tree node.", + action() { + synthesizeKey("KEY_Tab", { shiftKey: true }); + }, + props: { focused: "A", active: null }, + activeElement: document.documentElement, + }]; + + for (const test of tests) { + const { action, event, props, name } = test; + + info(name); + if (event) { + const { type, options, el } = event; + const target = typeof el === "string" ? els[el] : el; + Simulate[type](target, options); + } else if (action) { + action(); + } + + await forceRender(gTree); + + if (test.activeElement) { + const expected = typeof test.activeElement === "string" ? + els[test.activeElement] : test.activeElement; + // eslint-disable-next-line no-debugger + if (document.activeElement!==expected) {debugger;} + is(document.activeElement, expected, "Focus is set correctly."); + } + + for (const key in props) { + is(gTree.props[key], props[key], `${key} prop is correct.`); + } + } + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_15.html b/devtools/client/shared/components/test/chrome/test_tree_15.html new file mode 100644 index 0000000000..399e3d9ecd --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_15.html @@ -0,0 +1,99 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test scroll position when focusing items in traversal but not rendered. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> + <style> + .tree { + height: 30px; + overflow: auto; + display: block; + } + + .tree-node { + font-size: 10px; + height: 10px; + } + </style> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const { Simulate } = + browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const Tree = + createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + + function renderTree(props) { + const treeProps = Object.assign({}, + TEST_TREE_INTERFACE, + { + itemHeight: 10, + onFocus: item => renderTree({ focused: item }) + }, + props + ); + return ReactDOM.render(Tree(treeProps), window.document.body); + } + + info("Test first focused."); + const tree = renderTree({ focused: "A" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:true", + "-B:false", + "--E:false", + "---K:false", + ], "Should render initial correctly"); + + info("Test last item focused when it was not yet rendered."); + Simulate.keyDown(document.querySelector(".tree"), { key: "End" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "--J:false", + "M:false", + "-N:false", + "--O:true", + ], "Should render last focused item correctly"); + + info("Test first item focused when it was not yet rendered."); + Simulate.keyDown(document.querySelector(".tree"), { key: "Home" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:true", + "-B:false", + "--E:false", + "---K:false", + ], "Should render first focused item correctly"); + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/chrome/test_tree_16.html b/devtools/client/shared/components/test/chrome/test_tree_16.html new file mode 100644 index 0000000000..b70e63eade --- /dev/null +++ b/devtools/client/shared/components/test/chrome/test_tree_16.html @@ -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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test scroll position when showing items both in traversal and/or rendered. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css"> + <style> + .tree { + height: 30px; + overflow: auto; + display: block; + } + + .tree-node { + font-size: 10px; + height: 10px; + } + </style> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const Tree = + createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + + function renderTree(props) { + const treeProps = Object.assign({}, + TEST_TREE_INTERFACE, + { + itemHeight: 10, + onFocus: item => renderTree({ shown: item }) + }, + props + ); + return ReactDOM.render(Tree(treeProps), window.document.body); + } + + info("Test first shown."); + const tree = renderTree({ shown: "A" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + ], "Should render initial correctly"); + + info("Test last as shown when it was not yet rendered."); + renderTree({ shown: "O" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "Should render shown item correctly"); + + info("Test first item shown when it's not first rendered."); + renderTree({ shown: "A" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + ], "Should render shown item correctly"); + + info("Test mid item shown when it's not first rendered."); + renderTree({ shown: "G" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + ], "Should render shown item correctly"); + + info("Test mid item shown when it's already rendered."); + renderTree({ shown: "C" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + ], "Should render shown item correctly"); + + info("Test item that is not in traversal."); + renderTree({ shown: "Z" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + ], "Should render without changes"); + + info("Test item that is already shown."); + renderTree({ shown: "F" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + ], "Should render without changes"); + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/client/shared/components/test/node/.eslintrc.js b/devtools/client/shared/components/test/node/.eslintrc.js new file mode 100644 index 0000000000..ffb3e70473 --- /dev/null +++ b/devtools/client/shared/components/test/node/.eslintrc.js @@ -0,0 +1,10 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +module.exports = { + env: { + jest: true, + }, +}; diff --git a/devtools/client/shared/components/test/node/__mocks__/Services.js b/devtools/client/shared/components/test/node/__mocks__/Services.js new file mode 100644 index 0000000000..14581e8fda --- /dev/null +++ b/devtools/client/shared/components/test/node/__mocks__/Services.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/. */ + +"use strict"; + +module.exports = { + appinfo: "", + prefs: { + getBoolPref(name, defaultVal) { + return defaultVal; + }, + }, +}; diff --git a/devtools/client/shared/components/test/node/__mocks__/object-front.js b/devtools/client/shared/components/test/node/__mocks__/object-front.js new file mode 100644 index 0000000000..def182111d --- /dev/null +++ b/devtools/client/shared/components/test/node/__mocks__/object-front.js @@ -0,0 +1,55 @@ +/* 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/>. */ + +"use strict"; + +function ObjectFront(grip, overrides) { + return { + grip, + enumEntries() { + return Promise.resolve( + this.getIterator({ + ownProperties: {}, + }) + ); + }, + enumProperties(options) { + return Promise.resolve( + this.getIterator({ + ownProperties: {}, + }) + ); + }, + enumSymbols() { + return Promise.resolve( + this.getIterator({ + ownSymbols: [], + }) + ); + }, + enumPrivateProperties() { + return Promise.resolve( + this.getIterator({ + privateProperties: [], + }) + ); + }, + getPrototype() { + return Promise.resolve({ + prototype: {}, + }); + }, + // Declared here so we can override it. + getIterator(res) { + return { + slice(start, count) { + return Promise.resolve(res); + }, + }; + }, + ...overrides, + }; +} + +module.exports = ObjectFront; diff --git a/devtools/client/shared/components/test/node/__mocks__/string-front.js b/devtools/client/shared/components/test/node/__mocks__/string-front.js new file mode 100644 index 0000000000..d743f79e8b --- /dev/null +++ b/devtools/client/shared/components/test/node/__mocks__/string-front.js @@ -0,0 +1,15 @@ +/* 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/>. */ + +"use strict"; + +function LongStringFront(grip, overrides) { + return { + grip, + substring: async () => "", + ...overrides, + }; +} + +module.exports = { LongStringFront }; diff --git a/devtools/client/shared/components/test/node/babel.config.js b/devtools/client/shared/components/test/node/babel.config.js new file mode 100644 index 0000000000..2a95c9f71c --- /dev/null +++ b/devtools/client/shared/components/test/node/babel.config.js @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +module.exports = { + plugins: [ + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-optional-chaining", + "@babel/plugin-proposal-nullish-coalescing-operator", + "transform-amd-to-commonjs", + ], +}; diff --git a/devtools/client/shared/components/test/node/components/__snapshots__/tree.test.js.snap b/devtools/client/shared/components/test/node/components/__snapshots__/tree.test.js.snap new file mode 100644 index 0000000000..aad7d06189 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/__snapshots__/tree.test.js.snap @@ -0,0 +1,1171 @@ +// 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 active item - focus is inside the tree node and then blur 1`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | [L anchor] +| | F +| | G +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - focus is inside the tree node when possible 1`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | [L anchor] +| | F +| | G +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - focus is inside the tree node when possible 2`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | [L anchor] +| | F +| | G +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - navigate inside the tree node 1`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | [L anchor] +| | F +| | G +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - navigate inside the tree node 2`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | [L anchor] +| | F +| | G +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - navigate inside the tree node 3`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | [L anchor] +| | F +| | G +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - renders as expected when clicking away 1`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | L +| | F +| | [G] +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - renders as expected when clicking away 2`] = ` +" +▶︎ [A] +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - renders as expected when moving away with keyboard 1`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | [L] +| | F +| | G +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - renders as expected when tree blurs 1`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | L +| | F +| | [G] +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - renders as expected when tree blurs 2`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | L +| | F +| | [G] +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - renders as expected when using keyboard and Enter 1`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | [L] +| | F +| | G +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree active item - renders as expected when using keyboard and Space 1`] = ` +" +▼ A +| ▼ B +| | ▼ E +| | | K +| | | [L] +| | F +| | G +| ▼ C +| | H +| | I +| ▼ D +| | J +▼ M +| ▼ N +| | O +" +`; + +exports[`Tree calls shouldItemUpdate when provided 1`] = ` +" +▶︎ A +▶︎ M +" +`; + +exports[`Tree calls shouldItemUpdate when provided 2`] = ` +" +▶︎ A +▶︎ M +" +`; + +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 +" +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/basic.test.js.snap b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/basic.test.js.snap new file mode 100644 index 0000000000..9db3eadc93 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/basic.test.js.snap @@ -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\\" } ] +" +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/classnames.test.js.snap b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/classnames.test.js.snap new file mode 100644 index 0000000000..81cc9f7028 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/classnames.test.js.snap @@ -0,0 +1,351 @@ +// 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 {}} + tabIndex="0" +> + <TreeNode + active={false} + depth={0} + expanded={false} + focused={false} + id="root" + index={0} + isExpandable={false} + item={ + Object { + "contents": Object { + "value": 42, + }, + "name": "root", + "path": "root", + } + } + key="root-inactive" + onClick={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-level={1} + className="tree-node" + data-expandable={false} + id="root" + onClick={[Function]} + onKeyDownCapture={null} + role="treeitem" + > + <ObjectInspectorItem + addWatchpoint={[Function]} + arrow={null} + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + depth={0} + evaluations={Map {}} + expanded={false} + expandedPaths={Set {}} + focused={false} + invokeGetter={[Function]} + item={ + Object { + "contents": Object { + "value": 42, + }, + "name": "root", + "path": "root", + } + } + loadedProperties={Map {}} + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + onContextMenu={[Function]} + removeWatchpoint={[Function]} + renderItemActions={[Function]} + roots={ + Array [ + Object { + "contents": Object { + "value": 42, + }, + "name": "root", + "path": "root", + }, + ] + } + rootsChanged={[Function]} + setExpanded={[Function]} + > + <div + className="node object-node" + onClick={[Function]} + onContextMenu={[Function]} + > + <span + className="object-label" + > + root + </span> + <span + className="object-delimiter" + > + : + </span> + <span + className="objectBox objectBox-number" + title={null} + > + 42 + </span> + </div> + </ObjectInspectorItem> + </div> + </TreeNode> +</div> +`; + +exports[`ObjectInspector - classnames has the inline class when inline prop is true 1`] = ` +<div + className="tree object-inspector inline" + onBlur={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyPress={[Function]} + onKeyUp={[Function]} + role="tree" + style={Object {}} + tabIndex="0" +> + <TreeNode + active={false} + depth={0} + expanded={false} + focused={false} + id="root" + index={0} + isExpandable={false} + item={ + Object { + "contents": Object { + "value": 42, + }, + "name": "root", + "path": "root", + } + } + key="root-inactive" + onClick={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-level={1} + className="tree-node" + data-expandable={false} + id="root" + onClick={[Function]} + onKeyDownCapture={null} + role="treeitem" + > + <ObjectInspectorItem + addWatchpoint={[Function]} + arrow={null} + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + depth={0} + evaluations={Map {}} + expanded={false} + expandedPaths={Set {}} + focused={false} + inline={true} + invokeGetter={[Function]} + item={ + Object { + "contents": Object { + "value": 42, + }, + "name": "root", + "path": "root", + } + } + loadedProperties={Map {}} + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + onContextMenu={[Function]} + removeWatchpoint={[Function]} + renderItemActions={[Function]} + roots={ + Array [ + Object { + "contents": Object { + "value": 42, + }, + "name": "root", + "path": "root", + }, + ] + } + rootsChanged={[Function]} + setExpanded={[Function]} + > + <div + className="node object-node" + onClick={[Function]} + onContextMenu={[Function]} + > + <span + className="object-label" + > + root + </span> + <span + className="object-delimiter" + > + : + </span> + <span + className="objectBox objectBox-number" + title={null} + > + 42 + </span> + </div> + </ObjectInspectorItem> + </div> + </TreeNode> +</div> +`; + +exports[`ObjectInspector - classnames has the nowrap class when disableWrap prop is true 1`] = ` +<div + className="tree object-inspector nowrap" + onBlur={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyPress={[Function]} + onKeyUp={[Function]} + role="tree" + style={Object {}} + tabIndex="0" +> + <TreeNode + active={false} + depth={0} + expanded={false} + focused={false} + id="root" + index={0} + isExpandable={false} + item={ + Object { + "contents": Object { + "value": 42, + }, + "name": "root", + "path": "root", + } + } + key="root-inactive" + onClick={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-level={1} + className="tree-node" + data-expandable={false} + id="root" + onClick={[Function]} + onKeyDownCapture={null} + role="treeitem" + > + <ObjectInspectorItem + addWatchpoint={[Function]} + arrow={null} + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + depth={0} + disableWrap={true} + evaluations={Map {}} + expanded={false} + expandedPaths={Set {}} + focused={false} + invokeGetter={[Function]} + item={ + Object { + "contents": Object { + "value": 42, + }, + "name": "root", + "path": "root", + } + } + loadedProperties={Map {}} + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + onContextMenu={[Function]} + removeWatchpoint={[Function]} + renderItemActions={[Function]} + roots={ + Array [ + Object { + "contents": Object { + "value": 42, + }, + "name": "root", + "path": "root", + }, + ] + } + rootsChanged={[Function]} + setExpanded={[Function]} + > + <div + className="node object-node" + onClick={[Function]} + onContextMenu={[Function]} + > + <span + className="object-label" + > + root + </span> + <span + className="object-delimiter" + > + : + </span> + <span + className="objectBox objectBox-number" + title={null} + > + 42 + </span> + </div> + </ObjectInspectorItem> + </div> + </TreeNode> +</div> +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/entries.test.js.snap b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/entries.test.js.snap new file mode 100644 index 0000000000..5208db3ebb --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/entries.test.js.snap @@ -0,0 +1,94 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ObjectInspector - entries calls ObjectFront.enumEntries when expected 1`] = ` +" +▼ Map(11) +| ▶︎ <entries> +" +`; + +exports[`ObjectInspector - entries calls ObjectFront.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 ObjectFront.enumEntries when expected 3`] = ` +" + ▼ Map(11) +[ | ▶︎ <entries> ] +" +`; + +exports[`ObjectInspector - entries calls ObjectFront.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 { Symbol(\\"a\\") → \\"value-a\\", Symbol(\\"b\\") → \\"value-b\\" } +| size: 2 +| ▼ <entries> +| | ▼ 0: \\"key-0\\" → \\"value-0\\" +| | | <key>: \\"key-0\\" +| | | <value>: \\"value-0\\" +| | ▼ 1: \\"key-1\\" → \\"value-1\\" +| | | <key>: \\"key-1\\" +| | | <value>: \\"value-1\\" +| | ▼ 2: \\"key-2\\" → \\"value-2\\" +| | | <key>: \\"key-2\\" +| | | <value>: \\"value-2\\" +| | ▼ 3: \\"key-3\\" → \\"value-3\\" +| | | <key>: \\"key-3\\" +| | | <value>: \\"value-3\\" +| | ▼ 4: \\"key-4\\" → \\"value-4\\" +| | | <key>: \\"key-4\\" +| | | <value>: \\"value-4\\" +| | ▼ 5: \\"key-5\\" → \\"value-5\\" +| | | <key>: \\"key-5\\" +| | | <value>: \\"value-5\\" +| | ▼ 6: \\"key-6\\" → \\"value-6\\" +| | | <key>: \\"key-6\\" +| | | <value>: \\"value-6\\" +| | ▼ 7: \\"key-7\\" → \\"value-7\\" +| | | <key>: \\"key-7\\" +| | | <value>: \\"value-7\\" +| | ▼ 8: \\"key-8\\" → \\"value-8\\" +| | | <key>: \\"key-8\\" +| | | <value>: \\"value-8\\" +| | ▼ 9: \\"key-9\\" → \\"value-9\\" +| | | <key>: \\"key-9\\" +| | | <value>: \\"value-9\\" +| | ▼ 10: \\"key-10\\" → \\"value-10\\" +| | | <key>: \\"key-10\\" +| | | <value>: \\"value-10\\" +| ▼ <prototype>: Object { … } +| | <prototype>: Object { } +" +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/expand.test.js.snap b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/expand.test.js.snap new file mode 100644 index 0000000000..3cb8b39dee --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/expand.test.js.snap @@ -0,0 +1,175 @@ +// 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 handle actors when client does not have releaseActor function 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 handle actors when client does not have releaseActor function 2`] = ` +" +[ ▼ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … } ] + | ▶︎ <prototype>: Object { } + ▶︎ Proxy { <target>: {…}, <handler>: (3) […] } +" +`; + +exports[`ObjectInspector - state does not handle actors when client does not have releaseActor function 3`] = ` +" + ▼ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … } +[ | ▼ <prototype>: Object { } ] + | | ▶︎ <prototype>: Object { } + ▶︎ 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`] = ` +" +▼ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … } +| ▼ 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`] = ` +" +[ ▼ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … } ] + | 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`] = ` +" +[ ▼ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … } ] + | 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>: {…}, <handler>: (3) […] } ] + | ▶︎ <target>: Object { … } + | ▶︎ <handler>: Array(3) [ … ] +" +`; + +exports[`ObjectInspector - state has the expected expandedPaths state when clicking nodes 5`] = ` +" +[ ▼ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … } ] + | a: 1 + | Symbol(): \\"hello\\" + | ▶︎ <prototype>: Object { … } + ▼ Proxy { <target>: {…}, <handler>: (3) […] } + | ▶︎ <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`] = ` +" +[ ▼ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … } ] + | ▶︎ <prototype>: Object { } + ▶︎ Proxy { <target>: {…}, <handler>: (3) […] } +" +`; + +exports[`ObjectInspector - state has the expected state when expanding a node 3`] = ` +" + ▼ Object { p0: \\"0\\", p1: \\"1\\", p2: \\"2\\", p3: \\"3\\", p4: \\"4\\", p5: \\"5\\", p6: \\"6\\", p7: \\"7\\", p8: \\"8\\", p9: \\"9\\", … } +[ | ▼ <prototype>: Object { } ] + | | ▶︎ <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 { } +" +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/getter-setter.test.js.snap b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/getter-setter.test.js.snap new file mode 100644 index 0000000000..86ededb690 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/getter-setter.test.js.snap @@ -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() +" +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/keyboard-navigation.test.js.snap b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/keyboard-navigation.test.js.snap new file mode 100644 index 0000000000..8998ce3aff --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/keyboard-navigation.test.js.snap @@ -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`] = ` +" +[ ▼ Object { a: \\"a\\", b: \\"b\\", c: \\"c\\" } ] + | <prototype>: Object { } +" +`; + +exports[`ObjectInspector - keyboard navigation works as expected 4`] = ` +" + ▼ Object { a: \\"a\\", b: \\"b\\", c: \\"c\\" } +[ | <prototype>: Object { } ] +" +`; + +exports[`ObjectInspector - keyboard navigation works as expected 5`] = ` +" +[ ▼ Object { a: \\"a\\", b: \\"b\\", c: \\"c\\" } ] + | <prototype>: Object { } +" +`; + +exports[`ObjectInspector - keyboard navigation works as expected 6`] = ` +" + ▼ Object { a: \\"a\\", b: \\"b\\", c: \\"c\\" } +[ | <prototype>: Object { } ] +" +`; + +exports[`ObjectInspector - keyboard navigation works as expected 7`] = ` +" +[ ▼ Object { a: \\"a\\", b: \\"b\\", c: \\"c\\" } ] + | <prototype>: Object { } +" +`; + +exports[`ObjectInspector - keyboard navigation works as expected 8`] = ` +" +▼ Object { a: \\"a\\", b: \\"b\\", c: \\"c\\" } +| <prototype>: Object { } +" +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/properties.test.js.snap b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/properties.test.js.snap new file mode 100644 index 0000000000..3bb9e517a8 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/properties.test.js.snap @@ -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) +" +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/proxy.test.js.snap b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/proxy.test.js.snap new file mode 100644 index 0000000000..d48a6e058d --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/proxy.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ObjectInspector - Proxy renders Proxy as expected 1`] = ` +" +▼ Proxy { <target>: {…}, <handler>: (3) […] } +| ▶︎ <target>: Object { … } +| ▶︎ <handler>: Array(3) [ … ] +" +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/window.test.js.snap b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/window.test.js.snap new file mode 100644 index 0000000000..fcd2cc3693 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/window.test.js.snap @@ -0,0 +1,2119 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ObjectInspector - dimTopLevelWindow renders collapsed top-level window when dimTopLevelWindow =false 1`] = ` +<Provider + store={ + Object { + "dispatch": [Function], + "getState": [Function], + "replaceReducer": [Function], + "subscribe": [Function], + Symbol(observable): [Function], + } + } +> + <Component + autoExpandDepth={0} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + > + <Connect(ObjectInspector) + autoExpandDepth={0} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + > + <ObjectInspector + addWatchpoint={[Function]} + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + evaluations={Map {}} + expandedPaths={Set {}} + invokeGetter={[Function]} + loadedProperties={Map {}} + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + removeWatchpoint={[Function]} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + rootsChanged={[Function]} + > + <Tree + autoExpandAll={true} + autoExpandDepth={0} + className="object-inspector" + getChildren={[Function]} + getKey={[Function]} + getParent={[Function]} + getRoots={[Function]} + isExpandable={[Function]} + isExpanded={[Function]} + onActivate={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + onFocus={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + className="tree object-inspector" + onBlur={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyPress={[Function]} + onKeyUp={[Function]} + role="tree" + style={Object {}} + tabIndex="0" + > + <TreeNode + active={false} + depth={0} + expanded={false} + focused={false} + id="window" + index={0} + isExpandable={true} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + key="window-inactive" + onClick={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-expanded={false} + aria-level={1} + className="tree-node" + data-expandable={true} + id="window" + onClick={[Function]} + onKeyDownCapture={null} + role="treeitem" + > + <ObjectInspectorItem + addWatchpoint={[Function]} + arrow={ + <ArrowExpander + expanded={false} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + /> + } + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + depth={0} + evaluations={Map {}} + expanded={false} + expandedPaths={Set {}} + focused={false} + invokeGetter={[Function]} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + loadedProperties={Map {}} + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + onContextMenu={[Function]} + removeWatchpoint={[Function]} + renderItemActions={[Function]} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + rootsChanged={[Function]} + setExpanded={[Function]} + > + <div + className="node object-node" + onClick={[Function]} + onContextMenu={[Function]} + > + <ArrowExpander + expanded={false} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + > + <button + className="arrow" + title="Expand" + /> + </ArrowExpander> + <span + className="object-label" + > + window + </span> + <span + className="object-delimiter" + > + : + </span> + <span + className="objectBox objectBox-Window" + data-link-actor-id="server0.conn0.windowGlobal2147483651/obj35" + title={null} + > + <span + className="objectTitle" + > + Window + </span> + </span> + </div> + </ObjectInspectorItem> + </div> + </TreeNode> + </div> + </Tree> + </ObjectInspector> + </Connect(ObjectInspector)> + </Component> +</Provider> +`; + +exports[`ObjectInspector - dimTopLevelWindow renders sub-level window 1`] = ` +<Provider + store={ + Object { + "dispatch": [Function], + "getState": [Function], + "replaceReducer": [Function], + "subscribe": [Function], + Symbol(observable): [Function], + } + } +> + <Component + autoExpandDepth={0} + dimTopLevelWindow={true} + injectWaitService={true} + roots={ + Array [ + Object { + "contents": Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ], + "meta": undefined, + "name": "root", + "parent": undefined, + "path": "root", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + > + <Connect(ObjectInspector) + autoExpandDepth={0} + dimTopLevelWindow={true} + injectWaitService={true} + roots={ + Array [ + Object { + "contents": Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ], + "meta": undefined, + "name": "root", + "parent": undefined, + "path": "root", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + > + <ObjectInspector + addWatchpoint={[Function]} + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + dimTopLevelWindow={true} + evaluations={Map {}} + expandedPaths={ + Set { + "root", + } + } + injectWaitService={true} + invokeGetter={[Function]} + loadedProperties={ + Map { + "root" => Object {}, + } + } + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + removeWatchpoint={[Function]} + roots={ + Array [ + Object { + "contents": Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ], + "meta": undefined, + "name": "root", + "parent": undefined, + "path": "root", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + rootsChanged={[Function]} + > + <Tree + autoExpandAll={true} + autoExpandDepth={0} + className="object-inspector" + focused={ + Object { + "contents": Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ], + "meta": undefined, + "name": "root", + "parent": undefined, + "path": "root", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + getChildren={[Function]} + getKey={[Function]} + getParent={[Function]} + getRoots={[Function]} + isExpandable={[Function]} + isExpanded={[Function]} + onActivate={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + onFocus={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-activedescendant="root" + className="tree object-inspector" + onBlur={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyPress={[Function]} + onKeyUp={[Function]} + role="tree" + style={Object {}} + tabIndex="0" + > + <TreeNode + active={false} + depth={0} + expanded={true} + focused={true} + id="root" + index={0} + isExpandable={true} + item={ + Object { + "contents": Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ], + "meta": undefined, + "name": "root", + "parent": undefined, + "path": "root", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + key="root-inactive" + onClick={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-expanded={true} + aria-level={1} + className="tree-node focused" + data-expandable={true} + id="root" + onClick={[Function]} + onKeyDownCapture={null} + role="treeitem" + > + <ObjectInspectorItem + addWatchpoint={[Function]} + arrow={ + <ArrowExpander + expanded={true} + item={ + Object { + "contents": Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ], + "meta": undefined, + "name": "root", + "parent": undefined, + "path": "root", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + /> + } + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + depth={0} + dimTopLevelWindow={true} + evaluations={Map {}} + expanded={true} + expandedPaths={ + Set { + "root", + } + } + focused={true} + injectWaitService={true} + invokeGetter={[Function]} + item={ + Object { + "contents": Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ], + "meta": undefined, + "name": "root", + "parent": undefined, + "path": "root", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + loadedProperties={Map {}} + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + onContextMenu={[Function]} + removeWatchpoint={[Function]} + renderItemActions={[Function]} + roots={ + Array [ + Object { + "contents": Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ], + "meta": undefined, + "name": "root", + "parent": undefined, + "path": "root", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + rootsChanged={[Function]} + setExpanded={[Function]} + > + <div + className="node object-node focused" + onClick={[Function]} + onContextMenu={[Function]} + > + <ArrowExpander + expanded={true} + item={ + Object { + "contents": Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ], + "meta": undefined, + "name": "root", + "parent": undefined, + "path": "root", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + > + <button + className="arrow expanded" + title="Collapse" + /> + </ArrowExpander> + <span + className="object-label" + > + root + </span> + </div> + </ObjectInspectorItem> + </div> + </TreeNode> + <TreeNode + active={false} + depth={1} + expanded={false} + focused={false} + id="window" + index={1} + isExpandable={true} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + key="window-inactive" + onClick={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-expanded={false} + aria-level={2} + className="tree-node" + data-expandable={true} + id="window" + onClick={[Function]} + onKeyDownCapture={null} + role="treeitem" + > + <span + className="tree-indent tree-last-indent" + > + ​ + </span> + <ObjectInspectorItem + addWatchpoint={[Function]} + arrow={ + <ArrowExpander + expanded={false} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + /> + } + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + depth={1} + dimTopLevelWindow={true} + evaluations={Map {}} + expanded={false} + expandedPaths={ + Set { + "root", + } + } + focused={false} + injectWaitService={true} + invokeGetter={[Function]} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + loadedProperties={Map {}} + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + onContextMenu={[Function]} + removeWatchpoint={[Function]} + renderItemActions={[Function]} + roots={ + Array [ + Object { + "contents": Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ], + "meta": undefined, + "name": "root", + "parent": undefined, + "path": "root", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + rootsChanged={[Function]} + setExpanded={[Function]} + > + <div + className="node object-node" + onClick={[Function]} + onContextMenu={[Function]} + > + <ArrowExpander + expanded={false} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + > + <button + className="arrow" + title="Expand" + /> + </ArrowExpander> + <span + className="object-label" + > + window + </span> + <span + className="object-delimiter" + > + : + </span> + <span + className="objectBox objectBox-Window" + data-link-actor-id="server0.conn0.windowGlobal2147483651/obj35" + title={null} + > + <span + className="objectTitle" + > + Window + </span> + </span> + </div> + </ObjectInspectorItem> + </div> + </TreeNode> + </div> + </Tree> + </ObjectInspector> + </Connect(ObjectInspector)> + </Component> +</Provider> +`; + +exports[`ObjectInspector - dimTopLevelWindow renders window as expected when dimTopLevelWindow is true 1`] = ` +<Provider + store={ + Object { + "dispatch": [Function], + "getState": [Function], + "replaceReducer": [Function], + "subscribe": [Function], + Symbol(observable): [Function], + } + } +> + <Component + autoExpandDepth={0} + dimTopLevelWindow={true} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + > + <Connect(ObjectInspector) + autoExpandDepth={0} + dimTopLevelWindow={true} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + > + <ObjectInspector + addWatchpoint={[Function]} + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + dimTopLevelWindow={true} + evaluations={Map {}} + expandedPaths={Set {}} + invokeGetter={[Function]} + loadedProperties={Map {}} + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + removeWatchpoint={[Function]} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + rootsChanged={[Function]} + > + <Tree + autoExpandAll={true} + autoExpandDepth={0} + className="object-inspector" + getChildren={[Function]} + getKey={[Function]} + getParent={[Function]} + getRoots={[Function]} + isExpandable={[Function]} + isExpanded={[Function]} + onActivate={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + onFocus={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + className="tree object-inspector" + onBlur={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyPress={[Function]} + onKeyUp={[Function]} + role="tree" + style={Object {}} + tabIndex="0" + > + <TreeNode + active={false} + depth={0} + expanded={false} + focused={false} + id="window" + index={0} + isExpandable={true} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + key="window-inactive" + onClick={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-expanded={false} + aria-level={1} + className="tree-node" + data-expandable={true} + id="window" + onClick={[Function]} + onKeyDownCapture={null} + role="treeitem" + > + <ObjectInspectorItem + addWatchpoint={[Function]} + arrow={ + <ArrowExpander + expanded={false} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + /> + } + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + depth={0} + dimTopLevelWindow={true} + evaluations={Map {}} + expanded={false} + expandedPaths={Set {}} + focused={false} + invokeGetter={[Function]} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + loadedProperties={Map {}} + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + onContextMenu={[Function]} + removeWatchpoint={[Function]} + renderItemActions={[Function]} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + rootsChanged={[Function]} + setExpanded={[Function]} + > + <div + className="node object-node lessen" + onClick={[Function]} + onContextMenu={[Function]} + > + <ArrowExpander + expanded={false} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + > + <button + className="arrow" + title="Expand" + /> + </ArrowExpander> + <span + className="object-label" + > + window + </span> + <span + className="object-delimiter" + > + : + </span> + <span + className="objectBox objectBox-Window" + data-link-actor-id="server0.conn0.windowGlobal2147483651/obj35" + title={null} + > + <span + className="objectTitle" + > + Window + </span> + </span> + </div> + </ObjectInspectorItem> + </div> + </TreeNode> + </div> + </Tree> + </ObjectInspector> + </Connect(ObjectInspector)> + </Component> +</Provider> +`; + +exports[`ObjectInspector - dimTopLevelWindow renders window as expected when dimTopLevelWindow is true 2`] = ` +<Provider + store={ + Object { + "dispatch": [Function], + "getState": [Function], + "replaceReducer": [Function], + "subscribe": [Function], + Symbol(observable): [Function], + } + } +> + <Component + autoExpandDepth={0} + dimTopLevelWindow={true} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + > + <Connect(ObjectInspector) + autoExpandDepth={0} + dimTopLevelWindow={true} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + > + <ObjectInspector + addWatchpoint={[Function]} + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + dimTopLevelWindow={true} + evaluations={Map {}} + expandedPaths={ + Set { + "window", + } + } + invokeGetter={[Function]} + loadedProperties={ + Map { + "window" => Object { + "ownProperties": Object {}, + "prototype": Object {}, + }, + } + } + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + removeWatchpoint={[Function]} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + rootsChanged={[Function]} + > + <Tree + autoExpandAll={true} + autoExpandDepth={0} + className="object-inspector" + focused={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + getChildren={[Function]} + getKey={[Function]} + getParent={[Function]} + getRoots={[Function]} + isExpandable={[Function]} + isExpanded={[Function]} + onActivate={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + onFocus={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-activedescendant="window" + className="tree object-inspector" + onBlur={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyPress={[Function]} + onKeyUp={[Function]} + role="tree" + style={Object {}} + tabIndex="0" + > + <TreeNode + active={false} + depth={0} + expanded={true} + focused={true} + id="window" + index={0} + isExpandable={true} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + key="window-inactive" + onClick={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-expanded={true} + aria-level={1} + className="tree-node focused" + data-expandable={true} + id="window" + onClick={[Function]} + onKeyDownCapture={null} + role="treeitem" + > + <ObjectInspectorItem + addWatchpoint={[Function]} + arrow={ + <ArrowExpander + expanded={true} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + /> + } + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + depth={0} + dimTopLevelWindow={true} + evaluations={Map {}} + expanded={true} + expandedPaths={ + Set { + "window", + } + } + focused={true} + invokeGetter={[Function]} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + loadedProperties={Map {}} + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + onContextMenu={[Function]} + removeWatchpoint={[Function]} + renderItemActions={[Function]} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + rootsChanged={[Function]} + setExpanded={[Function]} + > + <div + className="node object-node focused" + onClick={[Function]} + onContextMenu={[Function]} + > + <ArrowExpander + expanded={true} + item={ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + } + } + > + <button + className="arrow expanded" + title="Collapse" + /> + </ArrowExpander> + <span + className="object-label" + > + window + </span> + <span + className="object-delimiter" + > + : + </span> + <span + className="objectBox objectBox-Window" + data-link-actor-id="server0.conn0.windowGlobal2147483651/obj35" + title={null} + > + <span + className="objectTitle" + > + Window + </span> + </span> + </div> + </ObjectInspectorItem> + </div> + </TreeNode> + <TreeNode + active={false} + depth={1} + expanded={false} + focused={false} + id="window◦<prototype>" + index={1} + isExpandable={false} + item={ + Object { + "contents": Object { + "front": null, + "value": Object {}, + }, + "meta": undefined, + "name": "<prototype>", + "parent": Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + "path": "window◦<prototype>", + "propertyName": undefined, + "type": Symbol(<prototype>), + } + } + key="window◦<prototype>-inactive" + onClick={[Function]} + onCollapse={[Function]} + onExpand={[Function]} + renderItem={[Function]} + shouldItemUpdate={[Function]} + > + <div + aria-level={2} + className="tree-node" + data-expandable={false} + id="window◦<prototype>" + onClick={[Function]} + onKeyDownCapture={null} + role="treeitem" + > + <span + className="tree-indent tree-last-indent" + > + ​ + </span> + <ObjectInspectorItem + addWatchpoint={[Function]} + arrow={null} + autoExpandDepth={0} + autoReleaseObjectActors={true} + closeObjectInspector={[Function]} + depth={1} + dimTopLevelWindow={true} + evaluations={Map {}} + expanded={false} + expandedPaths={ + Set { + "window", + } + } + focused={false} + invokeGetter={[Function]} + item={ + Object { + "contents": Object { + "front": null, + "value": Object {}, + }, + "meta": undefined, + "name": "<prototype>", + "parent": Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + "path": "window◦<prototype>", + "propertyName": undefined, + "type": Symbol(<prototype>), + } + } + loadedProperties={ + Map { + "window" => Object { + "ownProperties": Object {}, + "prototype": Object {}, + }, + } + } + nodeCollapse={[Function]} + nodeExpand={[Function]} + nodeLoadProperties={[Function]} + nodePropertiesLoaded={[Function]} + onContextMenu={[Function]} + removeWatchpoint={[Function]} + renderItemActions={[Function]} + roots={ + Array [ + Object { + "contents": Object { + "value": Object { + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "extensible": true, + "frozen": false, + "isError": false, + "ownPropertyLength": 806, + "preview": Object { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation", + }, + "sealed": false, + "type": "object", + }, + }, + "meta": undefined, + "name": "window", + "parent": undefined, + "path": "window", + "propertyName": undefined, + "type": Symbol(GRIP), + }, + ] + } + rootsChanged={[Function]} + setExpanded={[Function]} + > + <div + className="node object-node lessen" + onClick={[Function]} + onContextMenu={[Function]} + > + <span + className="object-label" + > + <prototype> + </span> + <span + className="object-delimiter" + > + : + </span> + <span + className="objectBox objectBox-object" + title={null} + > + <span + className="objectLeftBrace" + > + { + </span> + <span + className="objectRightBrace" + > + } + </span> + </span> + </div> + </ObjectInspectorItem> + </div> + </TreeNode> + </div> + </Tree> + </ObjectInspector> + </Connect(ObjectInspector)> + </Component> +</Provider> +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/basic.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/basic.test.js new file mode 100644 index 0000000000..47a919db02 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/basic.test.js @@ -0,0 +1,439 @@ +/* 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("devtools/client/shared/components/test/node/components/object-inspector/test-utils"); +const { mount } = require("enzyme"); +const { + createNode, + NODE_TYPES, +} = require("devtools/client/shared/components/object-inspector/utils/node"); + +const { Rep } = require(`devtools/client/shared/components/reps/reps/rep`); +const { + MODE, +} = require(`devtools/client/shared/components/reps/reps/constants`); +const { + formatObjectInspector, + waitForDispatch, + waitForLoadedProperties, +} = require("devtools/client/shared/components/test/node/components/object-inspector/test-utils"); +const ObjectFront = require("devtools/client/shared/components/test/node/__mocks__/object-front"); +const gripRepStubs = require(`devtools/client/shared/components/test/node/stubs/reps/grip`); + +function generateDefaults(overrides) { + return { + autoExpandDepth: 0, + ...overrides, + }; +} + +function mountOI(props, { initialState } = {}) { + const client = { + createObjectFront: grip => ObjectFront(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, ["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); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/classnames.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/classnames.test.js new file mode 100644 index 0000000000..9f93d8fb70 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/classnames.test.js @@ -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/>. */ + +const ObjectFront = require("resource://devtools/client/shared/components/test/node/__mocks__/object-front.js"); +const { + mountObjectInspector, +} = require("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); + +function generateDefaults(overrides) { + return { + autoExpandDepth: 0, + roots: [ + { + path: "root", + name: "root", + contents: { value: 42 }, + }, + ], + ...overrides, + }; +} + +function mount(props) { + const client = { createObjectFront: grip => ObjectFront(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(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/create-long-string-front.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/create-long-string-front.test.js new file mode 100644 index 0000000000..40e2fd772a --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/create-long-string-front.test.js @@ -0,0 +1,94 @@ +/* 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("devtools/client/shared/components/test/node/components/object-inspector/test-utils"); +const ObjectFront = require("devtools/client/shared/components/test/node/__mocks__/object-front"); +const { + LongStringFront, +} = require("devtools/client/shared/components/test/node/__mocks__/string-front"); + +const longStringStubs = require(`devtools/client/shared/components/test/node/stubs/reps/long-string`); + +function mount(props) { + const substring = jest.fn(() => Promise.resolve("")); + + const client = { + createObjectFront: grip => ObjectFront(grip), + createLongStringFront: jest.fn(grip => + LongStringFront(grip, { substring }) + ), + }; + + const obj = mountObjectInspector({ + client, + props, + }); + + return { ...obj, substring }; +} + +describe("createLongStringFront", () => { + it("is called with the expected object for longString node", () => { + const stub = longStringStubs.get("testMultiline"); + + const { client } = mount({ + autoExpandDepth: 1, + roots: [ + { + path: "root", + contents: { + value: stub, + }, + }, + ], + }); + + expect(client.createLongStringFront.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, + }, + }, + ], + }); + + expect(substring.mock.calls[0]).toHaveLength(2); + 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); + }); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/create-object-client.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/create-object-client.test.js new file mode 100644 index 0000000000..f969debfb7 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/create-object-client.test.js @@ -0,0 +1,114 @@ +/* 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("devtools/client/shared/components/test/node/components/object-inspector/test-utils"); +const ObjectFront = require("devtools/client/shared/components/test/node/__mocks__/object-front"); + +const { + createNode, + makeNodesForEntries, + makeNumericalBuckets, +} = require("devtools/client/shared/components/object-inspector/utils/node"); + +const gripRepStubs = require(`devtools/client/shared/components/test/node/stubs/reps/grip`); +const gripArrayRepStubs = require(`devtools/client/shared/components/test/node/stubs/reps/grip-array`); + +function mount(props, overrides = {}) { + const client = { + createObjectFront: + overrides.createObjectFront || jest.fn(grip => ObjectFront(grip)), + getFrontByID: _id => null, + }; + + return mountObjectInspector({ + client, + props, + }); +} + +describe("createObjectFront", () => { + 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.createObjectFront.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.createObjectFront.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.createObjectFront.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.createObjectFront.mock.calls[0][0]).toBe(grip); + }); + + it("doesn't fail when ObjectFront 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 createObjectFront = x => ({}); + mount( + { + autoExpandDepth: 1, + roots: [root], + }, + { createObjectFront } + ); + + // rollback console.error. + console.error = originalConsoleError; + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/entries.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/entries.test.js new file mode 100644 index 0000000000..0cb896d3ae --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/entries.test.js @@ -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("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { + formatObjectInspector, + waitForDispatch, + waitForLoadedProperties, +} = require("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); + +const gripMapRepStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); +const mapStubs = require("resource://devtools/client/shared/components/test/node/stubs/object-inspector/map.js"); +const ObjectFront = require("resource://devtools/client/shared/components/test/node/__mocks__/object-front.js"); + +function generateDefaults(overrides) { + return { + autoExpandDepth: 0, + createObjectFront: grip => ObjectFront(grip), + ...overrides, + }; +} + +function getEnumEntriesMock() { + return jest.fn(() => ({ + slice: () => mapStubs.get("11-entries"), + })); +} + +function mount(props, { initialState }) { + const enumEntries = getEnumEntriesMock(); + + const client = { + createObjectFront: grip => ObjectFront(grip, { enumEntries }), + getFrontByID: _id => null, + }; + 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, [ + "root◦<entries>◦0", + "root◦<entries>◦1", + ]); + + wrapper.update(); + expect(formatObjectInspector(wrapper)).toMatchSnapshot(); + }); + + it("calls ObjectFront.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); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/events.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/events.test.js new file mode 100644 index 0000000000..e6483dbefb --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/events.test.js @@ -0,0 +1,171 @@ +/* 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("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); + +const gripRepStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); +const ObjectFront = require("resource://devtools/client/shared/components/test/node/__mocks__/object-front.js"); + +function generateDefaults(overrides) { + return { + autoExpandDepth: 0, + ...overrides, + }; +} + +function mount(props) { + const client = { createObjectFront: grip => ObjectFront(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(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/expand.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/expand.test.js new file mode 100644 index 0000000000..3243d8c259 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/expand.test.js @@ -0,0 +1,435 @@ +/* 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("devtools/client/shared/components/test/node/components/object-inspector/test-utils"); + +const { MODE } = require(`devtools/client/shared/components/reps/index`); +const ObjectFront = require("devtools/client/shared/components/test/node/__mocks__/object-front"); +const gripRepStubs = require(`devtools/client/shared/components/test/node/stubs/reps/grip`); +const gripPropertiesStubs = require("devtools/client/shared/components/test/node/stubs/object-inspector/grip"); +const { + formatObjectInspector, + storeHasExactExpandedPaths, + storeHasExpandedPath, + storeHasLoadedProperty, + waitForDispatch, +} = require("devtools/client/shared/components/test/node/components/object-inspector/test-utils"); +const { + createNode, + NODE_TYPES, +} = require("devtools/client/shared/components/object-inspector/utils/node"); +const { + getExpandedPaths, +} = require("devtools/client/shared/components/object-inspector/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"), + }, + }, + ], + createObjectFront: grip => ObjectFront(grip), + mode: MODE.LONG, + ...overrides, + }; +} +const { + LongStringFront, +} = require("devtools/client/shared/components/test/node/__mocks__/string-front"); + +function getClient(overrides = {}) { + return { + releaseActor: () => {}, + + createObjectFront: grip => + ObjectFront(grip, { + getPrototype: () => Promise.resolve(protoStub), + getProxySlots: () => + Promise.resolve(gripRepStubs.get("testProxySlots")), + }), + + createLongStringFront: grip => + LongStringFront(grip, { + substring: async function(initiaLength, length) { + return "<<<<"; + }, + }), + + getFrontByID: _id => null, + + ...overrides, + }; +} + +function mount(props, { initialState, client = getClient() } = {}) { + 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", + "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(); + 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, "root-1◦<prototype>")).toBeTruthy(); + }); + + it("does not handle actors when client does not have releaseActor function", async () => { + const { wrapper, store } = mount( + {}, + { client: getClient({ releaseActor: null }) } + ); + + 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(); + 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, "root-1◦<prototype>")).toBeTruthy(); + }); + + it.skip("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(); + + 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, "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(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/function.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/function.test.js new file mode 100644 index 0000000000..0e5bb9573a --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/function.test.js @@ -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 { + mountObjectInspector, +} = require("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { + createNode, +} = require("resource://devtools/client/shared/components/object-inspector/utils/node.js"); + +const functionStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/function.js"); +const ObjectFront = require("resource://devtools/client/shared/components/test/node/__mocks__/object-front.js"); + +function generateDefaults(overrides) { + return { + autoExpandDepth: 1, + ...overrides, + }; +} + +function mount(props) { + const client = { + createObjectFront: grip => ObjectFront(grip), + getFrontByID: _id => null, + }; + + 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()"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/getter-setter.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/getter-setter.test.js new file mode 100644 index 0000000000..e5fbbff7de --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/getter-setter.test.js @@ -0,0 +1,106 @@ +/* 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("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { + formatObjectInspector, + waitForLoadedProperties, + mountObjectInspector, +} = require("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); + +const { + makeNodesForProperties, +} = require("resource://devtools/client/shared/components/object-inspector/utils/node.js"); +const accessorStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/accessor.js"); +const ObjectFront = require("resource://devtools/client/shared/components/test/node/__mocks__/object-front.js"); + +function generateDefaults(overrides) { + return { + autoExpandDepth: 1, + createObjectFront: grip => ObjectFront(grip), + mode: MODE.LONG, + ...overrides, + }; +} + +function mount(stub, propsOverride = {}) { + const client = { createObjectFront: grip => ObjectFront(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(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/keyboard-navigation.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/keyboard-navigation.test.js new file mode 100644 index 0000000000..c36c611a53 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/keyboard-navigation.test.js @@ -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/>. */ + +const { + mountObjectInspector, +} = require("devtools/client/shared/components/test/node/components/object-inspector/test-utils"); +const { MODE } = require("devtools/client/shared/components/reps/index"); + +const { + formatObjectInspector, + waitForDispatch, +} = require("devtools/client/shared/components/test/node/components/object-inspector/test-utils"); +const ObjectFront = require("devtools/client/shared/components/test/node/__mocks__/object-front"); +const gripRepStubs = require(`devtools/client/shared/components/test/node/stubs/reps/grip`); + +function generateDefaults(overrides) { + return { + autoExpandDepth: 0, + mode: MODE.LONG, + ...overrides, + }; +} + +function mount(props) { + const client = { + createObjectFront: grip => ObjectFront(grip), + getFrontByID: _id => null, + }; + + 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: () => {}, + }); +} diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/properties.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/properties.test.js new file mode 100644 index 0000000000..8773cfd49f --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/properties.test.js @@ -0,0 +1,158 @@ +/* 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("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); +const gripRepStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); +const ObjectFront = require("resource://devtools/client/shared/components/test/node/__mocks__/object-front.js"); + +const { + formatObjectInspector, +} = require("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); + +function generateDefaults(overrides) { + return { + autoExpandDepth: 0, + createObjectFront: grip => ObjectFront(grip), + ...overrides, + }; +} + +function getEnumPropertiesMock() { + return jest.fn(() => ({ + slice: () => ({}), + })); +} + +function mount(props, { initialState } = {}) { + const enumProperties = getEnumPropertiesMock(); + + const client = { + createObjectFront: grip => ObjectFront(grip, { enumProperties }), + getFrontByID: _id => null, + }; + + 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, + }, + }, + ], + createObjectFront: grip => ObjectFront(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(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/proxy.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/proxy.test.js new file mode 100644 index 0000000000..0bf716ccff --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/proxy.test.js @@ -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/>. */ + +/* global jest */ +const { + mountObjectInspector, +} = require("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); + +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const gripStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); +const stub = gripStubs.get("testProxy"); +const proxySlots = gripStubs.get("testProxySlots"); +const { + formatObjectInspector, +} = require("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); + +const ObjectFront = require("resource://devtools/client/shared/components/test/node/__mocks__/object-front.js"); +function generateDefaults(overrides) { + return { + roots: [ + { + path: "root", + contents: { + value: stub, + }, + }, + ], + autoExpandDepth: 1, + mode: MODE.LONG, + ...overrides, + }; +} + +function getEnumPropertiesMock() { + return jest.fn(() => ({ + slice: () => ({}), + })); +} + +function getProxySlotsMock() { + return jest.fn(() => proxySlots); +} + +function mount(props, { initialState } = {}) { + const enumProperties = getEnumPropertiesMock(); + const getProxySlots = getProxySlotsMock(); + + const client = { + createObjectFront: grip => + ObjectFront(grip, { enumProperties, getProxySlots }), + getFrontByID: _id => null, + }; + + const obj = mountObjectInspector({ + client, + props: generateDefaults(props), + initialState, + }); + + return { ...obj, enumProperties, getProxySlots }; +} + +describe("ObjectInspector - Proxy", () => { + it("renders Proxy as expected", () => { + const { wrapper, enumProperties, getProxySlots } = mount( + {}, + { + initialState: { + objectInspector: { + // Have the prototype already loaded so the component does not call + // enumProperties for the root's properties. + loadedProperties: new Map([["root", proxySlots]]), + evaluations: new Map(), + }, + }, + } + ); + + expect(formatObjectInspector(wrapper)).toMatchSnapshot(); + + // enumProperties should not have been called. + expect(enumProperties.mock.calls).toHaveLength(0); + + // getProxySlots should not have been called. + expect(getProxySlots.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", proxySlots]]), + 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, + }); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/should-item-update.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/should-item-update.test.js new file mode 100644 index 0000000000..645b4ede6c --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/should-item-update.test.js @@ -0,0 +1,96 @@ +/* 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("devtools/client/shared/components/test/node/components/object-inspector/test-utils"); +const ObjectFront = require("devtools/client/shared/components/test/node/__mocks__/object-front"); +const { + LongStringFront, +} = require("devtools/client/shared/components/test/node/__mocks__/string-front"); + +const longStringStubs = require(`devtools/client/shared/components/test/node/stubs/reps/long-string`); +const gripStubs = require(`devtools/client/shared/components/test/node/stubs/reps/grip`); + +function mount(stub) { + const root = { + path: "root", + contents: { + value: stub, + }, + }; + + const { wrapper } = mountObjectInspector({ + client: { + createObjectFront: grip => ObjectFront(grip), + createLongStringFront: grip => LongStringFront(grip), + getFrontByID: _id => null, + }, + props: { + roots: [root], + }, + }); + + return { wrapper, root }; +} + +describe("shouldItemUpdate", () => { + it("for longStrings", () => { + shouldItemUpdateCheck(longStringStubs.get("testUnloadedFullText"), true, 2); + }); + + it("for basic object", () => { + shouldItemUpdateCheck(gripStubs.get("testBasic"), false, 1); + }); +}); + +function shouldItemUpdateCheck( + stub, + shouldItemUpdateResult, + renderCallsLength +) { + const { root, wrapper } = mount(stub); + + const shouldItemUpdateSpy = getShouldItemUpdateSpy(wrapper); + const treeNodeRenderSpy = getTreeNodeRenderSpy(wrapper); + + updateObjectInspectorTree(wrapper); + + checkShouldItemUpdate(shouldItemUpdateSpy, root, shouldItemUpdateResult); + expect(treeNodeRenderSpy.mock.calls).toHaveLength(renderCallsLength); +} + +function checkShouldItemUpdate(spy, item, result) { + expect(spy.mock.calls).toHaveLength(1); + expect(spy.mock.calls[0][0]).toBe(item); + expect(spy.mock.calls[0][1]).toBe(item); + expect(spy.mock.results[0].value).toBe(result); +} + +function getInstance(wrapper, selector) { + return wrapper + .find(selector) + .first() + .instance(); +} + +function getShouldItemUpdateSpy(wrapper) { + return jest.spyOn( + getInstance(wrapper, "ObjectInspector"), + "shouldItemUpdate" + ); +} + +function getTreeNodeRenderSpy(wrapper) { + return jest.spyOn(getInstance(wrapper, "TreeNode"), "render"); +} + +function updateObjectInspectorTree(wrapper) { + // Update the ObjectInspector first to propagate its updated options to the + // Tree component. + getInstance(wrapper, "ObjectInspector").forceUpdate(); + getInstance(wrapper, "Tree").forceUpdate(); +} diff --git a/devtools/client/shared/components/test/node/components/object-inspector/component/window.test.js b/devtools/client/shared/components/test/node/components/object-inspector/component/window.test.js new file mode 100644 index 0000000000..c0b1036385 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/component/window.test.js @@ -0,0 +1,96 @@ +/* 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("resource://devtools/client/shared/components/object-inspector/utils/node.js"); +const { + waitForDispatch, + mountObjectInspector, +} = require("resource://devtools/client/shared/components/test/node/components/object-inspector/test-utils.js"); + +const gripWindowStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/window.js"); +const ObjectFront = require("resource://devtools/client/shared/components/test/node/__mocks__/object-front.js"); +const windowNode = createNode({ + name: "window", + contents: { value: gripWindowStubs.get("Window")._grip }, +}); + +const client = { + createObjectFront: grip => ObjectFront(grip), + getFrontByID: _id => null, +}; + +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(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/test-utils.js b/devtools/client/shared/components/test/node/components/object-inspector/test-utils.js new file mode 100644 index 0000000000..79d3e41161 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/test-utils.js @@ -0,0 +1,231 @@ +/* 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 { mount } = require("enzyme"); +const { createFactory } = require("resource://devtools/client/shared/vendor/react.js"); + +const { Provider } = require("resource://devtools/client/shared/vendor/react-redux.js"); +const { + combineReducers, + createStore, + applyMiddleware, +} = require("resource://devtools/client/shared/vendor/redux.js"); + +const { thunk } = require("resource://devtools/client/shared/redux/middleware/thunk.js"); +const { + waitUntilService, +} = require("resource://devtools/client/shared/redux/middleware/wait-service.js"); + +/** + * Redux store utils + * @module utils/create-store + */ +const objectInspector = require("resource://devtools/client/shared/components/object-inspector/index.js"); +const { + getLoadedProperties, + getLoadedPropertyKeys, + getExpandedPaths, + getExpandedPathKeys, +} = require("resource://devtools/client/shared/components/object-inspector/reducer.js"); + +const ObjectInspector = createFactory(objectInspector.ObjectInspector); + +const { + NAME: WAIT_UNTIL_TYPE, +} = require("resource://devtools/client/shared/redux/middleware/wait-service.js"); + +/* + * Takes an Enzyme wrapper (obtained with mount/shallow/…) and + * returns a stringified version of the ObjectInspector, e.g. + * + * ▼ Map { "a" → "value-a", "b" → "value-b" } + * | size : 2 + * | ▼ <entries> + * | | ▼ 0 : "a" → "value-a" + * | | | <key> : "a" + * | | | <value> : "value-a" + * | | ▼ 1 : "b" → "value-b" + * | | | <key> : "b" + * | | | <value> : "value-b" + * | ▼ <prototype> : Object { … } + * + */ +function formatObjectInspector(wrapper) { + 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, type) { + 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, interval = 50, maxTries = 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, expectedKeys, interval, maxTries) { + return waitFor( + () => storeHasLoadedPropertiesKeys(store, expectedKeys), + interval, + maxTries + ); +} + +function storeHasLoadedPropertiesKeys(store, expectedKeys) { + return expectedKeys.every(key => storeHasLoadedProperty(store, key)); +} + +function storeHasLoadedProperty(store, key) { + return getLoadedPropertyKeys(store.getState()).some( + k => k.toString() === key + ); +} + +function storeHasExactLoadedProperties(store, expectedKeys) { + return ( + expectedKeys.length === getLoadedProperties(store.getState()).size && + expectedKeys.every(key => storeHasLoadedProperty(store, key)) + ); +} + +function storeHasExpandedPaths(store, expectedKeys) { + return expectedKeys.every(key => storeHasExpandedPath(store, key)); +} + +function storeHasExpandedPath(store, key) { + return getExpandedPathKeys(store.getState()).some(k => k.toString() === key); +} + +function storeHasExactExpandedPaths(store, expectedKeys) { + return ( + expectedKeys.length === getExpandedPaths(store.getState()).size && + expectedKeys.every(key => storeHasExpandedPath(store, key)) + ); +} + +function createOiStore(client, initialState = {}) { + const reducers = { objectInspector: objectInspector.reducer.default }; + return configureStore({ + thunkArgs: { client }, + })(combineReducers(reducers), initialState); +} + +const configureStore = (opts = {}) => { + 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(), + ...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, +}; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/__snapshots__/promises.test.js.snap b/devtools/client/shared/components/test/node/components/object-inspector/utils/__snapshots__/promises.test.js.snap new file mode 100644 index 0000000000..75903c0ff1 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/__snapshots__/promises.test.js.snap @@ -0,0 +1,49 @@ +// 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/obj36", + "class": "Promise", + "type": "object", + }, + }, + "path": "root", + }, + "path": "root◦<state>", + "propertyName": undefined, + "type": Symbol(<state>), + }, + Object { + "contents": Object { + "front": null, + "value": Object { + "type": "3", + }, + }, + "meta": undefined, + "name": "<reason>", + "parent": Object { + "contents": Object { + "value": Object { + "actor": "server2.conn2.child1/obj36", + "class": "Promise", + "type": "object", + }, + }, + "path": "root", + }, + "path": "root◦<reason>", + "propertyName": undefined, + "type": Symbol(<reason>), + }, +] +`; diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/create-node.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/create-node.test.js new file mode 100644 index 0000000000..792ad2dfb0 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/create-node.test.js @@ -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("resource://devtools/client/shared/components/object-inspector/utils/node.js"); + +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("name"); + }); + + it("wraps the path in a Symbol when provided", () => { + expect( + createNode({ + name: "name", + path: "path", + contents: "contents", + }).path.toString() + ).toBe("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("root◦path"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/get-children.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/get-children.test.js new file mode 100644 index 0000000000..f57f82073d --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/get-children.test.js @@ -0,0 +1,278 @@ +/* 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 accessorStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/accessor.js"); +const performanceStubs = require("resource://devtools/client/shared/components/test/node/stubs/object-inspector/performance.js"); +const gripMapStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const gripEntryStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-entry.js"); +const gripStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); + +const { + createNode, + getChildren, + getValue, + makeNodesForProperties, +} = require("resource://devtools/client/shared/components/object-inspector/utils/node.js"); + +function createRootNodeWithAccessorProperty(accessorStub) { + const node = { name: "root", path: "rootpath" }; + const nodes = makeNodesForProperties( + { + ownProperties: { + x: accessorStub, + }, + }, + node + ); + node.contents = nodes; + + return createNode(node); +} + +describe("getChildren", () => { + it("accessors - getter", () => { + const children = getChildren({ + item: createRootNodeWithAccessorProperty(accessorStubs.get("getter")), + }); + + const names = children.map(n => n.name); + const paths = children.map(n => n.path.toString()); + + expect(names).toEqual(["x", "<get x()>"]); + expect(paths).toEqual(["rootpath◦x", "rootpath◦<get x()>"]); + }); + + it("accessors - setter", () => { + const children = getChildren({ + item: createRootNodeWithAccessorProperty(accessorStubs.get("setter")), + }); + + const names = children.map(n => n.name); + const paths = children.map(n => n.path.toString()); + + expect(names).toEqual(["x", "<set x()>"]); + expect(paths).toEqual(["rootpath◦x", "rootpath◦<set x()>"]); + }); + + it("accessors - getter & setter", () => { + const children = getChildren({ + item: createRootNodeWithAccessorProperty( + accessorStubs.get("getter setter") + ), + }); + + const names = children.map(n => n.name); + const paths = children.map(n => n.path.toString()); + + expect(names).toEqual(["x", "<get x()>", "<set x()>"]); + expect(paths).toEqual([ + "rootpath◦x", + "rootpath◦<get x()>", + "rootpath◦<set x()>", + ]); + }); + + it("returns the expected nodes for Proxy", () => { + const proxyNode = createNode({ + name: "root", + path: "rootpath", + contents: { value: gripStubs.get("testProxy") }, + }); + const loadedProperties = new Map([ + [proxyNode.path, gripStubs.get("testProxySlots")], + ]); + const nodes = getChildren({ item: proxyNode, loadedProperties }); + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path.toString()); + + expect(names).toEqual(["<target>", "<handler>"]); + expect(paths).toEqual(["rootpath◦<target>", "rootpath◦<handler>"]); + }); + + it("safeGetterValues", () => { + const stub = performanceStubs.get("timing"); + const root = createNode({ + name: "root", + path: "rootpath", + contents: { + value: { + actor: "rootactor", + type: "object", + }, + }, + }); + const nodes = getChildren({ + item: root, + loadedProperties: new Map([[root.path, stub]]), + }); + + const nodeEntries = nodes.map(n => [n.name, getValue(n)]); + const nodePaths = nodes.map(n => n.path.toString()); + + const childrenEntries = [ + ["connectEnd", 1500967716401], + ["connectStart", 1500967716401], + ["domComplete", 1500967716719], + ["domContentLoadedEventEnd", 1500967716715], + ["domContentLoadedEventStart", 1500967716696], + ["domInteractive", 1500967716552], + ["domLoading", 1500967716426], + ["domainLookupEnd", 1500967716401], + ["domainLookupStart", 1500967716401], + ["fetchStart", 1500967716401], + ["loadEventEnd", 1500967716720], + ["loadEventStart", 1500967716719], + ["navigationStart", 1500967716401], + ["redirectEnd", 0], + ["redirectStart", 0], + ["requestStart", 1500967716401], + ["responseEnd", 1500967716401], + ["responseStart", 1500967716401], + ["secureConnectionStart", 1500967716401], + ["unloadEventEnd", 0], + ["unloadEventStart", 0], + ["<prototype>", stub.prototype], + ]; + const childrenPaths = childrenEntries.map(([name]) => `rootpath◦${name}`); + + expect(nodeEntries).toEqual(childrenEntries); + expect(nodePaths).toEqual(childrenPaths); + }); + + it("gets data from the cache when it exists", () => { + const mapNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("testSymbolKeyedMap"), + }, + }); + const cachedData = ""; + const children = getChildren({ + cachedNodes: new Map([[mapNode.path, cachedData]]), + item: mapNode, + }); + expect(children).toBe(cachedData); + }); + + it("returns an empty array if the node does not represent an object", () => { + const node = createNode({ name: "root", contents: { value: 42 } }); + expect( + getChildren({ + item: node, + }) + ).toEqual([]); + }); + + it("returns an empty array if a grip node has no loaded properties", () => { + const node = createNode({ + name: "root", + contents: { value: gripMapStubs.get("testMaxProps") }, + }); + expect( + getChildren({ + item: node, + }) + ).toEqual([]); + }); + + it("adds children to cache when a grip node has loaded properties", () => { + const stub = performanceStubs.get("timing"); + const cachedNodes = new Map(); + + const rootNode = createNode({ + name: "root", + contents: { + value: { + actor: "rootactor", + type: "object", + }, + }, + }); + const children = getChildren({ + cachedNodes, + item: rootNode, + loadedProperties: new Map([[rootNode.path, stub]]), + }); + expect(cachedNodes.get(rootNode.path)).toBe(children); + }); + + it("adds children to cache when it already has some", () => { + const cachedNodes = new Map(); + const children = [""]; + const rootNode = createNode({ name: "root", contents: children }); + getChildren({ + cachedNodes, + item: rootNode, + }); + expect(cachedNodes.get(rootNode.path)).toBe(children); + }); + + it("adds children to cache on a node with accessors", () => { + const cachedNodes = new Map(); + const node = createRootNodeWithAccessorProperty( + accessorStubs.get("getter setter") + ); + + const children = getChildren({ + cachedNodes, + item: node, + }); + expect(cachedNodes.get(node.path)).toBe(children); + }); + + it("adds children to cache on a map entry node", () => { + const cachedNodes = new Map(); + const node = createNode({ + name: "root", + contents: { value: gripEntryStubs.get("A → 0") }, + }); + const children = getChildren({ + cachedNodes, + item: node, + }); + expect(cachedNodes.get(node.path)).toBe(children); + }); + + it("adds children to cache on a proxy node having loaded props", () => { + const cachedNodes = new Map(); + const node = createNode({ + name: "root", + contents: { value: gripStubs.get("testProxy") }, + }); + const children = getChildren({ + cachedNodes, + item: node, + loadedProperties: new Map([[node.path, gripStubs.get("testProxySlots")]]), + }); + expect(cachedNodes.get(node.path)).toBe(children); + }); + + it("doesn't cache children on node with buckets and no loaded props", () => { + const cachedNodes = new Map(); + const node = createNode({ + name: "root", + contents: { value: gripArrayStubs.get("Array(234)") }, + }); + getChildren({ + cachedNodes, + item: node, + }); + expect(cachedNodes.has(node.path)).toBeFalsy(); + }); + + it("caches children on a node with buckets having loaded props", () => { + const cachedNodes = new Map(); + const node = createNode({ + name: "root", + contents: { value: gripArrayStubs.get("Array(234)") }, + }); + const children = getChildren({ + cachedNodes, + item: node, + loadedProperties: new Map([[node.path, { prototype: {} }]]), + }); + expect(cachedNodes.get(node.path)).toBe(children); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/get-closest-grip-node.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/get-closest-grip-node.test.js new file mode 100644 index 0000000000..9aa7e127a8 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/get-closest-grip-node.test.js @@ -0,0 +1,52 @@ +/* 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, + getClosestGripNode, + makeNodesForEntries, + makeNumericalBuckets, +} = require("devtools/client/shared/components/object-inspector/utils/node"); + +const gripRepStubs = require(`devtools/client/shared/components/test/node/stubs/reps/grip`); +const gripArrayRepStubs = require(`devtools/client/shared/components/test/node/stubs/reps/grip-array`); + +describe("getClosestGripNode", () => { + it("returns grip node itself", () => { + const stub = gripRepStubs.get("testMoreThanMaxProps"); + const node = createNode({ name: "root", contents: { value: stub } }); + expect(getClosestGripNode(node)).toBe(node); + }); + + it("returns the expected node for entries node", () => { + const mapStubNode = createNode({ name: "map", contents: { value: {} } }); + const entriesNode = makeNodesForEntries(mapStubNode); + expect(getClosestGripNode(entriesNode)).toBe(mapStubNode); + }); + + it("returns the expected node for bucket node", () => { + const grip = gripArrayRepStubs.get("testMaxProps"); + const root = createNode({ name: "root", contents: { value: grip } }); + const [bucket] = makeNumericalBuckets(root); + expect(getClosestGripNode(bucket)).toBe(root); + }); + + it("returns the expected node 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); + expect(getClosestGripNode(subBucket)).toBe(root); + }); + + it("returns the expected node for deep sub-bucket node", () => { + const grip = gripArrayRepStubs.get("testMaxProps"); + const root = createNode({ name: "root", contents: { value: grip } }); + let [bucket] = makeNumericalBuckets(root); + for (let i = 0; i < 10; i++) { + bucket = makeNumericalBuckets({ ...bucket })[0]; + } + expect(getClosestGripNode(bucket)).toBe(root); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/get-value.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/get-value.test.js new file mode 100644 index 0000000000..29f0c0ffce --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/get-value.test.js @@ -0,0 +1,91 @@ +/* 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 { getValue } = require("resource://devtools/client/shared/components/object-inspector/utils/node.js"); + +describe("getValue", () => { + it("get the value from contents.value", () => { + let item = { + contents: { + value: "my value", + }, + }; + expect(getValue(item)).toBe("my value"); + + item = { + contents: { + value: 0, + }, + }; + expect(getValue(item)).toBe(0); + + item = { + contents: { + value: false, + }, + }; + expect(getValue(item)).toBe(false); + + item = { + contents: { + value: null, + }, + }; + expect(getValue(item)).toBe(null); + }); + + it("get the value from contents.getterValue", () => { + let item = { + contents: { + getterValue: "my getter value", + }, + }; + expect(getValue(item)).toBe("my getter value"); + + item = { + contents: { + getterValue: 0, + }, + }; + expect(getValue(item)).toBe(0); + + item = { + contents: { + getterValue: false, + }, + }; + expect(getValue(item)).toBe(false); + + item = { + contents: { + getterValue: null, + }, + }; + expect(getValue(item)).toBe(null); + }); + + it("get the value from getter and setter", () => { + let item = { + contents: { + get: "get", + }, + }; + expect(getValue(item)).toEqual({ get: "get" }); + + item = { + contents: { + set: "set", + }, + }; + expect(getValue(item)).toEqual({ set: "set" }); + + item = { + contents: { + get: "get", + set: "set", + }, + }; + expect(getValue(item)).toEqual({ get: "get", set: "set" }); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/make-node-for-properties.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/make-node-for-properties.test.js new file mode 100644 index 0000000000..da0a221531 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/make-node-for-properties.test.js @@ -0,0 +1,295 @@ +/* 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, + makeNodesForProperties, + nodeIsDefaultProperties, + nodeIsEntries, + nodeIsMapEntry, + nodeIsPrototype, +} = require("resource://devtools/client/shared/components/object-inspector/utils/node.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); + +const root = { + path: "root", + contents: { + value: gripArrayStubs.get("testBasic"), + }, +}; + +const objProperties = { + ownProperties: { + "0": { + value: {}, + }, + "2": {}, + length: { + value: 3, + }, + }, + prototype: { + type: "object", + actor: "server2.conn1.child1/obj618", + class: "bla", + }, +}; + +describe("makeNodesForProperties", () => { + it("kitchen sink", () => { + const nodes = makeNodesForProperties(objProperties, root); + + const names = nodes.map(n => n.name); + expect(names).toEqual(["0", "length", "<prototype>"]); + + const paths = nodes.map(n => n.path.toString()); + expect(paths).toEqual(["root◦0", "root◦length", "root◦<prototype>"]); + }); + + it("includes getters and setters", () => { + const nodes = makeNodesForProperties( + { + ownProperties: { + foo: { value: "foo" }, + bar: { + get: { + type: "object", + }, + set: { + type: "undefined", + }, + }, + baz: { + get: { + type: "undefined", + }, + set: { + type: "object", + }, + }, + }, + prototype: { + class: "bla", + }, + }, + root + ); + + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path.toString()); + + expect(names).toEqual([ + "bar", + "baz", + "foo", + "<get bar()>", + "<set baz()>", + "<prototype>", + ]); + + expect(paths).toEqual([ + "root◦bar", + "root◦baz", + "root◦foo", + "root◦<get bar()>", + "root◦<set baz()>", + "root◦<prototype>", + ]); + }); + + it("does not include unrelevant properties", () => { + const nodes = makeNodesForProperties( + { + ownProperties: { + foo: undefined, + bar: null, + baz: {}, + }, + }, + root + ); + + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path); + + expect(names).toEqual([]); + expect(paths).toEqual([]); + }); + + it("sorts keys", () => { + const nodes = makeNodesForProperties( + { + ownProperties: { + bar: { value: {} }, + 1: { value: {} }, + 11: { value: {} }, + 2: { value: {} }, + _bar: { value: {} }, + }, + prototype: { + class: "bla", + }, + }, + root + ); + + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path.toString()); + + expect(names).toEqual(["1", "2", "11", "_bar", "bar", "<prototype>"]); + expect(paths).toEqual([ + "root◦1", + "root◦2", + "root◦11", + "root◦_bar", + "root◦bar", + "root◦<prototype>", + ]); + }); + + it("prototype is included", () => { + const nodes = makeNodesForProperties( + { + ownProperties: { + bar: { value: {} }, + }, + prototype: { value: {}, class: "bla" }, + }, + root + ); + + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path.toString()); + + expect(names).toEqual(["bar", "<prototype>"]); + expect(paths).toEqual(["root◦bar", "root◦<prototype>"]); + + expect(nodeIsPrototype(nodes[1])).toBe(true); + }); + + it("window object", () => { + const nodes = makeNodesForProperties( + { + ownProperties: { + bar: { + value: {}, + get: { type: "function" }, + set: { type: "function" }, + }, + location: { value: {} }, + onload: { + get: { type: "function" }, + set: { type: "function" }, + }, + }, + class: "Window", + }, + { + path: "root", + contents: { value: { class: "Window" } }, + } + ); + + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path); + + expect(names).toEqual([ + "bar", + "<default properties>", + "<get bar()>", + "<set bar()>", + ]); + expect(paths).toEqual([ + "root◦bar", + "root◦<default properties>", + "root◦<get bar()>", + "root◦<set bar()>", + ]); + + const defaultPropertyNode = nodes[1]; + expect(nodeIsDefaultProperties(defaultPropertyNode)).toBe(true); + + const defaultPropNames = defaultPropertyNode.contents.map(n => n.name); + const defaultPropPath = defaultPropertyNode.contents.map(n => n.path); + expect(defaultPropNames).toEqual([ + "location", + "onload", + "<get onload()>", + "<set onload()>", + ]); + expect(defaultPropPath).toEqual([ + "root◦<default properties>◦location", + "root◦<default properties>◦onload", + "root◦<default properties>◦<get onload()>", + "root◦<default properties>◦<set onload()>", + ]); + }); + + it("object with entries", () => { + const gripMapStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); + + const mapNode = createNode({ + name: "map", + path: "root", + contents: { + value: gripMapStubs.get("testSymbolKeyedMap"), + }, + }); + + const nodes = makeNodesForProperties( + { + ownProperties: { + size: { value: 1 }, + custom: { value: "customValue" }, + }, + }, + mapNode + ); + + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path.toString()); + + expect(names).toEqual(["custom", "size", "<entries>"]); + expect(paths).toEqual(["root◦custom", "root◦size", "root◦<entries>"]); + + const entriesNode = nodes[2]; + expect(nodeIsEntries(entriesNode)).toBe(true); + }); + + it("quotes property names", () => { + const nodes = makeNodesForProperties( + { + ownProperties: { + // Numbers are ok. + 332217: { value: {} }, + "needs-quotes": { value: {} }, + unquoted: { value: {} }, + "": { value: {} }, + }, + prototype: { + class: "WindowPrototype", + }, + }, + root + ); + + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path.toString()); + + expect(names).toEqual([ + '""', + "332217", + '"needs-quotes"', + "unquoted", + "<prototype>", + ]); + expect(paths).toEqual([ + 'root◦""', + "root◦332217", + 'root◦"needs-quotes"', + "root◦unquoted", + "root◦<prototype>", + ]); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/make-numerical-buckets.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/make-numerical-buckets.test.js new file mode 100644 index 0000000000..02fb1a3bc5 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/make-numerical-buckets.test.js @@ -0,0 +1,138 @@ +/* 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, + makeNumericalBuckets, +} = require("resource://devtools/client/shared/components/object-inspector/utils/node.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); + +describe("makeNumericalBuckets", () => { + it("handles simple numerical buckets", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(234)"), + }, + }); + const nodes = makeNumericalBuckets(node); + + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path.toString()); + + expect(names).toEqual(["[0…99]", "[100…199]", "[200…233]"]); + + expect(paths).toEqual(["root◦[0…99]", "root◦[100…199]", "root◦[200…233]"]); + }); + + // TODO: Re-enable when we have support for lonely node. + it.skip("does not create a numerical bucket for a single node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(101)"), + }, + }); + const nodes = makeNumericalBuckets(node); + + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path.toString()); + + expect(names).toEqual(["[0…99]", "100"]); + + expect(paths).toEqual(["root◦bucket_0-99", "root◦100"]); + }); + + // TODO: Re-enable when we have support for lonely node. + it.skip("does create a numerical bucket for two node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(234)"), + }, + }); + const nodes = makeNumericalBuckets(node); + + const names = nodes.map(n => n.name); + const paths = nodes.map(n => n.path.toString()); + + expect(names).toEqual(["[0…99]", "[100…101]"]); + + expect(paths).toEqual(["root◦bucket_0-99", "root◦bucket_100-101"]); + }); + + it("creates sub-buckets when needed", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(23456)"), + }, + }); + const nodes = makeNumericalBuckets(node); + const names = nodes.map(n => n.name); + + expect(names).toEqual([ + "[0…999]", + "[1000…1999]", + "[2000…2999]", + "[3000…3999]", + "[4000…4999]", + "[5000…5999]", + "[6000…6999]", + "[7000…7999]", + "[8000…8999]", + "[9000…9999]", + "[10000…10999]", + "[11000…11999]", + "[12000…12999]", + "[13000…13999]", + "[14000…14999]", + "[15000…15999]", + "[16000…16999]", + "[17000…17999]", + "[18000…18999]", + "[19000…19999]", + "[20000…20999]", + "[21000…21999]", + "[22000…22999]", + "[23000…23455]", + ]); + + const firstBucketNodes = makeNumericalBuckets(nodes[0]); + const firstBucketNames = firstBucketNodes.map(n => n.name); + const firstBucketPaths = firstBucketNodes.map(n => n.path.toString()); + + expect(firstBucketNames).toEqual([ + "[0…99]", + "[100…199]", + "[200…299]", + "[300…399]", + "[400…499]", + "[500…599]", + "[600…699]", + "[700…799]", + "[800…899]", + "[900…999]", + ]); + expect(firstBucketPaths[0]).toEqual("root◦[0…999]◦[0…99]"); + expect(firstBucketPaths[firstBucketPaths.length - 1]).toEqual( + "root◦[0…999]◦[900…999]" + ); + + const lastBucketNodes = makeNumericalBuckets(nodes[nodes.length - 1]); + const lastBucketNames = lastBucketNodes.map(n => n.name); + const lastBucketPaths = lastBucketNodes.map(n => n.path.toString()); + expect(lastBucketNames).toEqual([ + "[23000…23099]", + "[23100…23199]", + "[23200…23299]", + "[23300…23399]", + "[23400…23455]", + ]); + expect(lastBucketPaths[0]).toEqual("root◦[23000…23455]◦[23000…23099]"); + expect(lastBucketPaths[lastBucketPaths.length - 1]).toEqual( + "root◦[23000…23455]◦[23400…23455]" + ); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/node-has-entries.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/node-has-entries.test.js new file mode 100644 index 0000000000..4a7aaa971d --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/node-has-entries.test.js @@ -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 gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const gripMapStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); + +const { + createNode, + nodeHasEntries, +} = require("resource://devtools/client/shared/components/object-inspector/utils/node.js"); + +const createRootNode = value => + createNode({ name: "root", contents: { value } }); +describe("nodeHasEntries", () => { + it("returns true for Maps", () => { + expect( + nodeHasEntries(createRootNode(gripMapStubs.get("testSymbolKeyedMap"))) + ).toBe(true); + }); + + it("returns true for WeakMaps", () => { + expect( + nodeHasEntries(createRootNode(gripMapStubs.get("testWeakMap"))) + ).toBe(true); + }); + + it("returns true for Sets", () => { + expect( + nodeHasEntries(createRootNode(gripArrayStubs.get("new Set([1,2,3,4])"))) + ).toBe(true); + }); + + it("returns true for WeakSets", () => { + expect( + nodeHasEntries( + createRootNode( + gripArrayStubs.get( + "new WeakSet(document.querySelectorAll('div, button'))" + ) + ) + ) + ).toBe(true); + }); + + it("returns false for Arrays", () => { + expect( + nodeHasEntries(createRootNode(gripMapStubs.get("testMaxProps"))) + ).toBe(false); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/node-is-window.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/node-is-window.test.js new file mode 100644 index 0000000000..8fe920ed17 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/node-is-window.test.js @@ -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/>. */ + +const gripWindowStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/window.js"); + +const { + createNode, + nodeIsWindow, +} = require("resource://devtools/client/shared/components/object-inspector/utils/node.js"); + +const createRootNode = value => + createNode({ name: "root", contents: { value } }); +describe("nodeIsWindow", () => { + it("returns true for Window", () => { + expect( + nodeIsWindow(createRootNode(gripWindowStubs.get("Window")._grip)) + ).toBe(true); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/node-supports-numerical-bucketing.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/node-supports-numerical-bucketing.test.js new file mode 100644 index 0000000000..51199146e0 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/node-supports-numerical-bucketing.test.js @@ -0,0 +1,72 @@ +/* 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, + makeNodesForEntries, + nodeSupportsNumericalBucketing, +} = require("resource://devtools/client/shared/components/object-inspector/utils/node.js"); + +const createRootNode = stub => + createNode({ + name: "root", + contents: { value: stub }, + }); + +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const gripMapStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); + +describe("nodeSupportsNumericalBucketing", () => { + it("returns true for Arrays", () => { + expect( + nodeSupportsNumericalBucketing( + createRootNode(gripArrayStubs.get("testBasic")) + ) + ).toBe(true); + }); + + it("returns true for NodeMap", () => { + expect( + nodeSupportsNumericalBucketing( + createRootNode(gripArrayStubs.get("testNamedNodeMap")) + ) + ).toBe(true); + }); + + it("returns true for NodeList", () => { + expect( + nodeSupportsNumericalBucketing( + createRootNode(gripArrayStubs.get("testNodeList")) + ) + ).toBe(true); + }); + + it("returns true for DocumentFragment", () => { + expect( + nodeSupportsNumericalBucketing( + createRootNode(gripArrayStubs.get("testDocumentFragment")) + ) + ).toBe(true); + }); + + it("returns true for <entries> node", () => { + expect( + nodeSupportsNumericalBucketing( + makeNodesForEntries( + createRootNode(gripMapStubs.get("testSymbolKeyedMap")) + ) + ) + ).toBe(true); + }); + + it("returns true for buckets node", () => { + expect( + nodeSupportsNumericalBucketing( + makeNodesForEntries( + createRootNode(gripMapStubs.get("testSymbolKeyedMap")) + ) + ) + ).toBe(true); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/promises.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/promises.test.js new file mode 100644 index 0000000000..229f717b56 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/promises.test.js @@ -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 { + makeNodesForPromiseProperties, + nodeIsPromise, +} = require("resource://devtools/client/shared/components/object-inspector/utils/node.js"); + +describe("promises utils function", () => { + it("is promise", () => { + const promise = { + contents: { + enumerable: true, + configurable: false, + value: { + actor: "server2.conn2.child1/obj36", + promiseState: { + state: "rejected", + reason: { + type: "undefined", + }, + }, + class: "Promise", + type: "object", + }, + }, + }; + + expect(nodeIsPromise(promise)).toEqual(true); + }); + + it("makeNodesForPromiseProperties", () => { + const item = { + path: "root", + contents: { + value: { + actor: "server2.conn2.child1/obj36", + class: "Promise", + type: "object", + }, + }, + }; + const promiseState = { + state: "rejected", + reason: { + type: "3", + }, + }; + + const properties = makeNodesForPromiseProperties({promiseState}, item); + expect(properties).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-entries.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-entries.test.js new file mode 100644 index 0000000000..e4672f2a92 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-entries.test.js @@ -0,0 +1,171 @@ +/* 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 Utils = require("resource://devtools/client/shared/components/object-inspector/utils/index.js"); +const { createNode, getChildren, makeNodesForEntries } = Utils.node; + +const { shouldLoadItemEntries } = Utils.loadProperties; + +const gripMapStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const gripStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); + +describe("shouldLoadItemEntries", () => { + it("returns true for an entries node", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + expect(shouldLoadItemEntries(entriesNode)).toBeTruthy(); + }); + + it("returns false for an already loaded entries node", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + const loadedProperties = new Map([[entriesNode.path, true]]); + expect(shouldLoadItemEntries(entriesNode, loadedProperties)).toBeFalsy(); + }); + + it("returns true for entries on a Map with everything in preview", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("testSymbolKeyedMap"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + expect(shouldLoadItemEntries(entriesNode)).toBeTruthy(); + }); + + it("returns true for entries on a Set with everything in preview", () => { + const setStubNode = createNode({ + name: "set", + contents: { + value: gripArrayStubs.get("new Set([1,2,3,4])"), + }, + }); + const entriesNode = makeNodesForEntries(setStubNode); + expect(shouldLoadItemEntries(entriesNode)).toBeTruthy(); + }); + + it("returns false for a Set node", () => { + const setStubNode = createNode({ + name: "set", + contents: { + value: gripArrayStubs.get("new Set([1,2,3,4])"), + }, + }); + expect(shouldLoadItemEntries(setStubNode)).toBeFalsy(); + }); + + it("returns false for a Map node", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + expect(shouldLoadItemEntries(mapStubNode)).toBeFalsy(); + }); + + it("returns false for an array", () => { + const node = createNode({ + name: "array", + contents: { + value: gripMapStubs.get("testMaxProps"), + }, + }); + expect(shouldLoadItemEntries(node)).toBeFalsy(); + }); + + it("returns false for an object", () => { + const node = createNode({ + name: "array", + contents: { + value: gripStubs.get("testMaxProps"), + }, + }); + expect(shouldLoadItemEntries(node)).toBeFalsy(); + }); + + it("returns false for an entries node with buckets", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("234-entries Map"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + expect(shouldLoadItemEntries(entriesNode)).toBeFalsy(); + }); + + it("returns true for an entries bucket node", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("234-entries Map"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + const bucketNodes = getChildren({ + item: entriesNode, + loadedProperties: new Map([[entriesNode.path, true]]), + }); + + // Make sure we do have a bucket. + expect(bucketNodes[0].name).toBe("[0…99]"); + expect(shouldLoadItemEntries(bucketNodes[0])).toBeTruthy(); + }); + + it("returns false for an entries bucket node with sub-buckets", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("23456-entries Map"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + const bucketNodes = getChildren({ + item: entriesNode, + loadedProperties: new Map([[entriesNode.path, true]]), + }); + + // Make sure we do have a bucket. + expect(bucketNodes[0].name).toBe("[0…999]"); + expect(shouldLoadItemEntries(bucketNodes[0])).toBeFalsy(); + }); + + it("returns true for an entries sub-bucket node", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("23456-entries Map"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + const bucketNodes = getChildren({ + item: entriesNode, + loadedProperties: new Map([[entriesNode.path, true]]), + }); + // Make sure we do have a bucket. + expect(bucketNodes[0].name).toBe("[0…999]"); + + // Get the sub-buckets + const subBucketNodes = getChildren({ + item: bucketNodes[0], + loadedProperties: new Map([[bucketNodes[0].path, true]]), + }); + // Make sure we do have a bucket. + expect(subBucketNodes[0].name).toBe("[0…99]"); + expect(shouldLoadItemEntries(subBucketNodes[0])).toBeTruthy(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-full-text.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-full-text.test.js new file mode 100644 index 0000000000..9e696a028c --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-full-text.test.js @@ -0,0 +1,56 @@ +/* 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 Utils = require("resource://devtools/client/shared/components/object-inspector/utils/index.js"); +const { createNode } = Utils.node; +const { shouldLoadItemFullText } = Utils.loadProperties; + +const longStringStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/long-string.js"); +const symbolStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/symbol.js"); + +describe("shouldLoadItemFullText", () => { + it("returns true for a longString node with unloaded full text", () => { + const node = createNode({ + name: "root", + contents: { + value: longStringStubs.get("testUnloadedFullText"), + }, + }); + expect(shouldLoadItemFullText(node)).toBeTruthy(); + }); + + it("returns false for a longString node with loaded full text", () => { + const node = createNode({ + name: "root", + contents: { + value: longStringStubs.get("testLoadedFullText"), + }, + }); + const loadedProperties = new Map([[node.path, true]]); + expect(shouldLoadItemFullText(node, loadedProperties)).toBeFalsy(); + }); + + it("returns false for non longString primitive nodes", () => { + const values = [ + "primitive string", + 1, + -1, + 0, + true, + false, + null, + undefined, + symbolStubs.get("Symbol"), + ]; + + const nodes = values.map((value, i) => + createNode({ + name: `root${i}`, + contents: { value }, + }) + ); + + nodes.forEach(node => expect(shouldLoadItemFullText(node)).toBeFalsy()); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-indexed-properties.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-indexed-properties.test.js new file mode 100644 index 0000000000..63e505b947 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-indexed-properties.test.js @@ -0,0 +1,259 @@ +/* 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 Utils = require("resource://devtools/client/shared/components/object-inspector/utils/index.js"); +const { + createNode, + createGetterNode, + createSetterNode, + getChildren, + makeNodesForEntries, + nodeIsDefaultProperties, +} = Utils.node; + +const { shouldLoadItemIndexedProperties } = Utils.loadProperties; + +const { + createGripMapEntry, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); +const accessorStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/accessor.js"); +const gripMapStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const gripStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); +const windowStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/window.js"); + +describe("shouldLoadItemIndexedProperties", () => { + it("returns true for an array", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("testMaxProps"), + }, + }); + expect(shouldLoadItemIndexedProperties(node)).toBeTruthy(); + }); + + it("returns false for an already loaded item", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("testMaxProps"), + }, + }); + const loadedProperties = new Map([[node.path, true]]); + expect(shouldLoadItemIndexedProperties(node, loadedProperties)).toBeFalsy(); + }); + + it("returns false for an array node with buckets", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(234)"), + }, + }); + expect(shouldLoadItemIndexedProperties(node)).toBeFalsy(); + }); + + it("returns true for an array bucket node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(234)"), + }, + }); + const bucketNodes = getChildren({ + item: node, + loadedProperties: new Map([[node.path, true]]), + }); + + // Make sure we do have a bucket. + expect(bucketNodes[0].name).toBe("[0…99]"); + expect(shouldLoadItemIndexedProperties(bucketNodes[0])).toBeTruthy(); + }); + + it("returns false for an array bucket node with sub-buckets", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(23456)"), + }, + }); + const bucketNodes = getChildren({ + item: node, + loadedProperties: new Map([[node.path, true]]), + }); + + // Make sure we do have a bucket. + expect(bucketNodes[0].name).toBe("[0…999]"); + expect(shouldLoadItemIndexedProperties(bucketNodes[0])).toBeFalsy(); + }); + + it("returns true for an array sub-bucket node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(23456)"), + }, + }); + const bucketNodes = getChildren({ + item: node, + loadedProperties: new Map([[node.path, true]]), + }); + // Make sure we do have a bucket. + expect(bucketNodes[0].name).toBe("[0…999]"); + + // Get the sub-buckets + const subBucketNodes = getChildren({ + item: bucketNodes[0], + loadedProperties: new Map([[bucketNodes[0].path, true]]), + }); + // Make sure we do have a bucket. + expect(subBucketNodes[0].name).toBe("[0…99]"); + expect(shouldLoadItemIndexedProperties(subBucketNodes[0])).toBeTruthy(); + }); + + it("returns false for an entries node", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + expect(shouldLoadItemIndexedProperties(entriesNode)).toBeFalsy(); + }); + + it("returns true for an Object", () => { + const node = createNode({ + name: "root", + contents: { + value: gripStubs.get("testMaxProps"), + }, + }); + expect(shouldLoadItemIndexedProperties(node)).toBeTruthy(); + }); + + it("returns true for a Map", () => { + const node = createNode({ + name: "root", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + expect(shouldLoadItemIndexedProperties(node)).toBeTruthy(); + }); + + it("returns true for a Set", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("new Set([1,2,3,4])"), + }, + }); + expect(shouldLoadItemIndexedProperties(node)).toBeTruthy(); + }); + + it("returns true for a Window", () => { + const node = createNode({ + name: "root", + contents: { + value: windowStubs.get("Window")._grip, + }, + }); + expect(shouldLoadItemIndexedProperties(node)).toBeTruthy(); + }); + + it("returns false for a <default properties> node", () => { + const windowNode = createNode({ + name: "root", + contents: { + value: windowStubs.get("Window")._grip, + }, + }); + const loadedProperties = new Map([ + [ + windowNode.path, + { + ownProperties: { + foo: { value: "bar" }, + location: { value: "a" }, + }, + }, + ], + ]); + const [, defaultPropertiesNode] = getChildren({ + item: windowNode, + loadedProperties, + }); + expect(nodeIsDefaultProperties(defaultPropertiesNode)).toBe(true); + expect(shouldLoadItemIndexedProperties(defaultPropertiesNode)).toBeFalsy(); + }); + + it("returns false for a MapEntry node", () => { + const node = createGripMapEntry("key", "value"); + expect(shouldLoadItemIndexedProperties(node)).toBeFalsy(); + }); + + it("returns false for a Proxy node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripStubs.get("testProxy"), + }, + }); + expect(shouldLoadItemIndexedProperties(node)).toBeFalsy(); + }); + + it("returns true for a Proxy target node", () => { + const proxyNode = createNode({ + name: "root", + contents: { + value: gripStubs.get("testProxy"), + }, + }); + const loadedProperties = new Map([ + [proxyNode.path, gripStubs.get("testProxySlots")], + ]); + const [targetNode] = getChildren({ item: proxyNode, loadedProperties }); + // Make sure we have the target node. + expect(targetNode.name).toBe("<target>"); + expect(shouldLoadItemIndexedProperties(targetNode)).toBeTruthy(); + }); + + it("returns false for an accessor node", () => { + const accessorNode = createNode({ + name: "root", + contents: { + value: accessorStubs.get("getter"), + }, + }); + expect(shouldLoadItemIndexedProperties(accessorNode)).toBeFalsy(); + }); + + it("returns true for an accessor <get> node", () => { + const getNode = createGetterNode({ + name: "root", + property: accessorStubs.get("getter"), + }); + expect(getNode.name).toBe("<get root()>"); + expect(shouldLoadItemIndexedProperties(getNode)).toBeTruthy(); + }); + + it("returns true for an accessor <set> node", () => { + const setNode = createSetterNode({ + name: "root", + property: accessorStubs.get("setter"), + }); + expect(setNode.name).toBe("<set root()>"); + expect(shouldLoadItemIndexedProperties(setNode)).toBeTruthy(); + }); + + it("returns false for a primitive node", () => { + const node = createNode({ + name: "root", + contents: { value: 42 }, + }); + expect(shouldLoadItemIndexedProperties(node)).toBeFalsy(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-non-indexed-properties.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-non-indexed-properties.test.js new file mode 100644 index 0000000000..425540eee2 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-non-indexed-properties.test.js @@ -0,0 +1,222 @@ +/* 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 Utils = require("resource://devtools/client/shared/components/object-inspector/utils/index.js"); +const { + createNode, + createGetterNode, + createSetterNode, + getChildren, + makeNodesForEntries, + nodeIsDefaultProperties, +} = Utils.node; + +const { shouldLoadItemNonIndexedProperties } = Utils.loadProperties; + +const { + createGripMapEntry, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); +const accessorStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/accessor.js"); +const gripMapStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const gripStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); +const windowStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/window.js"); + +describe("shouldLoadItemNonIndexedProperties", () => { + it("returns true for an array", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("testMaxProps"), + }, + }); + expect(shouldLoadItemNonIndexedProperties(node)).toBeTruthy(); + }); + + it("returns false for an already loaded item", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("testMaxProps"), + }, + }); + const loadedProperties = new Map([[node.path, true]]); + expect( + shouldLoadItemNonIndexedProperties(node, loadedProperties) + ).toBeFalsy(); + }); + + it("returns true for an array node with buckets", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(234)"), + }, + }); + expect(shouldLoadItemNonIndexedProperties(node)).toBeTruthy(); + }); + + it("returns false for an array bucket node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(234)"), + }, + }); + const bucketNodes = getChildren({ + item: node, + loadedProperties: new Map([[node.path, true]]), + }); + + // Make sure we do have a bucket. + expect(bucketNodes[0].name).toBe("[0…99]"); + expect(shouldLoadItemNonIndexedProperties(bucketNodes[0])).toBeFalsy(); + }); + + it("returns false for an entries node", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + expect(shouldLoadItemNonIndexedProperties(entriesNode)).toBeFalsy(); + }); + + it("returns true for an Object", () => { + const node = createNode({ + name: "root", + contents: { + value: gripStubs.get("testMaxProps"), + }, + }); + expect(shouldLoadItemNonIndexedProperties(node)).toBeTruthy(); + }); + + it("returns true for a Map", () => { + const node = createNode({ + name: "root", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + expect(shouldLoadItemNonIndexedProperties(node)).toBeTruthy(); + }); + + it("returns true for a Set", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("new Set([1,2,3,4])"), + }, + }); + expect(shouldLoadItemNonIndexedProperties(node)).toBeTruthy(); + }); + + it("returns true for a Window", () => { + const node = createNode({ + name: "root", + contents: { + value: windowStubs.get("Window")._grip, + }, + }); + expect(shouldLoadItemNonIndexedProperties(node)).toBeTruthy(); + }); + + it("returns false for a <default properties> node", () => { + const windowNode = createNode({ + name: "root", + contents: { + value: windowStubs.get("Window")._grip, + }, + }); + const loadedProperties = new Map([ + [ + windowNode.path, + { + ownProperties: { + foo: { value: "bar" }, + location: { value: "a" }, + }, + }, + ], + ]); + const [, defaultPropertiesNode] = getChildren({ + item: windowNode, + loadedProperties, + }); + expect(nodeIsDefaultProperties(defaultPropertiesNode)).toBe(true); + expect( + shouldLoadItemNonIndexedProperties(defaultPropertiesNode) + ).toBeFalsy(); + }); + + it("returns false for a MapEntry node", () => { + const node = createGripMapEntry("key", "value"); + expect(shouldLoadItemNonIndexedProperties(node)).toBeFalsy(); + }); + + it("returns false for a Proxy node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripStubs.get("testProxy"), + }, + }); + expect(shouldLoadItemNonIndexedProperties(node)).toBeFalsy(); + }); + + it("returns true for a Proxy target node", () => { + const proxyNode = createNode({ + name: "root", + contents: { + value: gripStubs.get("testProxy"), + }, + }); + const loadedProperties = new Map([ + [proxyNode.path, gripStubs.get("testProxySlots")], + ]); + const [targetNode] = getChildren({ item: proxyNode, loadedProperties }); + // Make sure we have the target node. + expect(targetNode.name).toBe("<target>"); + expect(shouldLoadItemNonIndexedProperties(targetNode)).toBeTruthy(); + }); + + it("returns false for an accessor node", () => { + const accessorNode = createNode({ + name: "root", + contents: { + value: accessorStubs.get("getter"), + }, + }); + expect(shouldLoadItemNonIndexedProperties(accessorNode)).toBeFalsy(); + }); + + it("returns true for an accessor <get> node", () => { + const getNode = createGetterNode({ + name: "root", + property: accessorStubs.get("getter"), + }); + expect(getNode.name).toBe("<get root()>"); + expect(shouldLoadItemNonIndexedProperties(getNode)).toBeTruthy(); + }); + + it("returns true for an accessor <set> node", () => { + const setNode = createSetterNode({ + name: "root", + property: accessorStubs.get("setter"), + }); + expect(setNode.name).toBe("<set root()>"); + expect(shouldLoadItemNonIndexedProperties(setNode)).toBeTruthy(); + }); + + it("returns false for a primitive node", () => { + const node = createNode({ + name: "root", + contents: { value: 42 }, + }); + expect(shouldLoadItemNonIndexedProperties(node)).toBeFalsy(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-prototype.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-prototype.test.js new file mode 100644 index 0000000000..83d45df70c --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-prototype.test.js @@ -0,0 +1,218 @@ +/* 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 Utils = require("resource://devtools/client/shared/components/object-inspector/utils/index.js"); +const { + createNode, + createGetterNode, + createSetterNode, + getChildren, + makeNodesForEntries, + nodeIsDefaultProperties, +} = Utils.node; + +const { shouldLoadItemPrototype } = Utils.loadProperties; + +const { + createGripMapEntry, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); +const accessorStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/accessor.js"); +const gripMapStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const gripStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); +const windowStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/window.js"); + +describe("shouldLoadItemPrototype", () => { + it("returns true for an array", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("testMaxProps"), + }, + }); + expect(shouldLoadItemPrototype(node)).toBeTruthy(); + }); + + it("returns false for an already loaded item", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("testMaxProps"), + }, + }); + const loadedProperties = new Map([[node.path, true]]); + expect(shouldLoadItemPrototype(node, loadedProperties)).toBeFalsy(); + }); + + it("returns true for an array node with buckets", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(234)"), + }, + }); + expect(shouldLoadItemPrototype(node)).toBeTruthy(); + }); + + it("returns false for an array bucket node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(234)"), + }, + }); + const bucketNodes = getChildren({ + item: node, + loadedProperties: new Map([[node.path, true]]), + }); + + // Make sure we do have a bucket. + expect(bucketNodes[0].name).toBe("[0…99]"); + expect(shouldLoadItemPrototype(bucketNodes[0])).toBeFalsy(); + }); + + it("returns false for an entries node", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + expect(shouldLoadItemPrototype(entriesNode)).toBeFalsy(); + }); + + it("returns true for an Object", () => { + const node = createNode({ + name: "root", + contents: { + value: gripStubs.get("testMaxProps"), + }, + }); + expect(shouldLoadItemPrototype(node)).toBeTruthy(); + }); + + it("returns true for a Map", () => { + const node = createNode({ + name: "root", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + expect(shouldLoadItemPrototype(node)).toBeTruthy(); + }); + + it("returns true for a Set", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("new Set([1,2,3,4])"), + }, + }); + expect(shouldLoadItemPrototype(node)).toBeTruthy(); + }); + + it("returns true for a Window", () => { + const node = createNode({ + name: "root", + contents: { + value: windowStubs.get("Window")._grip, + }, + }); + expect(shouldLoadItemPrototype(node)).toBeTruthy(); + }); + + it("returns false for a <default properties> node", () => { + const windowNode = createNode({ + name: "root", + contents: { + value: windowStubs.get("Window")._grip, + }, + }); + const loadedProperties = new Map([ + [ + windowNode.path, + { + ownProperties: { + foo: { value: "bar" }, + location: { value: "a" }, + }, + }, + ], + ]); + const [, defaultPropertiesNode] = getChildren({ + item: windowNode, + loadedProperties, + }); + expect(nodeIsDefaultProperties(defaultPropertiesNode)).toBe(true); + expect(shouldLoadItemPrototype(defaultPropertiesNode)).toBeFalsy(); + }); + + it("returns false for a MapEntry node", () => { + const node = createGripMapEntry("key", "value"); + expect(shouldLoadItemPrototype(node)).toBeFalsy(); + }); + + it("returns false for a Proxy node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripStubs.get("testProxy"), + }, + }); + expect(shouldLoadItemPrototype(node)).toBeFalsy(); + }); + + it("returns true for a Proxy target node", () => { + const proxyNode = createNode({ + name: "root", + contents: { + value: gripStubs.get("testProxy"), + }, + }); + const loadedProperties = new Map([ + [proxyNode.path, gripStubs.get("testProxySlots")], + ]); + const [targetNode] = getChildren({ item: proxyNode, loadedProperties }); + // Make sure we have the target node. + expect(targetNode.name).toBe("<target>"); + expect(shouldLoadItemPrototype(targetNode)).toBeTruthy(); + }); + + it("returns false for an accessor node", () => { + const accessorNode = createNode({ + name: "root", + contents: { + value: accessorStubs.get("getter"), + }, + }); + expect(shouldLoadItemPrototype(accessorNode)).toBeFalsy(); + }); + + it("returns true for an accessor <get> node", () => { + const getNode = createGetterNode({ + name: "root", + property: accessorStubs.get("getter"), + }); + expect(getNode.name).toBe("<get root()>"); + expect(shouldLoadItemPrototype(getNode)).toBeTruthy(); + }); + + it("returns true for an accessor <set> node", () => { + const setNode = createSetterNode({ + name: "root", + property: accessorStubs.get("setter"), + }); + expect(setNode.name).toBe("<set root()>"); + expect(shouldLoadItemPrototype(setNode)).toBeTruthy(); + }); + + it("returns false for a primitive node", () => { + const node = createNode({ + name: "root", + contents: { value: 42 }, + }); + expect(shouldLoadItemPrototype(node)).toBeFalsy(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-symbols.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-symbols.test.js new file mode 100644 index 0000000000..a937b9fcab --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-symbols.test.js @@ -0,0 +1,218 @@ +/* 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 Utils = require("resource://devtools/client/shared/components/object-inspector/utils/index.js"); +const { + createNode, + createGetterNode, + createSetterNode, + getChildren, + makeNodesForEntries, + nodeIsDefaultProperties, +} = Utils.node; + +const { shouldLoadItemSymbols } = Utils.loadProperties; + +const { + createGripMapEntry, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); +const accessorStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/accessor.js"); +const gripMapStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const gripStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); +const windowStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/window.js"); + +describe("shouldLoadItemSymbols", () => { + it("returns true for an array", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("testMaxProps"), + }, + }); + expect(shouldLoadItemSymbols(node)).toBeTruthy(); + }); + + it("returns false for an already loaded item", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("testMaxProps"), + }, + }); + const loadedProperties = new Map([[node.path, true]]); + expect(shouldLoadItemSymbols(node, loadedProperties)).toBeFalsy(); + }); + + it("returns true for an array node with buckets", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(234)"), + }, + }); + expect(shouldLoadItemSymbols(node)).toBeTruthy(); + }); + + it("returns false for an array bucket node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("Array(234)"), + }, + }); + const bucketNodes = getChildren({ + item: node, + loadedProperties: new Map([[node.path, true]]), + }); + + // Make sure we do have a bucket. + expect(bucketNodes[0].name).toBe("[0…99]"); + expect(shouldLoadItemSymbols(bucketNodes[0])).toBeFalsy(); + }); + + it("returns false for an entries node", () => { + const mapStubNode = createNode({ + name: "map", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + const entriesNode = makeNodesForEntries(mapStubNode); + expect(shouldLoadItemSymbols(entriesNode)).toBeFalsy(); + }); + + it("returns true for an Object", () => { + const node = createNode({ + name: "root", + contents: { + value: gripStubs.get("testMaxProps"), + }, + }); + expect(shouldLoadItemSymbols(node)).toBeTruthy(); + }); + + it("returns true for a Map", () => { + const node = createNode({ + name: "root", + contents: { + value: gripMapStubs.get("20-entries Map"), + }, + }); + expect(shouldLoadItemSymbols(node)).toBeTruthy(); + }); + + it("returns true for a Set", () => { + const node = createNode({ + name: "root", + contents: { + value: gripArrayStubs.get("new Set([1,2,3,4])"), + }, + }); + expect(shouldLoadItemSymbols(node)).toBeTruthy(); + }); + + it("returns true for a Window", () => { + const node = createNode({ + name: "root", + contents: { + value: windowStubs.get("Window")._grip, + }, + }); + expect(shouldLoadItemSymbols(node)).toBeTruthy(); + }); + + it("returns false for a <default properties> node", () => { + const windowNode = createNode({ + name: "root", + contents: { + value: windowStubs.get("Window")._grip, + }, + }); + const loadedProperties = new Map([ + [ + windowNode.path, + { + ownProperties: { + foo: { value: "bar" }, + location: { value: "a" }, + }, + }, + ], + ]); + const [, defaultPropertiesNode] = getChildren({ + item: windowNode, + loadedProperties, + }); + expect(nodeIsDefaultProperties(defaultPropertiesNode)).toBe(true); + expect(shouldLoadItemSymbols(defaultPropertiesNode)).toBeFalsy(); + }); + + it("returns false for a MapEntry node", () => { + const node = createGripMapEntry("key", "value"); + expect(shouldLoadItemSymbols(node)).toBeFalsy(); + }); + + it("returns false for a Proxy node", () => { + const node = createNode({ + name: "root", + contents: { + value: gripStubs.get("testProxy"), + }, + }); + expect(shouldLoadItemSymbols(node)).toBeFalsy(); + }); + + it("returns true for a Proxy target node", () => { + const proxyNode = createNode({ + name: "root", + contents: { + value: gripStubs.get("testProxy"), + }, + }); + const loadedProperties = new Map([ + [proxyNode.path, gripStubs.get("testProxySlots")], + ]); + const [targetNode] = getChildren({ item: proxyNode, loadedProperties }); + // Make sure we have the target node. + expect(targetNode.name).toBe("<target>"); + expect(shouldLoadItemSymbols(targetNode)).toBeTruthy(); + }); + + it("returns false for an accessor node", () => { + const accessorNode = createNode({ + name: "root", + contents: { + value: accessorStubs.get("getter"), + }, + }); + expect(shouldLoadItemSymbols(accessorNode)).toBeFalsy(); + }); + + it("returns true for an accessor <get> node", () => { + const getNode = createGetterNode({ + name: "root", + property: accessorStubs.get("getter"), + }); + expect(getNode.name).toBe("<get root()>"); + expect(shouldLoadItemSymbols(getNode)).toBeTruthy(); + }); + + it("returns true for an accessor <set> node", () => { + const setNode = createSetterNode({ + name: "root", + property: accessorStubs.get("setter"), + }); + expect(setNode.name).toBe("<set root()>"); + expect(shouldLoadItemSymbols(setNode)).toBeTruthy(); + }); + + it("returns false for a primitive node", () => { + const node = createNode({ + name: "root", + contents: { value: 42 }, + }); + expect(shouldLoadItemSymbols(node)).toBeFalsy(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/object-inspector/utils/should-render-roots-in-reps.test.js b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-render-roots-in-reps.test.js new file mode 100644 index 0000000000..456326545a --- /dev/null +++ b/devtools/client/shared/components/test/node/components/object-inspector/utils/should-render-roots-in-reps.test.js @@ -0,0 +1,153 @@ +/* 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 Utils = require("resource://devtools/client/shared/components/object-inspector/utils/index.js"); +const { shouldRenderRootsInReps } = Utils; + +const nullStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/null.js"); +const numberStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/number.js"); +const undefinedStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/undefined.js"); +const gripStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const symbolStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/symbol.js"); +const errorStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/error.js"); +const bigIntStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/big-int.js"); + +describe("shouldRenderRootsInReps", () => { + it("returns true for a string", () => { + expect( + shouldRenderRootsInReps([ + { + contents: { value: "Hello" }, + }, + ]) + ).toBeTruthy(); + }); + + it("returns true for an integer", () => { + expect( + shouldRenderRootsInReps([ + { + contents: { value: numberStubs.get("Int") }, + }, + ]) + ).toBeTruthy(); + }); + + it("returns false for empty roots", () => { + expect(shouldRenderRootsInReps([])).toBeFalsy(); + }); + + it("returns true for a big int", () => { + expect( + shouldRenderRootsInReps([ + { + contents: { value: bigIntStubs.get("1n") }, + }, + ]) + ).toBeTruthy(); + }); + + it("returns true for undefined", () => { + expect( + shouldRenderRootsInReps([ + { + contents: { value: undefinedStubs.get("Undefined") }, + }, + ]) + ).toBeTruthy(); + }); + + it("returns true for null", () => { + expect( + shouldRenderRootsInReps([ + { + contents: { value: nullStubs.get("Null") }, + }, + ]) + ).toBeTruthy(); + }); + + it("returns true for Symbols", () => { + expect( + shouldRenderRootsInReps([ + { + contents: { value: symbolStubs.get("Symbol") }, + }, + ]) + ).toBeTruthy(); + }); + + it("returns true for Errors when customFormat prop is true", () => { + expect( + shouldRenderRootsInReps( + [ + { + contents: { value: errorStubs.get("MultilineStackError") }, + }, + ], + { customFormat: true } + ) + ).toBeTruthy(); + }); + + it("returns false for Errors when customFormat prop is false", () => { + expect( + shouldRenderRootsInReps( + [ + { + contents: { value: errorStubs.get("MultilineStackError") }, + }, + ], + { customFormat: false } + ) + ).toBeFalsy(); + }); + + it("returns false when there are multiple primitive roots", () => { + expect( + shouldRenderRootsInReps([ + { + contents: { value: "Hello" }, + }, + { + contents: { value: 42 }, + }, + ]) + ).toBeFalsy(); + }); + + it("returns false for primitive when the root specifies a name", () => { + expect( + shouldRenderRootsInReps([ + { + name: "label", + contents: { value: 42 }, + }, + ]) + ).toBeFalsy(); + }); + + it("returns false for Grips", () => { + expect( + shouldRenderRootsInReps([ + { + name: "label", + contents: { value: gripStubs.get("testMaxProps") }, + }, + ]) + ).toBeFalsy(); + }); + + it("returns false for Arrays", () => { + expect( + shouldRenderRootsInReps([ + { + name: "label", + contents: { value: gripArrayStubs.get("testMaxProps") }, + }, + ]) + ).toBeFalsy(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/__snapshots__/accessor.test.js.snap b/devtools/client/shared/components/test/node/components/reps/__snapshots__/accessor.test.js.snap new file mode 100644 index 0000000000..d8e298956a --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/__snapshots__/accessor.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Accessor - Invoke getter does not render an icon when the object has an evaluation 1`] = `"\\"hello\\""`; diff --git a/devtools/client/shared/components/test/node/components/reps/__snapshots__/element-node.test.js.snap b/devtools/client/shared/components/test/node/components/reps/__snapshots__/element-node.test.js.snap new file mode 100644 index 0000000000..077bc6cc71 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/__snapshots__/element-node.test.js.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ElementNode - Node with spaces in the class name renders with expected text content 1`] = ` +<span + className="objectBox objectBox-node" + data-link-actor-id="server1.conn3.child1/obj59" +> + <span + className="angleBracket" + > + < + </span> + <span + className="tag-name" + > + body + </span> + + <span> + <span + className="attrName" + > + class + </span> + <span + className="attrEqual" + > + = + </span> + <span + className="objectBox objectBox-string attrValue" + > + "a b c" + </span> + </span> + <span + className="angleBracket" + > + > + </span> +</span> +`; diff --git a/devtools/client/shared/components/test/node/components/reps/__snapshots__/error.test.js.snap b/devtools/client/shared/components/test/node/components/reps/__snapshots__/error.test.js.snap new file mode 100644 index 0000000000..2dc830576f --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/__snapshots__/error.test.js.snap @@ -0,0 +1,1225 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Error - Error with V8-like stack renders with expected text 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1020" + title={null} +> + Error: + <span + className="objectBox objectBox-string" + > + BOOM + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + getAccount + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + http://moz.com/script.js:1:2 + </span> + + + </span> +</span> +`; + +exports[`Error - Error with invalid stack renders with expected text 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1020" + title={null} +> + Error: + <span + className="objectBox objectBox-string" + > + bad stack + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + /> +</span> +`; + +exports[`Error - Error with stack having frames with multiple @ renders with expected text for Error object 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1021" + title={null} +> + Error: + <span + className="objectBox objectBox-string" + > + bar + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + errorBar + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + https://example.com/turbo/from-npm.js@0.8.26/dist/from-npm.js:814:31 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn1" + > + errorFoo + </span> + + <span + className="objectBox-stackTrace-location" + key="location1" + > + https://example.com/turbo/from-npm.js@0.8.26/dist/from-npm.js:815:31 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn2" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location2" + > + https://example.com/turbo/from-npm.js@0.8.26/dist/from-npm.js:816:31 + </span> + + + </span> +</span> +`; + +exports[`Error - Error with undefined-grip message renders with expected text 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server0.conn0.child1/obj88" + title={null} +> + Error: + <span + className="objectBox objectBox-undefined" + title={null} + > + undefined + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:16:13 + </span> + + + </span> +</span> +`; + +exports[`Error - Error with undefined-grip message renders with expected text 2`] = ` +<span + className="objectBox-stackTrace " + data-link-actor-id="server0.conn0.child1/obj88" + title={null} +> + <span + className="objectTitle" + key="title" + > + Error + </span> +</span> +`; + +exports[`Error - Error with undefined-grip name renders with expected text 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server0.conn0.child1/obj88" + title={null} +> + Error: + <span + className="objectBox objectBox-string" + > + too much recursion + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:16:13 + </span> + + + </span> +</span> +`; + +exports[`Error - Error with undefined-grip name renders with expected text 2`] = ` +<span + className="objectBox-stackTrace " + data-link-actor-id="server0.conn0.child1/obj88" + title={null} +> + <span + className="objectTitle" + key="title" + > + Error + </span> +</span> +`; + +exports[`Error - Error with undefined-grip stack renders with expected text 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server0.conn0.child1/obj88" + title={null} +> + InternalError: + <span + className="objectBox objectBox-string" + > + too much recursion + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + /> +</span> +`; + +exports[`Error - Eval error renders with expected text for an EvalError 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1022" + title={null} +> + EvalError: + <span + className="objectBox objectBox-string" + > + EvalError message + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:10:13 + </span> + + + </span> +</span> +`; + +exports[`Error - Internal error renders with expected text for an InternalError 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1023" + title={null} +> + InternalError: + <span + className="objectBox objectBox-string" + > + InternalError message + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:11:13 + </span> + + + </span> +</span> +`; + +exports[`Error - Multi line stack error renders with expected text for Error object 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1021" + title={null} +> + Error: + <span + className="objectBox objectBox-string" + > + bar + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + errorBar + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:6:15 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn1" + > + errorFoo + </span> + + <span + className="objectBox-stackTrace-location" + key="location1" + > + debugger eval code:3:3 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn2" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location2" + > + debugger eval code:8:1 + </span> + + + </span> +</span> +`; + +exports[`Error - Range error renders with expected text for RangeError 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1024" + title={null} +> + RangeError: + <span + className="objectBox objectBox-string" + > + RangeError message + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:12:13 + </span> + + + </span> +</span> +`; + +exports[`Error - Reference error renders with expected text for ReferenceError 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1025" + title={null} +> + ReferenceError: + <span + className="objectBox objectBox-string" + > + ReferenceError message + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:13:13 + </span> + + + </span> +</span> +`; + +exports[`Error - Simple error renders with error type and preview message when in short mode 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1021" + title={null} +> + Error: + <span + className="objectBox objectBox-string" + > + bar + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + errorBar + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:6:15 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn1" + > + errorFoo + </span> + + <span + className="objectBox-stackTrace-location" + key="location1" + > + debugger eval code:3:3 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn2" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location2" + > + debugger eval code:8:1 + </span> + + + </span> +</span> +`; + +exports[`Error - Simple error renders with error type only when customFormat prop isn't set 1`] = ` +<span + className="objectBox-stackTrace " + data-link-actor-id="server1.conn1.child1/obj1021" + title={null} +> + <span + className="objectTitle" + key="title" + > + Error: + </span> + <span + className="objectBox objectBox-string" + > + bar + </span> +</span> +`; + +exports[`Error - Simple error renders with error type only when depth is > 0 1`] = ` +<span + className="objectBox-stackTrace " + data-link-actor-id="server1.conn1.child1/obj1021" + title={null} +> + <span + className="objectTitle" + key="title" + > + Error: + </span> + <span + className="objectBox objectBox-string" + > + bar + </span> +</span> +`; + +exports[`Error - Simple error renders with expected text for simple error 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1020" + title="Error: \\"Error message\\"" +> + Error: + <span + className="objectBox objectBox-string" + title="Error message" + > + Error message + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:1:13 + </span> + + + </span> +</span> +`; + +exports[`Error - Syntax error renders with expected text for SyntaxError 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1026" + title={null} +> + SyntaxError: + <span + className="objectBox objectBox-string" + > + SyntaxError message + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:14:13 + </span> + + + </span> +</span> +`; + +exports[`Error - Type error renders with expected text for TypeError 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1027" + title={null} +> + TypeError: + <span + className="objectBox objectBox-string" + > + TypeError message + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:15:13 + </span> + + + </span> +</span> +`; + +exports[`Error - URI error renders with expected text for URIError 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1028" + title={null} +> + URIError: + <span + className="objectBox objectBox-string" + > + URIError message + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + <anonymous> + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + debugger eval code:16:13 + </span> + + + </span> +</span> +`; + +exports[`Error - base-loader.sys.mjs renders as expected in HEADER mode 1`] = ` +<span + className="objectBox-stackTrace " + data-link-actor-id="server1.conn1.child1/obj1020" + title={null} +> + <span + className="objectTitle" + key="title" + > + Error + </span> +</span> +`; + +exports[`Error - base-loader.sys.mjs renders as expected in tiny mode 1`] = ` +<span + className="objectBox-stackTrace " + data-link-actor-id="server1.conn1.child1/obj1020" + title={null} +> + <span + className="objectTitle" + key="title" + > + Error + </span> +</span> +`; + +exports[`Error - base-loader.sys.mjs renders as expected without mode 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1020" + title={null} +> + Error: + <span + className="objectBox objectBox-string" + > + Error message + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + onPacket + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + resource://devtools/client/debugger-client.js:856:9 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn1" + > + send + </span> + + <span + className="objectBox-stackTrace-location" + key="location1" + > + resource://devtools/shared/transport/transport.js:569:13 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn2" + > + makeInfallible + </span> + + <span + className="objectBox-stackTrace-location" + key="location2" + > + resource://devtools/shared/ThreadSafeDevToolsUtils.js:109:14 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn3" + > + makeInfallible + </span> + + <span + className="objectBox-stackTrace-location" + key="location3" + > + resource://devtools/shared/ThreadSafeDevToolsUtils.js:109:14 + </span> + + + </span> +</span> +`; + +exports[`Error - longString stacktrace - cut-off location renders as expected 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj33" + title={null} +> + InternalError: + <span + className="objectBox objectBox-string" + > + too much recursion + </span> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + doStuff + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:32:1 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn1" + > + doStuff + </span> + + <span + className="objectBox-stackTrace-location" + key="location1" + > + https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn2" + > + doStuff + </span> + + <span + className="objectBox-stackTrace-location" + key="location2" + > + https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn3" + > + doStuff + </span> + + <span + className="objectBox-stackTrace-location" + key="location3" + > + https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn4" + > + doStuff + </span> + + <span + className="objectBox-stackTrace-location" + key="location4" + > + https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn5" + > + doStuff + </span> + + <span + className="objectBox-stackTrace-location" + key="location5" + > + https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn6" + > + doStuff + </span> + + <span + className="objectBox-stackTrace-location" + key="location6" + > + https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + </span> + + + </span> +</span> +`; + +exports[`Error - longString stacktrace renders as expected 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn2.child1/obj33" + title={null} +> + Error: + <span + className="objectBox objectBox-string" + /> + <span + className="objectBox-stackTrace-grid" + key="stack" + > + + <span + className="objectBox-stackTrace-fn" + key="fn0" + > + ngOnChanges + </span> + + <span + className="objectBox-stackTrace-location" + key="location0" + > + webpack-internal:///./node_modules/@angular/common/esm5/common.js:2656:27 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn1" + > + checkAndUpdateDirectiveInline + </span> + + <span + className="objectBox-stackTrace-location" + key="location1" + > + webpack-internal:///./node_modules/@angular/core/esm5/core.js:12581:9 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn2" + > + checkAndUpdateNodeInline + </span> + + <span + className="objectBox-stackTrace-location" + key="location2" + > + webpack-internal:///./node_modules/@angular/core/esm5/core.js:14109:20 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn3" + > + checkAndUpdateNode + </span> + + <span + className="objectBox-stackTrace-location" + key="location3" + > + webpack-internal:///./node_modules/@angular/core/esm5/core.js:14052:16 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn4" + > + debugCheckAndUpdateNode + </span> + + <span + className="objectBox-stackTrace-location" + key="location4" + > + webpack-internal:///./node_modules/@angular/core/esm5/core.js:14945:55 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn5" + > + debugCheckDirectivesFn + </span> + + <span + className="objectBox-stackTrace-location" + key="location5" + > + webpack-internal:///./node_modules/@angular/core/esm5/core.js:14886:13 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn6" + > + View_MetaTableComponent_6 + </span> + + <span + className="objectBox-stackTrace-location" + key="location6" + > + ng:///AppModule/MetaTableComponent.ngfactory.js:98:5 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn7" + > + debugUpdateDirectives + </span> + + <span + className="objectBox-stackTrace-location" + key="location7" + > + webpack-internal:///./node_modules/@angular/core/esm5/core.js:14871:12 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn8" + > + checkAndUpdateView + </span> + + <span + className="objectBox-stackTrace-location" + key="location8" + > + webpack-internal:///./node_modules/@angular/core/esm5/core.js:14018:5 + </span> + + + + <span + className="objectBox-stackTrace-fn" + key="fn9" + > + callViewAction + </span> + + <span + className="objectBox-stackTrace-location" + key="location9" + > + webpack-internal:///./node_modules/@angular/core/esm5/core.js:14369:21 + </span> + + + </span> +</span> +`; + +exports[`Error - renderStacktrace prop uses renderStacktrace prop when provided 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj1021" + title={null} +> + Error: + <span + className="objectBox objectBox-string" + > + bar + </span> + <li + className="frame" + > + Function errorBar called from debugger eval code:6:15 + + </li> + <li + className="frame" + > + Function errorFoo called from debugger eval code:3:3 + + </li> + <li + className="frame" + > + Function <anonymous> called from debugger eval code:8:1 + + </li> +</span> +`; + +exports[`Error - renderStacktrace prop uses renderStacktrace with longString errors too 1`] = ` +<span + className="objectBox-stackTrace reps-custom-format" + data-link-actor-id="server1.conn1.child1/obj33" + title={null} +> + InternalError: + <span + className="objectBox objectBox-string" + > + too much recursion + </span> + <li + className="frame" + > + Function execute/AppComponent</AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:32:1 + + </li> + <li + className="frame" + > + Function execute/AppComponent</AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + + </li> + <li + className="frame" + > + Function execute/AppComponent</AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + + </li> + <li + className="frame" + > + Function execute/AppComponent</AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + + </li> + <li + className="frame" + > + Function execute/AppComponent</AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + + </li> + <li + className="frame" + > + Function execute/AppComponent</AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + + </li> + <li + className="frame" + > + Function execute/AppComponent</AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21 + + </li> +</span> +`; diff --git a/devtools/client/shared/components/test/node/components/reps/__snapshots__/nan.test.js.snap b/devtools/client/shared/components/test/node/components/reps/__snapshots__/nan.test.js.snap new file mode 100644 index 0000000000..c80b14a2fb --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/__snapshots__/nan.test.js.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NaN renders NaN Rep as expected 1`] = ` +<span + className="objectBox objectBox-nan" + title={null} +> + NaN +</span> +`; diff --git a/devtools/client/shared/components/test/node/components/reps/accessible.test.js b/devtools/client/shared/components/test/node/components/reps/accessible.test.js new file mode 100644 index 0000000000..8df9650c36 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/accessible.test.js @@ -0,0 +1,321 @@ +/* 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/>. */ + +"use strict"; + +/* global jest, __dirname */ +const { mount, shallow } = require("enzyme"); +const { JSDOM } = require("jsdom"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { Accessible } = REPS; +const { + ELLIPSIS, +} = require("resource://devtools/client/shared/components/reps/reps/rep-utils.js"); +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/accessible.js"); + +describe("Accessible - Document", () => { + const stub = stubs.get("Document"); + + it("selects Accessible Rep", () => { + expect(getRep(stub)).toBe(Accessible.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + Accessible.rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual('"New Tab": document'); + expect(renderedComponent.prop("title")).toEqual('"New Tab": document'); + }); +}); + +describe("Accessible - ButtonMenu", () => { + const stub = stubs.get("ButtonMenu"); + + it("selects Accessible Rep", () => { + expect(getRep(stub)).toBe(Accessible.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + Accessible.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + '"New to Nightly? Let’s get started.": buttonmenu' + ); + }); + + it("renders an inspect icon", () => { + const onInspectIconClick = jest.fn(); + const renderedComponent = shallow( + Accessible.rep({ + object: stub, + onInspectIconClick, + }) + ); + + const node = renderedComponent.find(".open-accessibility-inspector"); + node.simulate("click", { type: "click" }); + + expect(node.exists()).toBeTruthy(); + expect(onInspectIconClick.mock.calls).toHaveLength(1); + expect(onInspectIconClick.mock.calls[0][0]).toEqual(stub); + expect(onInspectIconClick.mock.calls[0][1].type).toEqual("click"); + }); + + it("calls the expected function when click is fired on Rep", () => { + const onAccessibleClick = jest.fn(); + const renderedComponent = shallow( + Accessible.rep({ + object: stub, + onAccessibleClick, + }) + ); + + renderedComponent.simulate("click"); + + expect(onAccessibleClick.mock.calls).toHaveLength(1); + }); + + it("calls the expected function when mouseout is fired on Rep", () => { + const onAccessibleMouseOut = jest.fn(); + const renderedComponent = shallow( + Accessible.rep({ + object: stub, + onAccessibleMouseOut, + }) + ); + + renderedComponent.simulate("mouseout"); + + expect(onAccessibleMouseOut.mock.calls).toHaveLength(1); + }); + + it("calls the expected function when mouseover is fired on Rep", () => { + const onAccessibleMouseOver = jest.fn(); + const renderedComponent = shallow( + Accessible.rep({ + object: stub, + onAccessibleMouseOver, + }) + ); + + renderedComponent.simulate("mouseover"); + + expect(onAccessibleMouseOver.mock.calls).toHaveLength(1); + expect(onAccessibleMouseOver.mock.calls[0][0]).toEqual(stub); + }); +}); + +describe("Accessible - No Name Accessible", () => { + const stub = stubs.get("NoName"); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + Accessible.rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("text container"); + expect(renderedComponent.prop("title")).toEqual("text container"); + expect(renderedComponent.find(".separator").exists()).toBeFalsy(); + expect(renderedComponent.find(".accessible-namer").exists()).toBeFalsy(); + }); +}); + +describe("Accessible - Disconnected accessible", () => { + const stub = stubs.get("DisconnectedAccessible"); + + it( + "renders no inspect icon when the accessible is not in the Accessible " + + "tree", + () => { + const onInspectIconClick = jest.fn(); + const renderedComponent = shallow( + Accessible.rep({ + object: stub, + onInspectIconClick, + }) + ); + + expect( + renderedComponent.find(".open-accessibility-inspector").exists() + ).toBeFalsy(); + } + ); +}); + +describe("Accessible - No Preview (not a valid grip)", () => { + const stub = stubs.get("NoPreview"); + + it("does not select Accessible Rep", () => { + expect(getRep(stub)).not.toBe(Accessible.rep); + }); +}); + +describe("Accessible - Accessible with long name", () => { + const stub = stubs.get("AccessibleWithLongName"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(Accessible.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + Accessible.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + `"${"a".repeat(1000)}": text leaf` + ); + }); + + it("renders with expected text content with name max length", () => { + const renderedComponent = shallow( + Accessible.rep({ + object: stub, + nameMaxLength: 20, + }) + ); + + expect(renderedComponent.text()).toEqual( + `"${"a".repeat(9)}${ELLIPSIS}${"a".repeat(8)}": text leaf` + ); + }); +}); + +describe("Accessible - Inspect icon title", () => { + const stub = stubs.get("PushButton"); + + it("renders with expected title", () => { + const inspectIconTitle = "inspect icon title"; + + const renderedComponent = shallow( + Accessible.rep({ + inspectIconTitle, + object: stub, + onInspectIconClick: jest.fn(), + }) + ); + + const iconNode = renderedComponent.find(".open-accessibility-inspector"); + expect(iconNode.prop("title")).toEqual(inspectIconTitle); + }); +}); + +describe("Accessible - Separator text", () => { + const stub = stubs.get("PushButton"); + + it("renders with expected title", () => { + const separatorText = " - "; + + const renderedComponent = shallow( + Accessible.rep({ + separatorText, + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual('"Search" - pushbutton'); + }); +}); + +describe("Accessible - Role first", () => { + const stub = stubs.get("PushButton"); + + it("renders with expected title", () => { + const renderedComponent = shallow( + Accessible.rep({ + roleFirst: true, + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual('pushbutton: "Search"'); + }); +}); + +describe("Accessible - Cursor style", () => { + const stub = stubs.get("PushButton"); + + it("renders with styled cursor", async () => { + const window = await createWindowForCursorTest(); + const attachTo = window.document.querySelector("#attach-to"); + const renderedComponent = mount( + Accessible.rep({ + object: stub, + onAccessibleClick: jest.fn(), + onInspectIconClick: jest.fn(), + }), + { + attachTo, + } + ); + + const objectNode = renderedComponent.getDOMNode(); + const iconNode = objectNode.querySelector(".open-accessibility-inspector"); + expect(renderedComponent.hasClass("clickable")).toBeTruthy(); + expect(window.getComputedStyle(objectNode).cursor).toEqual("pointer"); + expect(window.getComputedStyle(iconNode).cursor).toEqual("pointer"); + }); + + it("renders with unstyled cursor", async () => { + const window = await createWindowForCursorTest(); + const attachTo = window.document.querySelector("#attach-to"); + const renderedComponent = mount( + Accessible.rep({ + object: stub, + }), + { + attachTo, + } + ); + + const objectNode = renderedComponent.getDOMNode(); + expect(renderedComponent.hasClass("clickable")).toBeFalsy(); + expect(window.getComputedStyle(objectNode).cursor).toEqual(""); + }); +}); + +async function createWindowForCursorTest() { + const path = require("path"); + const css = await readTextFile( + path.resolve(__dirname, "../../../../reps/", "reps.css") + ); + const html = ` + <body> + <style>${css}</style> + <div id="attach-to"></div> + </body> + `; + + return new JSDOM(html).window; +} + +async function readTextFile(fileName) { + return new Promise((resolve, reject) => { + const fs = require("fs"); + fs.readFile(fileName, "utf8", (error, text) => { + if (error) { + reject(error); + } else { + resolve(text); + } + }); + }); +} diff --git a/devtools/client/shared/components/test/node/components/reps/accessor.test.js b/devtools/client/shared/components/test/node/components/reps/accessor.test.js new file mode 100644 index 0000000000..31499fea52 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/accessor.test.js @@ -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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { Accessor, Rep } = REPS; + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/accessor.js"); + +describe("Accessor - getter", () => { + const object = stubs.get("getter"); + + it("Rep correctly selects Accessor Rep", () => { + expect(getRep(object)).toBe(Accessor.rep); + }); + + it("Accessor rep has expected text content", () => { + const renderedComponent = shallow( + Rep({ object, shouldRenderTooltip: true }) + ); + expect(renderedComponent.text()).toEqual("Getter"); + expect(renderedComponent.prop("title")).toEqual("Getter"); + + const node = renderedComponent.find(".jump-definition"); + expect(node.exists()).toBeFalsy(); + }); +}); + +describe("Accessor - setter", () => { + const object = stubs.get("setter"); + + it("Rep correctly selects Accessor Rep", () => { + expect(getRep(object)).toBe(Accessor.rep); + }); + + it("Accessor rep has expected text content", () => { + const renderedComponent = shallow( + Rep({ object, shouldRenderTooltip: true }) + ); + expect(renderedComponent.text()).toEqual("Setter"); + expect(renderedComponent.prop("title")).toEqual("Setter"); + + const node = renderedComponent.find(".jump-definition"); + expect(node.exists()).toBeFalsy(); + }); +}); + +describe("Accessor - getter & setter", () => { + const object = stubs.get("getter setter"); + + it("Rep correctly selects Accessor Rep", () => { + expect(getRep(object)).toBe(Accessor.rep); + }); + + it("Accessor rep has expected text content", () => { + const renderedComponent = shallow( + Rep({ object, shouldRenderTooltip: true }) + ); + expect(renderedComponent.text()).toEqual("Getter & Setter"); + expect(renderedComponent.prop("title")).toEqual("Getter & Setter"); + + const node = renderedComponent.find(".jump-definition"); + expect(node.exists()).toBeFalsy(); + }); +}); + +describe("Accessor - Invoke getter", () => { + it("renders an icon for getter with onInvokeGetterButtonClick", () => { + const onInvokeGetterButtonClick = jest.fn(); + const object = stubs.get("getter"); + const renderedComponent = shallow( + Rep({ object, onInvokeGetterButtonClick }) + ); + + const node = renderedComponent.find(".invoke-getter"); + node.simulate("click", { + type: "click", + stopPropagation: () => {}, + }); + expect(node.prop("title")).toEqual("Invoke getter"); + expect(node.exists()).toBeTruthy(); + expect(onInvokeGetterButtonClick.mock.calls).toHaveLength(1); + }); + + it("does not render an icon for a setter only", () => { + const onInvokeGetterButtonClick = jest.fn(); + const object = stubs.get("setter"); + const renderedComponent = shallow( + Rep({ object, onInvokeGetterButtonClick }) + ); + expect(renderedComponent.text()).toEqual("Setter"); + + const node = renderedComponent.find(".jump-definition"); + expect(node.exists()).toBeFalsy(); + }); + + it("renders an icon for getter/setter with onInvokeGetterButtonClick", () => { + const onInvokeGetterButtonClick = jest.fn(); + const object = stubs.get("getter setter"); + const renderedComponent = shallow( + Rep({ object, onInvokeGetterButtonClick }) + ); + + const node = renderedComponent.find(".invoke-getter"); + node.simulate("click", { + type: "click", + stopPropagation: () => {}, + }); + + expect(node.exists()).toBeTruthy(); + expect(onInvokeGetterButtonClick.mock.calls).toHaveLength(1); + }); + + it("does not render an icon when the object has an evaluation", () => { + const onInvokeGetterButtonClick = jest.fn(); + const object = stubs.get("getter"); + const renderedComponent = shallow( + Rep({ + object, + onInvokeGetterButtonClick, + evaluation: { getterValue: "hello" }, + }) + ); + expect(renderedComponent.text()).toMatchSnapshot(); + + const node = renderedComponent.find(".invoke-getter"); + expect(node.exists()).toBeFalsy(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/array.test.js b/devtools/client/shared/components/test/node/components/reps/array.test.js new file mode 100644 index 0000000000..cd8cfb9d97 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/array.test.js @@ -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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { ArrayRep, Rep } = REPS; +const { maxLengthMap } = ArrayRep; + +describe("Array", () => { + it("selects Array Rep as expected", () => { + const stub = []; + expect(getRep(stub, undefined, true)).toBe(ArrayRep.rep); + }); + + it("renders empty array as expected", () => { + const object = []; + const renderRep = props => shallow(Rep({ object, noGrip: true, ...props })); + + const defaultOutput = "[]"; + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: undefined }).prop("title")).toBe("Array"); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).prop("title")).toBe("Array"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); + + it("renders basic array as expected", () => { + const object = [1, "foo", {}]; + const renderRep = props => shallow(Rep({ object, noGrip: true, ...props })); + + const defaultOutput = '[ 1, "foo", {} ]'; + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: undefined }).prop("title")).toBe("Array"); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("[…]"); + expect(renderRep({ mode: MODE.TINY }).prop("title")).toBe("Array"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); + + it("renders array with more than SHORT mode max props as expected", () => { + const object = Array(maxLengthMap.get(MODE.SHORT) + 1).fill("foo"); + const renderRep = props => shallow(Rep({ object, noGrip: true, ...props })); + + const defaultShortOutput = `[ ${Array(maxLengthMap.get(MODE.SHORT)) + .fill('"foo"') + .join(", ")}, … ]`; + expect(renderRep({ mode: undefined }).text()).toBe(defaultShortOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("[…]"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultShortOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe( + `[ ${Array(maxLengthMap.get(MODE.SHORT) + 1) + .fill('"foo"') + .join(", ")} ]` + ); + }); + + it("renders array with more than LONG mode maximum props as expected", () => { + const object = Array(maxLengthMap.get(MODE.LONG) + 1).fill("foo"); + const renderRep = props => shallow(Rep({ object, noGrip: true, ...props })); + + const defaultShortOutput = `[ ${Array(maxLengthMap.get(MODE.SHORT)) + .fill('"foo"') + .join(", ")}, … ]`; + expect(renderRep({ mode: undefined }).text()).toBe(defaultShortOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("[…]"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultShortOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe( + `[ ${Array(maxLengthMap.get(MODE.LONG)).fill('"foo"').join(", ")}, … ]` + ); + }); + + it("renders recursive array as expected", () => { + const object = [1]; + object.push(object); + const renderRep = props => shallow(Rep({ object, noGrip: true, ...props })); + + const defaultOutput = "[ 1, […] ]"; + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: undefined }).prop("title")).toBe("Array"); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("[…]"); + expect(renderRep({ mode: MODE.TINY }).prop("title")).toBe("Array"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); + + it("renders array containing an object as expected", () => { + const object = [ + { + p1: "s1", + p2: ["a1", "a2", "a3"], + p3: "s3", + p4: "s4", + }, + ]; + const renderRep = props => shallow(Rep({ object, noGrip: true, ...props })); + + const defaultOutput = "[ {…} ]"; + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: undefined }).prop("title")).toBe("Array"); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("[…]"); + expect(renderRep({ mode: MODE.TINY }).prop("title")).toBe("Array"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/attribute.test.js b/devtools/client/shared/components/test/node/components/reps/attribute.test.js new file mode 100644 index 0000000000..c13ab59857 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/attribute.test.js @@ -0,0 +1,44 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const { Attribute, Rep } = REPS; + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/attribute.js"); + +describe("Attribute", () => { + const stub = stubs.get("Attribute")._grip; + + it("Rep correctly selects Attribute Rep", () => { + expect(getRep(stub)).toBe(Attribute.rep); + }); + + it("Attribute rep has expected text content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + expect(renderedComponent.text()).toEqual( + 'class="autocomplete-suggestions"' + ); + expect(renderedComponent.prop("title")).toBe( + 'class="autocomplete-suggestions"' + ); + expectActorAttribute(renderedComponent, stub.actor); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/big-int.test.js b/devtools/client/shared/components/test/node/components/reps/big-int.test.js new file mode 100644 index 0000000000..1fa6cd0ee1 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/big-int.test.js @@ -0,0 +1,106 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { BigInt, Rep } = REPS; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/big-int.js"); + +describe("BigInt", () => { + describe("1n", () => { + const stub = stubs.get("1n"); + + it("correctly selects BigInt Rep for BigInt value", () => { + expect(getRep(stub)).toBe(BigInt.rep); + }); + + it("renders with expected text content for BigInt", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("1n"); + expect(renderedComponent.prop("title")).toBe("1n"); + }); + }); + + describe("-2n", () => { + const stub = stubs.get("-2n"); + + it("correctly selects BigInt Rep for negative BigInt value", () => { + expect(getRep(stub)).toBe(BigInt.rep); + }); + + it("renders with expected text content for negative BigInt", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("-2n"); + expect(renderedComponent.prop("title")).toBe("-2n"); + }); + }); + + describe("0n", () => { + const stub = stubs.get("0n"); + + it("correctly selects BigInt Rep for zero BigInt value", () => { + expect(getRep(stub)).toBe(BigInt.rep); + }); + + it("renders with expected text content for zero BigInt", () => { + const renderedComponent = shallow(Rep({ object: stub })); + expect(renderedComponent.text()).toEqual("0n"); + }); + }); + + describe("in objects", () => { + it("renders with expected text content in Array", () => { + const stub = stubs.get("[1n,-2n,0n]"); + const renderedComponent = shallow(Rep({ object: stub })); + expect(renderedComponent.text()).toEqual("Array(3) [ 1n, -2n, 0n ]"); + }); + + it("renders with expected text content in Set", () => { + const stub = stubs.get("new Set([1n,-2n,0n])"); + const renderedComponent = shallow(Rep({ object: stub })); + expect(renderedComponent.text()).toEqual("Set(3) [ 1n, -2n, 0n ]"); + }); + + it("renders with expected text content in Map", () => { + const stub = stubs.get("new Map([ [1n, -1n], [-2n, 0n], [0n, -2n]])"); + const renderedComponent = shallow(Rep({ object: stub })); + expect(renderedComponent.text()).toEqual( + "Map(3) { 1n → -1n, -2n → 0n, 0n → -2n }" + ); + }); + + it("renders with expected text content in Object", () => { + const stub = stubs.get("({simple: 1n, negative: -2n, zero: 0n})"); + const renderedComponent = shallow(Rep({ object: stub })); + expect(renderedComponent.text()).toEqual( + "Object { simple: 1n, negative: -2n, zero: 0n }" + ); + }); + + it("renders with expected text content in Promise", () => { + const stub = stubs.get("Promise.resolve(1n)"); + const renderedComponent = shallow(Rep({ object: stub })); + expect(renderedComponent.text()).toEqual( + 'Promise { <state>: "fulfilled", <value>: 1n }' + ); + }); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/comment-node.test.js b/devtools/client/shared/components/test/node/components/reps/comment-node.test.js new file mode 100644 index 0000000000..c46f7587aa --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/comment-node.test.js @@ -0,0 +1,80 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { Rep, CommentNode } = REPS; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/comment-node.js"); + +describe("CommentNode", () => { + const stub = stubs.get("Comment")._grip; + + it("selects CommentNode Rep correctly", () => { + expect(getRep(stub)).toEqual(CommentNode.rep); + }); + + it("renders with correct class names", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + + expect(renderedComponent.hasClass("objectBox theme-comment")).toBe(true); + }); + + it("renders with correct title tooltip", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.prop("title")).toBe( + "<!-- test\nand test\nand test\nand test\nand test\nand test\nand test -->" + ); + }); + + it("renders as expected", () => { + const object = stubs.get("Comment")._grip; + const renderRep = props => shallow(CommentNode.rep({ object, ...props })); + + let component = renderRep({ mode: undefined }); + expect(component.text()).toEqual( + "<!-- test\nand test\nand test\nan…d test\nand test\nand test -->" + ); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.TINY }); + expect(component.text()).toEqual( + "<!-- test\\nand test\\na… test\\nand test -->" + ); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER }); + expect(component.text()).toEqual( + "<!-- test\\nand test\\na… test\\nand test -->" + ); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.LONG }); + expect(component.text()).toEqual(`<!-- ${stub.preview.textContent} -->`); + expectActorAttribute(component, object.actor); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/date-time.test.js b/devtools/client/shared/components/test/node/components/reps/date-time.test.js new file mode 100644 index 0000000000..62a8557c3a --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/date-time.test.js @@ -0,0 +1,61 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const { DateTime, Rep } = REPS; + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/date-time.js"); + +describe("test DateTime", () => { + const stub = stubs.get("DateTime")._grip; + + it("selects DateTime as expected", () => { + expect(getRep(stub)).toBe(DateTime.rep); + }); + + it("renders DateTime as expected", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + const expectedDate = new Date( + "Date Thu Mar 31 2016 00:17:24 GMT+0300 (EAT)" + ).toString(); + + expect(renderedComponent.text()).toEqual(`Date ${expectedDate}`); + expect(renderedComponent.prop("title")).toEqual(`Date ${expectedDate}`); + expectActorAttribute(renderedComponent, stub.actor); + }); +}); + +describe("test invalid DateTime", () => { + const stub = stubs.get("InvalidDateTime")._grip; + + it("renders expected text for invalid date", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("Invalid Date"); + expect(renderedComponent.prop("title")).toEqual("Invalid Date"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/document-type.test.js b/devtools/client/shared/components/test/node/components/reps/document-type.test.js new file mode 100644 index 0000000000..f4709c6d5d --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/document-type.test.js @@ -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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const { DocumentType } = REPS; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/document-type.js"); + +describe("DocumentType", () => { + const stub = stubs.get("html"); + it("correctly selects DocumentType Rep", () => { + expect(getRep(stub)).toBe(DocumentType.rep); + }); + + it("renders with expected text content on html doctype", () => { + const renderedComponent = shallow( + DocumentType.rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("<!DOCTYPE html>"); + expect(renderedComponent.prop("title")).toEqual("<!DOCTYPE html>"); + expectActorAttribute(renderedComponent, stub.actor); + }); + + it("renders with expected text content on empty doctype", () => { + const unnamedStub = stubs.get("unnamed"); + const renderedComponent = shallow( + DocumentType.rep({ + object: unnamedStub, + shouldRenderTooltip: true, + }) + ); + expect(renderedComponent.text()).toEqual("<!DOCTYPE>"); + expect(renderedComponent.prop("title")).toEqual("<!DOCTYPE>"); + expectActorAttribute(renderedComponent, unnamedStub.actor); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/document.test.js b/devtools/client/shared/components/test/node/components/reps/document.test.js new file mode 100644 index 0000000000..36d2eced11 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/document.test.js @@ -0,0 +1,52 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const { Document } = REPS; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/document.js"); + +describe("Document", () => { + const stub = stubs.get("Document"); + it("correctly selects Document Rep", () => { + expect(getRep(stub)).toBe(Document.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + Document.rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual( + "HTMLDocument https://www.mozilla.org/en-US/firefox/new/" + ); + expect(renderedComponent.prop("title")).toEqual( + "HTMLDocument https://www.mozilla.org/en-US/firefox/new/" + ); + expectActorAttribute(renderedComponent, stub.actor); + }); + + it("renders location-less document with expected text content", () => { + const renderedComponent = shallow( + Document.rep({ + object: stubs.get("Location-less Document"), + }) + ); + + expect(renderedComponent.text()).toEqual("HTMLDocument"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/element-node.test.js b/devtools/client/shared/components/test/node/components/reps/element-node.test.js new file mode 100644 index 0000000000..966965e46a --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/element-node.test.js @@ -0,0 +1,668 @@ +/* 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/>. */ + +"use strict"; + +/* global jest, __dirname */ +const { mount, shallow } = require("enzyme"); +const { JSDOM } = require("jsdom"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { + MAX_ATTRIBUTE_LENGTH, +} = require("resource://devtools/client/shared/components/reps/reps/element-node.js"); +const { ElementNode } = REPS; +const { + expectActorAttribute, + getSelectableInInspectorGrips, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); +const { + ELLIPSIS, +} = require("resource://devtools/client/shared/components/reps/reps/rep-utils.js"); +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/element-node.js"); + +describe("ElementNode - BodyNode", () => { + const stub = stubs.get("BodyNode"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + '<body id="body-id" class="body-class">' + ); + expectActorAttribute(renderedComponent, stub.actor); + }); + + it("renders with expected text content in HEADER mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual( + '<body id="body-id" class="body-class">' + ); + expectActorAttribute(renderedComponent, stub.actor); + }); + + it("renders with expected text content on tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("body#body-id.body-class"); + expect(renderedComponent.prop("title")).toEqual("body#body-id.body-class"); + expectActorAttribute(renderedComponent, stub.actor); + }); +}); + +describe("ElementNode - DocumentElement", () => { + const stub = stubs.get("DocumentElement"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual('<html dir="ltr" lang="en-US">'); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("html"); + }); +}); + +describe("ElementNode - Node", () => { + const stub = stubs.get("Node"); + const grips = getSelectableInInspectorGrips(stub); + + it("has one node grip", () => { + expect(grips).toHaveLength(1); + }); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + '<input id="newtab-customize-button" class="bar baz" dir="ltr" ' + + 'title="Customize your New Tab page" value="foo" type="button">' + ); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual( + "input#newtab-customize-button.bar.baz" + ); + }); + + it("renders an inspect icon", () => { + const onInspectIconClick = jest.fn(); + const renderedComponent = shallow( + ElementNode.rep({ + object: stubs.get("Node"), + onInspectIconClick, + }) + ); + + const node = renderedComponent.find(".open-inspector"); + node.simulate("click", { type: "click" }); + + expect(node.exists()).toBeTruthy(); + expect(onInspectIconClick.mock.calls).toHaveLength(1); + expect(onInspectIconClick.mock.calls[0][0]).toEqual(stub); + expect(onInspectIconClick.mock.calls[0][1].type).toEqual("click"); + }); + + it("calls the expected function when click is fired on Rep", () => { + const onDOMNodeClick = jest.fn(); + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + onDOMNodeClick, + }) + ); + + renderedComponent.simulate("click"); + + expect(onDOMNodeClick.mock.calls).toHaveLength(1); + }); + + it("calls the expected function when mouseout is fired on Rep", () => { + const onDOMNodeMouseOut = jest.fn(); + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + onDOMNodeMouseOut, + }) + ); + + renderedComponent.simulate("mouseout"); + + expect(onDOMNodeMouseOut.mock.calls).toHaveLength(1); + expect(onDOMNodeMouseOut.mock.calls[0][0]).toEqual(stub); + }); + + it("calls the expected function when mouseover is fired on Rep", () => { + const onDOMNodeMouseOver = jest.fn(); + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + onDOMNodeMouseOver, + }) + ); + + renderedComponent.simulate("mouseover"); + + expect(onDOMNodeMouseOver.mock.calls).toHaveLength(1); + expect(onDOMNodeMouseOver.mock.calls[0][0]).toEqual(stub); + }); +}); + +describe("ElementNode - Leading and trailing spaces class name", () => { + const stub = stubs.get("NodeWithLeadingAndTrailingSpacesClassName"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + '<body id="nightly-whatsnew" class=" html-ltr ">' + ); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("body#nightly-whatsnew.html-ltr"); + }); +}); + +describe("ElementNode - Node with spaces in the class name", () => { + const stub = stubs.get("NodeWithSpacesInClassName"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("body.a.b.c"); + }); +}); + +describe("ElementNode - Node without attributes", () => { + const stub = stubs.get("NodeWithoutAttributes"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual("<p>"); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("p"); + }); +}); + +describe("ElementNode - Node with many attributes", () => { + const stub = stubs.get("LotsOfAttributes"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + '<p id="lots-of-attributes" a="" b="" c="" d="" e="" f="" g="" ' + + 'h="" i="" j="" k="" l="" m="" n="">' + ); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("p#lots-of-attributes"); + }); +}); + +describe("ElementNode - SVG Node", () => { + const stub = stubs.get("SvgNode"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + '<clipPath id="clip" class="svg-element">' + ); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("clipPath#clip.svg-element"); + }); +}); + +describe("ElementNode - SVG Node in XHTML", () => { + const stub = stubs.get("SvgNodeInXHTML"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + '<svg:circle class="svg-element" cx="0" cy="0" r="5">' + ); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("svg:circle.svg-element"); + }); +}); + +describe("ElementNode - Disconnected node", () => { + const stub = stubs.get("DisconnectedNode"); + + it("renders no inspect icon when the node is not in the DOM tree", () => { + const onInspectIconClick = jest.fn(); + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + onInspectIconClick, + }) + ); + + expect(renderedComponent.find(".open-inspector").exists()).toBeFalsy(); + }); +}); + +describe("ElementNode - Element with longString attribute", () => { + const stub = stubs.get("NodeWithLongStringAttribute"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + `<div data-test="${"a".repeat(MAX_ATTRIBUTE_LENGTH)}${ELLIPSIS}">` + ); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("div"); + }); +}); + +describe("ElementNode - Element attribute cropping", () => { + it("renders no title attribute for short attribute", () => { + const stub = stubs.get("NodeWithSpacesInClassName"); + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + expect(renderedComponent.first().find("span.attrValue").prop("title")).toBe( + undefined + ); + }); + + it("renders partial value for long attribute", () => { + const stub = stubs.get("NodeWithLongAttribute"); + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual( + '<p data-test="aaaaaaaaaaaaaaaaaaaaaaaa…aaaaaaaaaaaaaaaaaaaaaaa">' + ); + expect(renderedComponent.first().find("span.attrValue").prop("title")).toBe( + "a".repeat(100) + ); + }); + + it("renders partial attribute for LongString", () => { + const stub = stubs.get("NodeWithLongStringAttribute"); + + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual( + '<div data-test="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa…">' + ); + expect(renderedComponent.first().find("span.attrValue").prop("title")).toBe( + "a".repeat(1000) + ); + }); +}); + +describe("ElementNode - : Marker pseudo element", () => { + const stub = stubs.get("MarkerPseudoElement"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("::marker"); + expect(renderedComponent.prop("title")).toEqual("::marker"); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("::marker"); + expect(renderedComponent.prop("title")).toEqual("::marker"); + }); +}); + +describe("ElementNode - : Before pseudo element", () => { + const stub = stubs.get("BeforePseudoElement"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual("::before"); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("::before"); + }); +}); + +describe("ElementNode - After pseudo element", () => { + const stub = stubs.get("AfterPseudoElement"); + + it("selects ElementNode Rep", () => { + expect(getRep(stub)).toBe(ElementNode.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual("::after"); + }); + + it("renders with expected text content in tiny mode", () => { + const renderedComponent = shallow( + ElementNode.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("::after"); + }); +}); + +describe("ElementNode - Inspect icon title", () => { + const stub = stubs.get("Node"); + + it("renders with expected title", () => { + const inspectIconTitle = "inspect icon title"; + + const renderedComponent = shallow( + ElementNode.rep({ + inspectIconTitle, + object: stub, + shouldRenderTooltip: true, + onInspectIconClick: jest.fn(), + }) + ); + + const iconNode = renderedComponent.find(".open-inspector"); + expect(iconNode.prop("title")).toEqual(inspectIconTitle); + }); +}); + +describe("ElementNode - Cursor style", () => { + const stub = stubs.get("Node"); + + it("renders with styled cursor", async () => { + const window = await createWindowForCursorTest(); + const attachTo = window.document.querySelector("#attach-to"); + const renderedComponent = mount( + ElementNode.rep({ + object: stub, + onDOMNodeClick: jest.fn(), + onInspectIconClick: jest.fn(), + }), + { + attachTo, + } + ); + + const objectNode = renderedComponent.getDOMNode(); + const iconNode = objectNode.querySelector(".open-inspector"); + expect(renderedComponent.hasClass("clickable")).toBeTruthy(); + expect(window.getComputedStyle(objectNode).cursor).toEqual("pointer"); + expect(window.getComputedStyle(iconNode).cursor).toEqual("pointer"); + }); + + it("renders with unstyled cursor", async () => { + const window = await createWindowForCursorTest(); + const attachTo = window.document.querySelector("#attach-to"); + const renderedComponent = mount( + ElementNode.rep({ + object: stub, + }), + { + attachTo, + } + ); + + const objectNode = renderedComponent.getDOMNode(); + expect(renderedComponent.hasClass("clickable")).toBeFalsy(); + expect(window.getComputedStyle(objectNode).cursor).toEqual(""); + }); +}); + +async function createWindowForCursorTest() { + const path = require("path"); + const css = await readTextFile( + path.resolve(__dirname, "../../../../reps/", "reps.css") + ); + const html = ` + <body> + <style>${css}</style> + <div id="attach-to"></div> + </body> + `; + + return new JSDOM(html).window; +} + +async function readTextFile(fileName) { + return new Promise((resolve, reject) => { + const fs = require("fs"); + fs.readFile(fileName, "utf8", (error, text) => { + if (error) { + reject(error); + } else { + resolve(text); + } + }); + }); +} diff --git a/devtools/client/shared/components/test/node/components/reps/error.test.js b/devtools/client/shared/components/test/node/components/reps/error.test.js new file mode 100644 index 0000000000..3486a6df44 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/error.test.js @@ -0,0 +1,869 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const { ErrorRep } = REPS; +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/error.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); + +describe("Error - Simple error", () => { + // Test object = `new Error("Error message")` + const stub = stubs.get("SimpleError"); + + it("correctly selects Error Rep for Error object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for simple error", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + shouldRenderTooltip: true, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + expect(renderedComponent.prop("title")).toBe('Error: "Error message"'); + expectActorAttribute(renderedComponent, stub.actor); + }); + + it("renders with expected text for simple error in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("Error"); + }); + + it("renders with expected text for simple error in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual("Error"); + }); + + it("renders with error type and preview message when in short mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stubs.get("MultilineStackError"), + mode: MODE.SHORT, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders with error type only when customFormat prop isn't set", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stubs.get("MultilineStackError"), + mode: MODE.SHORT, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders with error type only when depth is > 0", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stubs.get("MultilineStackError"), + customFormat: true, + depth: 1, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); +}); + +describe("Error - Multi line stack error", () => { + /* + * Test object = ` + * function errorFoo() { + * errorBar(); + * } + * function errorBar() { + * console.log(new Error("bar")); + * } + * errorFoo();` + */ + const stub = stubs.get("MultilineStackError"); + + it("correctly selects the Error Rep for Error object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for Error object", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders expected text for simple multiline error in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("Error"); + }); + + it("renders expected text for simple multiline error in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual("Error"); + }); +}); + +describe("Error - Error without stacktrace", () => { + const stub = stubs.get("ErrorWithoutStacktrace"); + + it("correctly selects the Error Rep for Error object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for Error object", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent.text()).toEqual("Error: Error message"); + }); + + it("renders expected text for error without stacktrace in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("Error"); + }); +}); + +describe("Error - Eval error", () => { + // Test object = `new EvalError("EvalError message")` + const stub = stubs.get("EvalError"); + + it("correctly selects the Error Rep for EvalError object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for an EvalError", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders with expected text for an EvalError in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("EvalError"); + }); + + it("renders with expected text for an EvalError in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual("EvalError"); + }); +}); + +describe("Error - Internal error", () => { + // Test object = `new InternalError("InternalError message")` + const stub = stubs.get("InternalError"); + + it("correctly selects the Error Rep for InternalError object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for an InternalError", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders with expected text for an InternalError in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("InternalError"); + }); + + it("renders with expected text for an InternalError in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual("InternalError"); + }); +}); + +describe("Error - Range error", () => { + // Test object = `new RangeError("RangeError message")` + const stub = stubs.get("RangeError"); + + it("correctly selects the Error Rep for RangeError object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for RangeError", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders with expected text for RangeError in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("RangeError"); + }); + + it("renders with expected text for RangeError in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual("RangeError"); + }); +}); + +describe("Error - Reference error", () => { + // Test object = `new ReferenceError("ReferenceError message"` + const stub = stubs.get("ReferenceError"); + + it("correctly selects the Error Rep for ReferenceError object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for ReferenceError", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders with expected text for ReferenceError in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("ReferenceError"); + }); + + it("renders with expected text for ReferenceError in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual("ReferenceError"); + }); +}); + +describe("Error - Syntax error", () => { + // Test object = `new SyntaxError("SyntaxError message"` + const stub = stubs.get("SyntaxError"); + + it("correctly selects the Error Rep for SyntaxError object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for SyntaxError", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders with expected text for SyntaxError in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("SyntaxError"); + }); + + it("renders with expected text for SyntaxError in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual("SyntaxError"); + }); +}); + +describe("Error - Type error", () => { + // Test object = `new TypeError("TypeError message"` + const stub = stubs.get("TypeError"); + + it("correctly selects the Error Rep for TypeError object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for TypeError", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders with expected text for TypeError in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("TypeError"); + }); + + it("renders with expected text for TypeError in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual("TypeError"); + }); +}); + +describe("Error - URI error", () => { + // Test object = `new URIError("URIError message")` + const stub = stubs.get("URIError"); + + it("correctly selects the Error Rep for URIError object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for URIError", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders with expected text for URIError in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("URIError"); + }); + + it("renders with expected text for URIError in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual("URIError"); + }); +}); + +describe("Error - DOMException", () => { + const stub = stubs.get("DOMException"); + + it("correctly selects Error Rep for Error object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text for DOMException", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent.text()).toEqual( + "DOMException: 'foo;()bar!' is not a valid selector" + ); + }); + + it("renders with expected text for DOMException in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("DOMException"); + }); + + it("renders with expected text for DOMException in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent.text()).toEqual("DOMException"); + }); +}); + +describe("Error - base-loader.sys.mjs", () => { + const stub = stubs.get("base-loader Error"); + + it("renders as expected without mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders as expected in tiny mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); + + it("renders as expected in HEADER mode", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.HEADER, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); +}); + +describe("Error - longString stacktrace", () => { + const stub = stubs.get("longString stack Error"); + + it("renders as expected", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); +}); + +describe("Error - longString stacktrace - cut-off location", () => { + const stub = stubs.get("longString stack Error - cut-off location"); + + it("renders as expected", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); +}); + +describe("Error - stacktrace location click", () => { + it("Calls onViewSourceInDebugger with the expected arguments", () => { + const onViewSourceInDebugger = jest.fn(); + const object = stubs.get("base-loader Error"); + + const renderedComponent = shallow( + ErrorRep.rep({ + object, + onViewSourceInDebugger, + customFormat: true, + }) + ); + + const locations = renderedComponent.find(".objectBox-stackTrace-location"); + expect(locations.exists()).toBeTruthy(); + + expect(locations.first().prop("title")).toBe( + "View source in debugger → " + + "resource://devtools/client/debugger-client.js:856:9" + ); + locations.first().simulate("click", { + type: "click", + stopPropagation: () => {}, + }); + + expect(onViewSourceInDebugger.mock.calls).toHaveLength(1); + let mockCall = onViewSourceInDebugger.mock.calls[0][0]; + expect(mockCall.url).toEqual( + "resource://devtools/client/debugger-client.js" + ); + expect(mockCall.line).toEqual(856); + expect(mockCall.column).toEqual(9); + + expect(locations.last().prop("title")).toBe( + "View source in debugger → " + + "resource://devtools/shared/ThreadSafeDevToolsUtils.js:109:14" + ); + locations.last().simulate("click", { + type: "click", + stopPropagation: () => {}, + }); + + expect(onViewSourceInDebugger.mock.calls).toHaveLength(2); + mockCall = onViewSourceInDebugger.mock.calls[1][0]; + expect(mockCall.url).toEqual( + "resource://devtools/shared/ThreadSafeDevToolsUtils.js" + ); + expect(mockCall.line).toEqual(109); + expect(mockCall.column).toEqual(14); + }); + + it("Does not call onViewSourceInDebugger on excluded urls", () => { + const onViewSourceInDebugger = jest.fn(); + const object = stubs.get("URIError"); + + const renderedComponent = shallow( + ErrorRep.rep({ + object, + onViewSourceInDebugger, + customFormat: true, + }) + ); + + const locations = renderedComponent.find(".objectBox-stackTrace-location"); + expect(locations.exists()).toBeTruthy(); + expect(locations.first().prop("title")).toBe(undefined); + + locations.first().simulate("click", { + type: "click", + stopPropagation: () => {}, + }); + + expect(onViewSourceInDebugger.mock.calls).toHaveLength(0); + }); + + it("Does not throw when onViewSourceInDebugger props is not provided", () => { + const object = stubs.get("base-loader Error"); + + const renderedComponent = shallow( + ErrorRep.rep({ + object, + customFormat: true, + }) + ); + + const locations = renderedComponent.find(".objectBox-stackTrace-location"); + expect(locations.exists()).toBeTruthy(); + expect(locations.first().prop("title")).toBe(undefined); + + locations.first().simulate("click", { + type: "click", + stopPropagation: () => {}, + }); + }); +}); + +describe("Error - renderStacktrace prop", () => { + it("uses renderStacktrace prop when provided", () => { + const stub = stubs.get("MultilineStackError"); + + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + renderStacktrace: frames => { + return frames.map(frame => + dom.li( + { className: "frame" }, + `Function ${frame.functionName} called from ${frame.filename}:${frame.lineNumber}:${frame.columnNumber}\n` + ) + ); + }, + customFormat: true, + }) + ); + expect(renderedComponent).toMatchSnapshot(); + }); + + it("uses renderStacktrace with longString errors too", () => { + const stub = stubs.get("longString stack Error - cut-off location"); + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + renderStacktrace: frames => { + return frames.map(frame => + dom.li( + { className: "frame" }, + `Function ${frame.functionName} called from ${frame.filename}:${frame.lineNumber}:${frame.columnNumber}\n` + ) + ); + }, + customFormat: true, + }) + ); + expect(renderedComponent).toMatchSnapshot(); + }); +}); + +describe("Error - Error with V8-like stack", () => { + // Test object: + // x = new Error("BOOM"); + // x.stack = "Error: BOOM\ngetAccount@http://moz.com/script.js:1:2"; + const stub = stubs.get("Error with V8-like stack"); + + it("correctly selects Error Rep for Error object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + expectActorAttribute(renderedComponent, stub.actor); + }); +}); + +describe("Error - Error with invalid stack", () => { + // Test object: + // x = new Error("bad stack"); + // x.stack = "bar\nbaz\nfoo\n\n\n\n\n\n\n"; + const stub = stubs.get("Error with invalid stack"); + + it("correctly selects Error Rep for Error object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + expectActorAttribute(renderedComponent, stub.actor); + }); +}); + +describe("Error - Error with undefined-grip stack", () => { + // Test object: + // x = new Error("sd"); + // x.stack = undefined; + const stub = stubs.get("Error with undefined-grip stack"); + + it("correctly selects Error Rep for Error object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + expectActorAttribute(renderedComponent, stub.actor); + }); +}); + +describe("Error - Error with undefined-grip name", () => { + // Test object: + // x = new Error(""); + // x.name = undefined; + const stub = stubs.get("Error with undefined-grip name"); + + it("correctly selects Error Rep for Error object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + expect(renderedComponent).toMatchSnapshot(); + + const tinyRenderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(tinyRenderedComponent).toMatchSnapshot(); + }); +}); + +describe("Error - Error with undefined-grip message", () => { + // Test object: + // x = new Error(""); + // x.message = undefined; + const stub = stubs.get("Error with undefined-grip message"); + + it("correctly selects Error Rep for Error object", () => { + expect(getRep(stub)).toBe(ErrorRep.rep); + }); + + it("renders with expected text", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + expect(renderedComponent).toMatchSnapshot(); + + const tinyRenderedComponent = shallow( + ErrorRep.rep({ + object: stub, + mode: MODE.TINY, + }) + ); + + expect(tinyRenderedComponent).toMatchSnapshot(); + }); +}); + +describe("Error - Error with stack having frames with multiple @", () => { + const stub = stubs.get("Error with stack having frames with multiple @"); + + it("renders with expected text for Error object", () => { + const renderedComponent = shallow( + ErrorRep.rep({ + object: stub, + customFormat: true, + }) + ); + + expect(renderedComponent).toMatchSnapshot(); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/event.test.js b/devtools/client/shared/components/test/node/components/reps/event.test.js new file mode 100644 index 0000000000..fe0f6b5601 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/event.test.js @@ -0,0 +1,160 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { Event } = REPS; +const { + expectActorAttribute, + getSelectableInInspectorGrips, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/event.js"); + +describe("Event - beforeprint", () => { + const object = stubs.get("testEvent"); + + it("correctly selects Event Rep", () => { + expect(getRep(object)).toBe(Event.rep); + }); + + it("renders with expected text", () => { + const renderedComponent = shallow(Event.rep({ object })); + expect(renderedComponent.text()).toEqual( + "beforeprint { target: Window, isTrusted: true, currentTarget: Window, " + + "… }" + ); + expectActorAttribute(renderedComponent, object.actor); + }); +}); + +describe("Event - keyboard event", () => { + const object = stubs.get("testKeyboardEvent"); + + it("correctly selects Event Rep", () => { + expect(getRep(object)).toBe(Event.rep); + }); + + it("renders with expected text", () => { + const renderRep = props => shallow(Event.rep({ object, ...props })); + expect(renderRep().text()).toEqual( + 'keyup { target: body, key: "Control", charCode: 0, … }' + ); + expect(renderRep({ mode: MODE.LONG }).text()).toEqual( + 'keyup { target: body, key: "Control", charCode: 0, keyCode: 17 }' + ); + }); +}); + +describe("Event - keyboard event with modifiers", () => { + const object = stubs.get("testKeyboardEventWithModifiers"); + + it("correctly selects Event Rep", () => { + expect(getRep(object)).toBe(Event.rep); + }); + + it("renders with expected text", () => { + const renderRep = props => shallow(Event.rep({ object, ...props })); + expect(renderRep({ mode: MODE.LONG }).text()).toEqual( + 'keyup Meta-Shift { target: body, key: "M", charCode: 0, keyCode: 77 }' + ); + }); +}); + +describe("Event - message event", () => { + const object = stubs.get("testMessageEvent"); + + it("correctly selects Event Rep", () => { + expect(getRep(object)).toBe(Event.rep); + }); + + it("renders with expected text", () => { + const renderRep = props => shallow(Event.rep({ object, ...props })); + expect(renderRep().text()).toEqual( + 'message { target: Window, isTrusted: false, data: "test data", … }' + ); + expect(renderRep({ mode: MODE.LONG }).text()).toEqual( + 'message { target: Window, isTrusted: false, data: "test data", ' + + 'origin: "null", lastEventId: "", source: Window, ports: Array, ' + + "currentTarget: Window, eventPhase: 2, bubbles: false, … }" + ); + }); +}); + +describe("Event - mouse event", () => { + const object = stubs.get("testMouseEvent"); + const renderRep = props => shallow(Event.rep({ object, ...props })); + + const grips = getSelectableInInspectorGrips(object); + + it("has stub with one node grip", () => { + expect(grips).toHaveLength(1); + }); + + it("correctly selects Event Rep", () => { + expect(getRep(object)).toBe(Event.rep); + }); + + it("renders with expected text", () => { + expect(renderRep({ shouldRenderTooltip: true }).text()).toEqual( + "click { target: div#test, clientX: 62, clientY: 18, … }" + ); + expect(renderRep({ shouldRenderTooltip: true }).prop("title")).toEqual( + "click" + ); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).text() + ).toEqual( + "click { target: div#test, buttons: 0, clientX: 62, clientY: 18, " + + "layerX: 0, layerY: 0 }" + ); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).prop("title") + ).toEqual("click"); + }); + + it("renders an inspect icon", () => { + const onInspectIconClick = jest.fn(); + const renderedComponent = renderRep({ onInspectIconClick }); + + const node = renderedComponent.find(".open-inspector"); + node.simulate("click", { type: "click" }); + + expect(node.exists()).toBeTruthy(); + expect(onInspectIconClick.mock.calls).toHaveLength(1); + expect(onInspectIconClick.mock.calls[0][0]).toEqual(grips[0]); + expect(onInspectIconClick.mock.calls[0][1].type).toEqual("click"); + }); + + it("calls the expected function when mouseout is fired on Rep", () => { + const onDOMNodeMouseOut = jest.fn(); + const wrapper = renderRep({ onDOMNodeMouseOut }); + + const node = wrapper.find(".objectBox-node"); + node.simulate("mouseout"); + + expect(onDOMNodeMouseOut.mock.calls).toHaveLength(1); + expect(onDOMNodeMouseOut.mock.calls[0][0]).toEqual(grips[0]); + }); + + it("calls the expected function when mouseover is fired on Rep", () => { + const onDOMNodeMouseOver = jest.fn(); + const wrapper = renderRep({ onDOMNodeMouseOver }); + + const node = wrapper.find(".objectBox-node"); + node.simulate("mouseover"); + + expect(onDOMNodeMouseOver.mock.calls).toHaveLength(1); + expect(onDOMNodeMouseOver.mock.calls[0][0]).toEqual(grips[0]); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/failure.test.js b/devtools/client/shared/components/test/node/components/reps/failure.test.js new file mode 100644 index 0000000000..6f31ae5687 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/failure.test.js @@ -0,0 +1,66 @@ +/* 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/>. */ + +"use strict"; + +/* global beforeAll, afterAll */ +const { shallow } = require("enzyme"); + +const { + REPS, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { Rep } = REPS; + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/failure.js"); + +let originalConsoleError; +beforeAll(() => { + // Let's override the console.error function so we don't get an error message + // in the jest output for the expected exception. + originalConsoleError = window.console.error; + window.console.error = () => {}; +}); + +describe("test Failure", () => { + const stub = stubs.get("Failure"); + + it("Fallback rendering has expected text content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + expect(renderedComponent.text()).toEqual("Invalid object"); + }); + + it("Fallback array rendering has expected text content", () => { + const renderedComponent = shallow( + Rep({ + object: { + type: "object", + class: "Array", + actor: "server1.conn0.obj337", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 3, + items: [1, stub, 2], + }, + }, + }) + ); + expect(renderedComponent.text()).toEqual( + "Array(3) [ 1, Invalid object, 2 ]" + ); + }); +}); + +afterAll(() => { + // Reverting the override. + window.console.error = originalConsoleError; +}); diff --git a/devtools/client/shared/components/test/node/components/reps/function.test.js b/devtools/client/shared/components/test/node/components/reps/function.test.js new file mode 100644 index 0000000000..b9ad70dd26 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/function.test.js @@ -0,0 +1,584 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ +const { shallow } = require("enzyme"); +const { + REPS, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { Func } = REPS; +const { getFunctionName } = Func; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/function.js"); +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); +const renderRep = (object, props) => { + return shallow(Func.rep({ object, ...props })); +}; + +describe("Function - Named", () => { + // Test declaration: `function testName() { let innerVar = "foo" }` + const object = stubs.get("Named"); + + it("renders named function as expected", () => { + expect( + renderRep(object, { mode: undefined, shouldRenderTooltip: true }).text() + ).toBe("function testName()"); + expect( + renderRep(object, { mode: undefined, shouldRenderTooltip: true }).prop( + "title" + ) + ).toBe("function testName()"); + expect( + renderRep( + { ...object, parameterNames: [] }, + { shouldRenderTooltip: true } + ).text() + ).toBe("function testName()"); + expect( + renderRep( + { ...object, parameterNames: [] }, + { shouldRenderTooltip: true } + ).prop("title") + ).toBe("function testName()"); + expect( + renderRep( + { ...object, parameterNames: ["a"] }, + { shouldRenderTooltip: true } + ).text() + ).toBe("function testName(a)"); + expect( + renderRep( + { ...object, parameterNames: ["a"] }, + { shouldRenderTooltip: true } + ).prop("title") + ).toBe("function testName(a)"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { shouldRenderTooltip: true } + ).text() + ).toBe("function testName(a, b, c)"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { shouldRenderTooltip: true } + ).prop("title") + ).toBe("function testName(a, b, c)"); + expect( + renderRep(object, { mode: MODE.TINY, shouldRenderTooltip: true }).text() + ).toBe("testName()"); + expect( + renderRep(object, { mode: MODE.TINY, shouldRenderTooltip: true }).prop( + "title" + ) + ).toBe("testName()"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { mode: MODE.TINY, shouldRenderTooltip: true } + ).text() + ).toBe("testName(a, b, c)"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { mode: MODE.TINY, shouldRenderTooltip: true } + ).prop("title") + ).toBe("testName(a, b, c)"); + + expectActorAttribute(renderRep(object), object.actor); + }); +}); + +describe("Function - User named", () => { + // Test declaration: `function testName() { let innerVar = "foo" }` + const object = stubs.get("UserNamed"); + + it("renders user named function as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe( + "function testUserName()" + ); + expect(renderRep({ ...object, parameterNames: [] }).text()).toBe( + "function testUserName()" + ); + expect(renderRep({ ...object, parameterNames: ["a"] }).text()).toBe( + "function testUserName(a)" + ); + expect( + renderRep({ ...object, parameterNames: ["a", "b", "c"] }).text() + ).toBe("function testUserName(a, b, c)"); + expect( + renderRep(object, { + mode: MODE.TINY, + }).text() + ).toBe("testUserName()"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { + mode: MODE.TINY, + } + ).text() + ).toBe("testUserName(a, b, c)"); + }); +}); + +describe("Function - Var named", () => { + // Test declaration: `let testVarName = function() { }` + const object = stubs.get("VarNamed"); + + it("renders var named function as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe( + "function testVarName()" + ); + expect(renderRep({ ...object, parameterNames: [] }).text()).toBe( + "function testVarName()" + ); + expect(renderRep({ ...object, parameterNames: ["a"] }).text()).toBe( + "function testVarName(a)" + ); + expect( + renderRep({ ...object, parameterNames: ["a", "b", "c"] }).text() + ).toBe("function testVarName(a, b, c)"); + expect( + renderRep(object, { + mode: MODE.TINY, + }).text() + ).toBe("testVarName()"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { + mode: MODE.TINY, + } + ).text() + ).toBe("testVarName(a, b, c)"); + }); +}); + +describe("Function - Anonymous", () => { + // Test declaration: `() => {}` + const object = stubs.get("Anon"); + + it("renders anonymous function as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe("function ()"); + expect(renderRep({ ...object, parameterNames: [] }).text()).toBe( + "function ()" + ); + expect(renderRep({ ...object, parameterNames: ["a"] }).text()).toBe( + "function (a)" + ); + expect( + renderRep({ ...object, parameterNames: ["a", "b", "c"] }).text() + ).toBe("function (a, b, c)"); + expect( + renderRep(object, { + mode: MODE.TINY, + }).text() + ).toBe("()"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { + mode: MODE.TINY, + } + ).text() + ).toBe("(a, b, c)"); + }); +}); + +describe("Function - Long name", () => { + // eslint-disable-next-line max-len + // Test declaration: `let f = function loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong() { }` + const object = stubs.get("LongName"); + const functionName = + "looooooooooooooooooooooooooooooooooooooooooooooo" + + "oo\u2026ooooooooooooooooooooooooooooooooooooooooooooooong"; + + it("renders long name function as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe( + `function ${functionName}()` + ); + expect(renderRep({ ...object, parameterNames: [] }).text()).toBe( + `function ${functionName}()` + ); + expect(renderRep({ ...object, parameterNames: ["a"] }).text()).toBe( + `function ${functionName}(a)` + ); + expect( + renderRep({ ...object, parameterNames: ["a", "b", "c"] }).text() + ).toBe(`function ${functionName}(a, b, c)`); + expect( + renderRep(object, { + mode: MODE.TINY, + }).text() + ).toBe(`${functionName}()`); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { + mode: MODE.TINY, + } + ).text() + ).toBe(`${functionName}(a, b, c)`); + }); +}); + +describe("Function - Async function", () => { + const object = stubs.get("AsyncFunction"); + + it("renders async function as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe( + "async function waitUntil2017()" + ); + expect(renderRep(object, { mode: MODE.TINY }).text()).toBe( + "async waitUntil2017()" + ); + expect(renderRep(object, { mode: MODE.SHORT }).text()).toBe( + "async function waitUntil2017()" + ); + expect(renderRep(object, { mode: MODE.LONG }).text()).toBe( + "async function waitUntil2017()" + ); + expect(renderRep({ ...object, parameterNames: [] }).text()).toBe( + "async function waitUntil2017()" + ); + expect(renderRep({ ...object, parameterNames: ["a"] }).text()).toBe( + "async function waitUntil2017(a)" + ); + expect( + renderRep({ ...object, parameterNames: ["a", "b", "c"] }).text() + ).toBe("async function waitUntil2017(a, b, c)"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { + mode: MODE.TINY, + } + ).text() + ).toBe("async waitUntil2017(a, b, c)"); + }); +}); + +describe("Function - Anonymous async function", () => { + const object = stubs.get("AnonAsyncFunction"); + + it("renders anonymous async function as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe( + "async function ()" + ); + expect(renderRep(object, { mode: MODE.TINY }).text()).toBe("async ()"); + expect(renderRep(object, { mode: MODE.SHORT }).text()).toBe( + "async function ()" + ); + expect(renderRep(object, { mode: MODE.LONG }).text()).toBe( + "async function ()" + ); + expect(renderRep({ ...object, parameterNames: [] }).text()).toBe( + "async function ()" + ); + expect(renderRep({ ...object, parameterNames: ["a"] }).text()).toBe( + "async function (a)" + ); + expect( + renderRep({ ...object, parameterNames: ["a", "b", "c"] }).text() + ).toBe("async function (a, b, c)"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { + mode: MODE.TINY, + } + ).text() + ).toBe("async (a, b, c)"); + }); +}); + +describe("Function - Generator function", () => { + const object = stubs.get("GeneratorFunction"); + + it("renders generator function as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe( + "function* fib()" + ); + expect(renderRep(object, { mode: MODE.TINY }).text()).toBe("* fib()"); + expect(renderRep(object, { mode: MODE.SHORT }).text()).toBe( + "function* fib()" + ); + expect(renderRep(object, { mode: MODE.LONG }).text()).toBe( + "function* fib()" + ); + expect(renderRep({ ...object, parameterNames: [] }).text()).toBe( + "function* fib()" + ); + expect(renderRep({ ...object, parameterNames: ["a"] }).text()).toBe( + "function* fib(a)" + ); + expect( + renderRep({ ...object, parameterNames: ["a", "b", "c"] }).text() + ).toBe("function* fib(a, b, c)"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { + mode: MODE.TINY, + } + ).text() + ).toBe("* fib(a, b, c)"); + }); +}); + +describe("Function - Anonymous generator function", () => { + const object = stubs.get("AnonGeneratorFunction"); + + it("renders anonymous generator function as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe("function* ()"); + expect(renderRep(object, { mode: MODE.TINY }).text()).toBe("* ()"); + expect(renderRep(object, { mode: MODE.SHORT }).text()).toBe("function* ()"); + expect(renderRep(object, { mode: MODE.LONG }).text()).toBe("function* ()"); + expect(renderRep({ ...object, parameterNames: [] }).text()).toBe( + "function* ()" + ); + expect(renderRep({ ...object, parameterNames: ["a"] }).text()).toBe( + "function* (a)" + ); + expect( + renderRep({ ...object, parameterNames: ["a", "b", "c"] }).text() + ).toBe("function* (a, b, c)"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { + mode: MODE.TINY, + } + ).text() + ).toBe("* (a, b, c)"); + }); +}); + +describe("Function - Jump to definition", () => { + it("renders an icon when onViewSourceInDebugger props is provided", async () => { + let onViewSourceInDebugger; + const onViewSourceCalled = new Promise(resolve => { + onViewSourceInDebugger = jest.fn(resolve); + }); + const object = stubs.get("getRandom"); + const renderedComponent = renderRep(object, { + onViewSourceInDebugger, + }); + + const node = renderedComponent.find(".jump-definition"); + node.simulate("click", { + type: "click", + stopPropagation: () => {}, + }); + await onViewSourceCalled; + + expect(node.exists()).toBeTruthy(); + expect(onViewSourceInDebugger.mock.calls).toHaveLength(1); + expect(onViewSourceInDebugger.mock.calls[0][0]).toEqual(object.location); + }); + + it("calls recordTelemetryEvent when jump to definition icon clicked", () => { + const onViewSourceInDebugger = jest.fn(); + const recordTelemetryEvent = jest.fn(); + const object = stubs.get("getRandom"); + const renderedComponent = renderRep(object, { + onViewSourceInDebugger, + recordTelemetryEvent, + }); + + const node = renderedComponent.find(".jump-definition"); + node.simulate("click", { + type: "click", + stopPropagation: () => {}, + }); + + expect(node.exists()).toBeTruthy(); + expect(recordTelemetryEvent.mock.calls).toHaveLength(1); + expect(recordTelemetryEvent.mock.calls[0][0]).toEqual("jump_to_definition"); + }); + + it("no icon when onViewSourceInDebugger props not provided", () => { + const object = stubs.get("getRandom"); + const renderedComponent = renderRep(object); + + const node = renderedComponent.find(".jump-definition"); + expect(node.exists()).toBeFalsy(); + }); + + it("does not render an icon when the object has no location", () => { + const object = { + ...stubs.get("getRandom"), + location: null, + }; + + const renderedComponent = renderRep(object, { + onViewSourceInDebugger: () => {}, + }); + + const node = renderedComponent.find(".jump-definition"); + expect(node.exists()).toBeFalsy(); + }); + + it("does not render an icon when the object has no url location", () => { + const object = { + ...stubs.get("getRandom"), + }; + object.location = { ...object.location, url: null }; + const renderedComponent = renderRep(object, { + onViewSourceInDebugger: () => {}, + }); + + const node = renderedComponent.find(".jump-definition"); + expect(node.exists()).toBeFalsy(); + }); + + it("no icon when function was declared in console input", () => { + const object = stubs.get("EvaledInDebuggerFunction"); + const renderedComponent = renderRep(object, { + onViewSourceInDebugger: () => {}, + }); + + const node = renderedComponent.find(".jump-definition"); + expect(node.exists()).toBeFalsy(); + }); +}); + +describe("Function - Simplify name", () => { + const cases = { + defaultCase: [["define", "define"]], + + objectProperty: [ + ["z.foz", "foz"], + ["z.foz/baz", "baz"], + ["z.foz/baz/y.bay", "bay"], + ["outer/x.fox.bax.nx", "nx"], + ["outer/fow.baw", "baw"], + ["fromYUI._attach", "_attach"], + ["Y.ClassNameManager</getClassName", "getClassName"], + ["orion.textview.TextView</addHandler", "addHandler"], + ["this.eventPool_.createObject", "createObject"], + ], + + arrayProperty: [ + ["this.eventPool_[createObject]", "createObject"], + ["jQuery.each(^)/jQuery.fn[o]", "o"], + ["viewport[get+D]", "get+D"], + ["arr[0]", "0"], + ], + + functionProperty: [ + ["fromYUI._attach/<.", "_attach"], + ["Y.ClassNameManager<", "ClassNameManager"], + ["fromExtJS.setVisible/cb<", "cb"], + ["fromDojo.registerWin/<", "registerWin"], + ], + + annonymousProperty: [["jQuery.each(^)", "each"]], + }; + + Object.keys(cases).forEach(type => { + for (const [kase, expected] of cases[type]) { + it(`${type} - ${kase}`, () => + expect(getFunctionName({ displayName: kase })).toEqual(expected)); + } + }); +}); +describe("Function - Two properties with same displayName", () => { + const object = stubs.get("ObjectProperty"); + + it("renders object properties as expected", () => { + expect( + renderRep(object, { mode: undefined, functionName: "$" }).text() + ).toBe("function $:jQuery()"); + expect( + renderRep({ ...object, parameterNames: [] }, { functionName: "$" }).text() + ).toBe("function $:jQuery()"); + expect( + renderRep( + { ...object, parameterNames: ["a"] }, + { functionName: "$" } + ).text() + ).toBe("function $:jQuery(a)"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { + functionName: "$", + } + ).text() + ).toBe("function $:jQuery(a, b, c)"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { + mode: MODE.TINY, + functionName: "$", + } + ).text() + ).toBe("$:jQuery(a, b, c)"); + }); +}); + +describe("Function - Class constructor", () => { + const object = stubs.get("EmptyClass"); + + it("renders empty class as expected", () => { + expect( + renderRep(object, { mode: undefined, shouldRenderTooltip: true }).text() + ).toBe("class EmptyClass {}"); + expect( + renderRep(object, { mode: undefined, shouldRenderTooltip: true }).prop( + "title" + ) + ).toBe("class EmptyClass {}"); + }); + + it("renders empty class in MODE.TINY as expected", () => { + expect( + renderRep(object, { mode: MODE.TINY, shouldRenderTooltip: true }).text() + ).toBe("class EmptyClass"); + expect( + renderRep(object, { mode: MODE.TINY, shouldRenderTooltip: true }).prop( + "title" + ) + ).toBe("class EmptyClass"); + }); + + it("renders class with constructor as expected", () => { + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { shouldRenderTooltip: true } + ).text() + ).toBe("class EmptyClass { constructor(a, b, c) }"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { shouldRenderTooltip: true } + ).prop("title") + ).toBe("class EmptyClass { constructor(a, b, c) }"); + }); + + it("renders class with constructor in MODE.TINY as expected", () => { + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { mode: MODE.TINY, shouldRenderTooltip: true } + ).text() + ).toBe("class EmptyClass"); + expect( + renderRep( + { ...object, parameterNames: ["a", "b", "c"] }, + { mode: MODE.TINY, shouldRenderTooltip: true } + ).prop("title") + ).toBe("class EmptyClass"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/grip-array.test.js b/devtools/client/shared/components/test/node/components/reps/grip-array.test.js new file mode 100644 index 0000000000..b69bcb1dc5 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/grip-array.test.js @@ -0,0 +1,755 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ +const { shallow } = require("enzyme"); +const { + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const GripArray = require("resource://devtools/client/shared/components/reps/reps/grip-array.js"); +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const { + expectActorAttribute, + getSelectableInInspectorGrips, + getGripLengthBubbleText, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); +const { maxLengthMap } = GripArray; + +function shallowRenderRep(object, props = {}) { + return shallow( + GripArray.rep({ + object, + ...props, + }) + ); +} + +describe("GripArray - basic", () => { + const object = stubs.get("testBasic"); + + it("correctly selects GripArray Rep", () => { + expect(getRep(object)).toBe(GripArray.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + const defaultOutput = `Array${length} []`; + + let component = renderRep({ mode: undefined, shouldRenderTooltip: true }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe("Array"); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }); + expect(component.text()).toBe("[]"); + expect(component.prop("title")).toBe("Array"); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }); + expect(component.text()).toBe("Array"); + expect(component.prop("title")).toBe("Array"); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe("Array"); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe("Array"); + expectActorAttribute(component, object.actor); + }); +}); + +describe("GripArray - max props", () => { + const object = stubs.get("testMaxProps"); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + + let length = getGripLengthBubbleText(object); + const defaultOutput = `Array${length} [ 1, "foo", {} ]`; + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + // Check the custom title with nested objects to make sure nested objects + // are not displayed with their parent's title. + expect( + renderRep({ + mode: MODE.LONG, + title: "CustomTitle", + }).text() + ).toBe(`CustomTitle${length} [ 1, "foo", {} ]`); + }); +}); + +describe("GripArray - more than short mode max props", () => { + const object = stubs.get("testMoreThanShortMaxProps"); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + + const shortLength = maxLengthMap.get(MODE.SHORT); + const shortContent = Array(shortLength).fill('"test string"').join(", "); + const longContent = Array(shortLength + 1) + .fill('"test string"') + .join(", "); + const defaultOutput = `Array${length} [ ${shortContent}, … ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + expect(renderRep({ mode: MODE.LONG }).text()).toBe( + `Array${length} [ ${longContent} ]` + ); + }); +}); + +describe("GripArray - more than long mode max props", () => { + const object = stubs.get("testMoreThanLongMaxProps"); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + + const shortLength = maxLengthMap.get(MODE.SHORT); + const longLength = maxLengthMap.get(MODE.LONG); + const shortContent = Array(shortLength).fill('"test string"').join(", "); + const defaultOutput = `Array${length} [ ${shortContent}, … ]`; + const longContent = Array(longLength).fill('"test string"').join(", "); + + expect( + renderRep({ mode: undefined, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: undefined, shouldRenderTooltip: true }).prop("title") + ).toBe("Array"); + expect( + renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }).text() + ).toBe(`${length} […]`); + expect( + renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }).prop("title") + ).toBe("Array"); + expect( + renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }).text() + ).toBe(`Array${length}`); + expect( + renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }).prop("title") + ).toBe("Array"); + expect( + renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }).prop("title") + ).toBe("Array"); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).text() + ).toBe(`Array${length} [ ${longContent}, … ]`); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).prop("title") + ).toBe("Array"); + }); +}); + +describe("GripArray - recursive array", () => { + const object = stubs.get("testRecursiveArray"); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + const childArrayLength = getGripLengthBubbleText(object.preview.items[0], { + mode: MODE.TINY, + }); + + const defaultOutput = `Array${length} [ ${childArrayLength} […] ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("GripArray - preview limit", () => { + const object = stubs.get("testPreviewLimit"); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + + const shortOutput = `Array${length} [ 0, 1, 2, … ]`; + const longOutput = `Array${length} [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, … ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(shortOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(shortOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); +}); + +describe("GripArray - empty slots", () => { + it("renders an array with empty slots only as expected", () => { + const object = stubs.get("Array(5)"); + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + const defaultOutput = `Array${length} [ <5 empty slots> ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + const longOutput = `Array${length} [ <5 empty slots> ]`; + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); + + it("one empty slot at the beginning as expected", () => { + const object = stubs.get("[,1,2,3]"); + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ <1 empty slot>, 1, 2, … ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + const longOutput = `Array${length} [ <1 empty slot>, 1, 2, 3 ]`; + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); + + it("multiple consecutive empty slots at the beginning as expected", () => { + const object = stubs.get("[,,,3,4,5]"); + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ <3 empty slots>, 3, 4, … ]`; + const longOutput = `Array${length} [ <3 empty slots>, 3, 4, 5 ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); + + it("one empty slot in the middle as expected", () => { + const object = stubs.get("[0,1,,3,4,5]"); + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ 0, 1, <1 empty slot>, … ]`; + const longOutput = `Array${length} [ 0, 1, <1 empty slot>, 3, 4, 5 ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); + + it("successive empty slots in the middle as expected", () => { + const object = stubs.get("[0,1,,,,5]"); + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ 0, 1, <3 empty slots>, … ]`; + const longOutput = `Array${length} [ 0, 1, <3 empty slots>, 5 ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); + + it("non successive single empty slots as expected", () => { + const object = stubs.get("[0,,2,,4,5]"); + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ 0, <1 empty slot>, 2, … ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe( + `Array${length} [ 0, <1 empty slot>, 2, <1 empty slot>, 4, 5 ]` + ); + }); + + it("multiple multi-slot holes as expected", () => { + const object = stubs.get("[0,,,3,,,,7,8]"); + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ 0, <2 empty slots>, 3, … ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe( + `Array${length} [ 0, <2 empty slots>, 3, <3 empty slots>, 7, 8 ]` + ); + }); + + it("a single slot hole at the end as expected", () => { + const object = stubs.get("[0,1,2,3,4,,]"); + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ 0, 1, 2, … ]`; + const longOutput = `Array${length} [ 0, 1, 2, 3, 4, <1 empty slot> ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); + + it("multiple consecutive empty slots at the end as expected", () => { + const object = stubs.get("[0,1,2,,,,]"); + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ 0, 1, 2, … ]`; + const longOutput = `Array${length} [ 0, 1, 2, <3 empty slots> ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); +}); + +describe("GripArray - NamedNodeMap", () => { + const object = stubs.get("testNamedNodeMap"); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + + const defaultOutput = + `NamedNodeMap${length} ` + + '[ class="myclass", cellpadding="7", border="3" ]'; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`NamedNodeMap${length}`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe( + `NamedNodeMap${length}` + ); + + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("GripArray - NodeList", () => { + const object = stubs.get("testNodeList"); + const grips = getSelectableInInspectorGrips(object); + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + + it("renders as expected", () => { + const defaultOutput = + `NodeList${length} [ button#btn-1.btn.btn-log, ` + + "button#btn-2.btn.btn-err, button#btn-3.btn.btn-count ]"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`NodeList${length}`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`NodeList${length}`); + + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); + + it("has 3 node grip", () => { + expect(grips).toHaveLength(3); + }); + + it("calls the expected function on mouseover", () => { + const onDOMNodeMouseOver = jest.fn(); + const wrapper = renderRep({ onDOMNodeMouseOver }); + const node = wrapper.find(".objectBox-node"); + + node.at(0).simulate("mouseover"); + node.at(1).simulate("mouseover"); + node.at(2).simulate("mouseover"); + + expect(onDOMNodeMouseOver.mock.calls).toHaveLength(3); + expect(onDOMNodeMouseOver.mock.calls[0][0]).toBe(grips[0]); + expect(onDOMNodeMouseOver.mock.calls[1][0]).toBe(grips[1]); + expect(onDOMNodeMouseOver.mock.calls[2][0]).toBe(grips[2]); + }); + + it("calls the expected function on mouseout", () => { + const onDOMNodeMouseOut = jest.fn(); + const wrapper = renderRep({ onDOMNodeMouseOut }); + const node = wrapper.find(".objectBox-node"); + + node.at(0).simulate("mouseout"); + node.at(1).simulate("mouseout"); + node.at(2).simulate("mouseout"); + + expect(onDOMNodeMouseOut.mock.calls).toHaveLength(3); + expect(onDOMNodeMouseOut.mock.calls[0][0]).toBe(grips[0]); + expect(onDOMNodeMouseOut.mock.calls[1][0]).toBe(grips[1]); + expect(onDOMNodeMouseOut.mock.calls[2][0]).toBe(grips[2]); + }); + + it("calls the expected function on click", () => { + const onInspectIconClick = jest.fn(); + const wrapper = renderRep({ onInspectIconClick }); + const node = wrapper.find(".open-inspector"); + + node.at(0).simulate("click"); + node.at(1).simulate("click"); + node.at(2).simulate("click"); + + expect(onInspectIconClick.mock.calls).toHaveLength(3); + expect(onInspectIconClick.mock.calls[0][0]).toBe(grips[0]); + expect(onInspectIconClick.mock.calls[1][0]).toBe(grips[1]); + expect(onInspectIconClick.mock.calls[2][0]).toBe(grips[2]); + }); + + it("no inspect icon when nodes are not connected to the DOM tree", () => { + const renderedComponentWithoutInspectIcon = shallowRenderRep( + stubs.get("testDisconnectedNodeList") + ); + const node = renderedComponentWithoutInspectIcon.find(".open-inspector"); + expect(node.exists()).toBe(false); + }); +}); + +describe("GripArray - DocumentFragment", () => { + const object = stubs.get("testDocumentFragment"); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + + let length = getGripLengthBubbleText(object); + const defaultOutput = + `DocumentFragment${length} [ li#li-0.list-element, ` + + "li#li-1.list-element, li#li-2.list-element, … ]"; + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe( + `DocumentFragment${length}` + ); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe( + `DocumentFragment${length}` + ); + + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + const longOutput = + `DocumentFragment${length} [ ` + + "li#li-0.list-element, li#li-1.list-element, li#li-2.list-element, " + + "li#li-3.list-element, li#li-4.list-element ]"; + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); +}); + +describe("GripArray - Items not in preview", () => { + const object = stubs.get("testItemsNotInPreview"); + + it("correctly selects GripArray Rep", () => { + expect(getRep(object)).toBe(GripArray.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + const defaultOutput = `Array${length} [ … ]`; + + let component = renderRep({ mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + component = renderRep({ mode: MODE.TINY }); + expect(component.text()).toBe(`${length} […]`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER }); + expect(component.text()).toBe(`Array${length}`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.LONG }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + }); +}); + +describe("GripArray - Set", () => { + it("correctly selects GripArray Rep", () => { + const object = stubs.get("new Set([1,2,3,4])"); + expect(getRep(object)).toBe(GripArray.rep); + }); + + it("renders short sets as expected", () => { + const object = stubs.get("new Set([1,2,3,4])"); + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + const defaultOutput = `Set${length} [ 1, 2, 3, … ]`; + + let component = renderRep({ mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.TINY }); + expect(component.text()).toBe(`Set${length}`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER }); + expect(component.text()).toBe(`Set${length}`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + component = renderRep({ mode: MODE.LONG }); + expect(component.text()).toBe(`Set${length} [ 1, 2, 3, 4 ]`); + expectActorAttribute(component, object.actor); + }); + + it("renders larger sets as expected", () => { + const object = stubs.get("new Set([0,1,2,…,19])"); + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + const defaultOutput = `Set${length} [ 0, 1, 2, … ]`; + + let component = renderRep({ mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.TINY }); + expect(component.text()).toBe(`Set${length}`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER }); + expect(component.text()).toBe(`Set${length}`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + component = renderRep({ mode: MODE.LONG }); + expect(component.text()).toBe( + `Set${length} [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, … ]` + ); + expectActorAttribute(component, object.actor); + }); +}); + +describe("GripArray - WeakSet", () => { + it("correctly selects GripArray Rep", () => { + const object = stubs.get( + "new WeakSet(document.querySelectorAll('button:nth-child(3n)'))" + ); + expect(getRep(object)).toBe(GripArray.rep); + }); + + it("renders short WeakSets as expected", () => { + const object = stubs.get( + "new WeakSet(document.querySelectorAll('button:nth-child(3n)'))" + ); + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + const defaultOutput = `WeakSet${length} [ button, button, button, … ]`; + + let component = renderRep({ mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.TINY }); + expect(component.text()).toBe(`WeakSet${length}`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER }); + expect(component.text()).toBe(`WeakSet${length}`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + component = renderRep({ mode: MODE.LONG }); + expect(component.text()).toBe( + `WeakSet${length} [ button, button, button, button ]` + ); + expectActorAttribute(component, object.actor); + }); + + it("renders larger WeakSets as expected", () => { + const object = stubs.get( + "new WeakSet(document.querySelectorAll('div, button'))" + ); + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + const defaultOutput = `WeakSet${length} [ button, button, button, … ]`; + + let component = renderRep({ mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.TINY }); + expect(component.text()).toBe(`WeakSet${length}`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER }); + expect(component.text()).toBe(`WeakSet${length}`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.LONG }); + expect(component.text()).toBe(`WeakSet(12) [ ${"button, ".repeat(10)}… ]`); + expectActorAttribute(component, object.actor); + }); +}); + +describe("GripArray - DOMTokenList", () => { + const object = stubs.get("DOMTokenList"); + + it("correctly selects GripArray Rep", () => { + expect(getRep(object)).toBe(GripArray.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const length = getGripLengthBubbleText(object); + const defaultOutput = `DOMTokenList${length} []`; + + let component = renderRep({ mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.TINY }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER }); + expect(component.text()).toBe(`DOMTokenList${length}`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.LONG }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + }); +}); + +describe("GripArray - accessor", () => { + it("renders an array with getter as expected", () => { + const object = stubs.get("TestArrayWithGetter"); + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ Getter ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + const longOutput = `Array${length} [ Getter ]`; + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); + + it("renders an array with setter as expected", () => { + const object = stubs.get("TestArrayWithSetter"); + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ Setter ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + const longOutput = `Array${length} [ Setter ]`; + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); + + it("renders an array with getter and setter as expected", () => { + const object = stubs.get("TestArrayWithGetterAndSetter"); + const renderRep = props => shallowRenderRep(object, props); + let length = getGripLengthBubbleText(object); + + const defaultOutput = `Array${length} [ Getter & Setter ]`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`${length} […]`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Array${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + length = getGripLengthBubbleText(object, { mode: MODE.LONG }); + const longOutput = `Array${length} [ Getter & Setter ]`; + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/grip-entry.test.js b/devtools/client/shared/components/test/node/components/reps/grip-entry.test.js new file mode 100644 index 0000000000..cdfcab755e --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/grip-entry.test.js @@ -0,0 +1,191 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { GripEntry } = REPS; +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { + createGripMapEntry, + getGripLengthBubbleText, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-entry.js"); +const nodeStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/element-node.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); + +const renderRep = (object, mode, props) => { + return shallow( + GripEntry.rep({ + object, + mode, + ...props, + }) + ); +}; + +describe("GripEntry - simple", () => { + const stub = stubs.get("A → 0"); + + it("Rep correctly selects GripEntry Rep", () => { + expect(getRep(stub)).toBe(GripEntry.rep); + }); + + it("GripEntry rep has expected text content", () => { + const renderedComponent = renderRep(stub); + expect(renderedComponent.text()).toEqual("A → 0"); + }); +}); + +describe("GripEntry - createGripMapEntry", () => { + it("return the expected object", () => { + const entry = createGripMapEntry("A", 0); + expect(entry).toEqual(stubs.get("A → 0")); + }); +}); + +describe("GripEntry - complex", () => { + it("Handles complex objects as key and value", () => { + let stub = gripArrayStubs.get("testBasic"); + let length = getGripLengthBubbleText(stub); + let entry = createGripMapEntry("A", stub); + expect(renderRep(entry, MODE.TINY).text()).toEqual("A → []"); + expect(renderRep(entry, MODE.SHORT).text()).toEqual( + `A → Array${length} []` + ); + expect(renderRep(entry, MODE.LONG).text()).toEqual(`A → Array${length} []`); + + entry = createGripMapEntry(stub, "A"); + expect(renderRep(entry, MODE.TINY).text()).toEqual('[] → "A"'); + expect(renderRep(entry, MODE.SHORT).text()).toEqual( + `Array${length} [] → "A"` + ); + expect(renderRep(entry, MODE.LONG).text()).toEqual( + `Array${length} [] → "A"` + ); + + stub = gripArrayStubs.get("testMaxProps"); + length = getGripLengthBubbleText(stub, { mode: MODE.TINY }); + entry = createGripMapEntry("A", stub); + expect(renderRep(entry, MODE.TINY).text()).toEqual(`A → ${length} […]`); + length = getGripLengthBubbleText(stub); + expect(renderRep(entry, MODE.SHORT).text()).toEqual( + `A → Array${length} [ 1, "foo", {} ]` + ); + length = getGripLengthBubbleText(stub, { mode: MODE.LONG }); + expect(renderRep(entry, MODE.LONG).text()).toEqual( + `A → Array${length} [ 1, "foo", {} ]` + ); + + entry = createGripMapEntry(stub, "A"); + length = getGripLengthBubbleText(stub, { mode: MODE.TINY }); + expect(renderRep(entry, MODE.TINY).text()).toEqual(`${length} […] → "A"`); + length = getGripLengthBubbleText(stub, { mode: MODE.SHORT }); + expect(renderRep(entry, MODE.SHORT).text()).toEqual( + `Array${length} [ 1, "foo", {} ] → "A"` + ); + length = getGripLengthBubbleText(stub, { mode: MODE.LONG }); + expect(renderRep(entry, MODE.LONG).text()).toEqual( + `Array${length} [ 1, "foo", {} ] → "A"` + ); + + stub = gripArrayStubs.get("testMoreThanShortMaxProps"); + length = getGripLengthBubbleText(stub); + entry = createGripMapEntry("A", stub); + length = getGripLengthBubbleText(stub, { mode: MODE.TINY }); + expect(renderRep(entry, MODE.TINY).text()).toEqual(`A → ${length} […]`); + length = getGripLengthBubbleText(stub, { mode: MODE.SHORT }); + expect(renderRep(entry, MODE.SHORT).text()).toEqual( + `A → Array${length} [ "test string", "test string", "test string", … ]` + ); + length = getGripLengthBubbleText(stub, { mode: MODE.LONG }); + expect(renderRep(entry, MODE.LONG).text()).toEqual( + `A → Array${length} [ "test string", "test string", "test string",\ + "test string" ]` + ); + + entry = createGripMapEntry(stub, "A"); + length = getGripLengthBubbleText(stub, { mode: MODE.TINY }); + expect(renderRep(entry, MODE.TINY).text()).toEqual(`${length} […] → "A"`); + length = getGripLengthBubbleText(stub, { mode: MODE.SHORT }); + expect(renderRep(entry, MODE.SHORT).text()).toEqual( + `Array${length} [ "test string", "test string", "test string", … ] → "A"` + ); + length = getGripLengthBubbleText(stub, { mode: MODE.LONG }); + expect(renderRep(entry, MODE.LONG).text()).toEqual( + `Array${length} [ "test string", "test string", "test string", ` + + '"test string" ] → "A"' + ); + }); + + it("Handles Element Nodes as key and value", () => { + const stub = nodeStubs.get("Node"); + + const onInspectIconClick = jest.fn(); + const onDOMNodeMouseOut = jest.fn(); + const onDOMNodeMouseOver = jest.fn(); + + let entry = createGripMapEntry("A", stub); + let renderedComponent = renderRep(entry, MODE.TINY, { + onInspectIconClick, + onDOMNodeMouseOut, + onDOMNodeMouseOver, + }); + expect(renderRep(entry, MODE.TINY).text()).toEqual( + "A → input#newtab-customize-button.bar.baz" + ); + + let node = renderedComponent.find(".objectBox-node"); + let icon = node.find(".open-inspector"); + icon.simulate("click", { type: "click" }); + expect(icon.exists()).toBeTruthy(); + expect(onInspectIconClick.mock.calls).toHaveLength(1); + expect(onInspectIconClick.mock.calls[0][0]).toEqual(stub); + expect(onInspectIconClick.mock.calls[0][1].type).toEqual("click"); + + node.simulate("mouseout"); + expect(onDOMNodeMouseOut.mock.calls).toHaveLength(1); + expect(onDOMNodeMouseOut.mock.calls[0][0]).toEqual(stub); + + node.simulate("mouseover"); + expect(onDOMNodeMouseOver.mock.calls).toHaveLength(1); + expect(onDOMNodeMouseOver.mock.calls[0][0]).toEqual(stub); + + entry = createGripMapEntry(stub, "A"); + renderedComponent = renderRep(entry, MODE.TINY, { + onInspectIconClick, + onDOMNodeMouseOut, + onDOMNodeMouseOver, + }); + expect(renderRep(entry, MODE.TINY).text()).toEqual( + 'input#newtab-customize-button.bar.baz → "A"' + ); + + node = renderedComponent.find(".objectBox-node"); + icon = node.find(".open-inspector"); + icon.simulate("click", { type: "click" }); + expect(node.exists()).toBeTruthy(); + expect(onInspectIconClick.mock.calls).toHaveLength(2); + expect(onInspectIconClick.mock.calls[1][0]).toEqual(stub); + expect(onInspectIconClick.mock.calls[1][1].type).toEqual("click"); + + node.simulate("mouseout"); + expect(onDOMNodeMouseOut.mock.calls).toHaveLength(2); + expect(onDOMNodeMouseOut.mock.calls[1][0]).toEqual(stub); + + node.simulate("mouseover"); + expect(onDOMNodeMouseOver.mock.calls).toHaveLength(2); + expect(onDOMNodeMouseOver.mock.calls[1][0]).toEqual(stub); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/grip-map.test.js b/devtools/client/shared/components/test/node/components/reps/grip-map.test.js new file mode 100644 index 0000000000..729d912a21 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/grip-map.test.js @@ -0,0 +1,390 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ + +const { shallow } = require("enzyme"); +const { + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const GripMap = require("resource://devtools/client/shared/components/reps/reps/grip-map.js"); +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); +const { + expectActorAttribute, + getSelectableInInspectorGrips, + getMapLengthBubbleText, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); +const { maxLengthMap, getLength } = GripMap; + +function shallowRenderRep(object, props = {}) { + return shallow( + GripMap.rep({ + object, + ...props, + }) + ); +} + +describe("GripMap - empty map", () => { + const object = stubs.get("testEmptyMap"); + + it("correctly selects GripMap Rep", () => { + expect(getRep(object)).toBe(GripMap.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const length = getMapLengthBubbleText(object); + const defaultOutput = `Map${length}`; + + let component = renderRep({ mode: undefined, shouldRenderTooltip: true }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(`Map(${getLength(object)})`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(`Map(${getLength(object)})`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(`Map(${getLength(object)})`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(`Map(${getLength(object)})`); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(`Map(${getLength(object)})`); + expectActorAttribute(component, object.actor); + }); +}); + +describe("GripMap - Symbol-keyed Map", () => { + // Test object: + // `new Map([[Symbol("a"), "value-a"], [Symbol("b"), "value-b"]])` + const object = stubs.get("testSymbolKeyedMap"); + + it("correctly selects GripMap Rep", () => { + expect(getRep(object)).toBe(GripMap.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + let length = getMapLengthBubbleText(object); + const out = `Map${length} { Symbol("a") → "value-a", Symbol("b") → "value-b" }`; + + expect(renderRep({ mode: undefined }).text()).toBe(out); + + length = getMapLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`Map${length}`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Map${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(out); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(out); + }); +}); + +describe("GripMap - WeakMap", () => { + // Test object: `new WeakMap([[{a: "key-a"}, "value-a"]])` + const object = stubs.get("testWeakMap"); + + it("correctly selects GripMap Rep", () => { + expect(getRep(object)).toBe(GripMap.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + let length = getMapLengthBubbleText(object); + const defaultOutput = `WeakMap${length} { {…} → "value-a" }`; + expect( + renderRep({ mode: undefined, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: undefined, shouldRenderTooltip: true }).prop("title") + ).toBe(`WeakMap(${getLength(object)})`); + + length = getMapLengthBubbleText(object, { mode: MODE.TINY }); + expect( + renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }).text() + ).toBe(`WeakMap${length}`); + expect( + renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }).prop("title") + ).toBe(`WeakMap(${getLength(object)})`); + + length = getMapLengthBubbleText(object, { mode: MODE.HEADER }); + expect( + renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }).text() + ).toBe(`WeakMap${length}`); + expect( + renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }).prop("title") + ).toBe(`WeakMap(${getLength(object)})`); + + expect( + renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }).prop("title") + ).toBe(`WeakMap(${getLength(object)})`); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).prop("title") + ).toBe(`WeakMap(${getLength(object)})`); + + length = getMapLengthBubbleText(object, { mode: MODE.LONG }); + + expect( + renderRep({ + mode: MODE.LONG, + title: "CustomTitle", + }).text() + ).toBe(`CustomTitle${length} { {…} → "value-a" }`); + expect( + renderRep({ + mode: MODE.LONG, + title: "CustomTitle", + shouldRenderTooltip: true, + }).prop("title") + ).toBe(`CustomTitle(${getLength(object)})`); + }); +}); + +describe("GripMap - max entries", () => { + // Test object: + // `new Map([["key-a","value-a"], ["key-b","value-b"], ["key-c","value-c"]])` + const object = stubs.get("testMaxEntries"); + + it("correctly selects GripMap Rep", () => { + expect(getRep(object)).toBe(GripMap.rep); + }); + + it("renders as expected", () => { + let length = getMapLengthBubbleText(object); + const renderRep = props => shallowRenderRep(object, props); + const out = + `Map${length} { ` + + '"key-a" → "value-a", "key-b" → "value-b", "key-c" → "value-c" }'; + + expect(renderRep({ mode: undefined }).text()).toBe(out); + + length = getMapLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`Map${length}`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Map${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(out); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(out); + }); +}); + +describe("GripMap - more than max entries", () => { + // Test object = `new Map( + // [["key-0", "value-0"], …, ["key-100", "value-100"]]}` + const object = stubs.get("testMoreThanMaxEntries"); + + it("correctly selects GripMap Rep", () => { + expect(getRep(object)).toBe(GripMap.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + let length = getMapLengthBubbleText(object); + const defaultOutput = + `Map${length} { "key-0" → "value-0", ` + + '"key-1" → "value-1", "key-2" → "value-2", … }'; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getMapLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`Map${length}`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Map${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + const longString = Array.from({ length: maxLengthMap.get(MODE.LONG) }).map( + (_, i) => `"key-${i}" → "value-${i}"` + ); + expect(renderRep({ mode: MODE.LONG }).text()).toBe( + `Map(${maxLengthMap.get(MODE.LONG) + 1}) { ${longString.join(", ")}, … }` + ); + }); +}); + +describe("GripMap - uninteresting entries", () => { + // Test object: + // `new Map([["key-a",null], ["key-b",undefined], ["key-c","value-c"], + // ["key-d",4]])` + const object = stubs.get("testUninterestingEntries"); + + it("correctly selects GripMap Rep", () => { + expect(getRep(object)).toBe(GripMap.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + let length = getMapLengthBubbleText(object); + const defaultOutput = + `Map${length} { "key-a" → null, ` + + '"key-c" → "value-c", "key-d" → 4, … }'; + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + + length = getMapLengthBubbleText(object, { mode: MODE.TINY }); + expect(renderRep({ mode: MODE.TINY }).text()).toBe(`Map${length}`); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe(`Map${length}`); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + length = getMapLengthBubbleText(object, { mode: MODE.LONG }); + const longOutput = + `Map${length} { "key-a" → null, "key-b" → undefined, ` + + '"key-c" → "value-c", "key-d" → 4 }'; + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); +}); + +describe("GripMap - Node-keyed entries", () => { + const object = stubs.get("testNodeKeyedMap"); + const renderRep = props => shallowRenderRep(object, props); + const grips = getSelectableInInspectorGrips(object); + + it("correctly selects GripMap Rep", () => { + expect(getRep(object)).toBe(GripMap.rep); + }); + + it("has the expected number of grips", () => { + expect(grips).toHaveLength(3); + }); + + it("calls the expected function on mouseover", () => { + const onDOMNodeMouseOver = jest.fn(); + const wrapper = renderRep({ onDOMNodeMouseOver }); + const node = wrapper.find(".objectBox-node"); + + node.at(0).simulate("mouseover"); + node.at(1).simulate("mouseover"); + node.at(2).simulate("mouseover"); + + expect(onDOMNodeMouseOver.mock.calls).toHaveLength(3); + expect(onDOMNodeMouseOver.mock.calls[0][0]).toBe(grips[0]); + expect(onDOMNodeMouseOver.mock.calls[1][0]).toBe(grips[1]); + expect(onDOMNodeMouseOver.mock.calls[2][0]).toBe(grips[2]); + }); + + it("calls the expected function on mouseout", () => { + const onDOMNodeMouseOut = jest.fn(); + const wrapper = renderRep({ onDOMNodeMouseOut }); + const node = wrapper.find(".objectBox-node"); + + node.at(0).simulate("mouseout"); + node.at(1).simulate("mouseout"); + node.at(2).simulate("mouseout"); + + expect(onDOMNodeMouseOut.mock.calls).toHaveLength(3); + expect(onDOMNodeMouseOut.mock.calls[0][0]).toBe(grips[0]); + expect(onDOMNodeMouseOut.mock.calls[1][0]).toBe(grips[1]); + expect(onDOMNodeMouseOut.mock.calls[2][0]).toBe(grips[2]); + }); + + it("calls the expected function on click", () => { + const onInspectIconClick = jest.fn(); + const wrapper = renderRep({ onInspectIconClick }); + const node = wrapper.find(".open-inspector"); + + node.at(0).simulate("click"); + node.at(1).simulate("click"); + node.at(2).simulate("click"); + + expect(onInspectIconClick.mock.calls).toHaveLength(3); + expect(onInspectIconClick.mock.calls[0][0]).toBe(grips[0]); + expect(onInspectIconClick.mock.calls[1][0]).toBe(grips[1]); + expect(onInspectIconClick.mock.calls[2][0]).toBe(grips[2]); + }); +}); + +describe("GripMap - Node-valued entries", () => { + const object = stubs.get("testNodeValuedMap"); + const renderRep = props => shallowRenderRep(object, props); + const grips = getSelectableInInspectorGrips(object); + + it("correctly selects GripMap Rep", () => { + expect(getRep(object)).toBe(GripMap.rep); + }); + + it("has the expected number of grips", () => { + expect(grips).toHaveLength(3); + }); + + it("calls the expected function on mouseover", () => { + const onDOMNodeMouseOver = jest.fn(); + const wrapper = renderRep({ onDOMNodeMouseOver }); + const node = wrapper.find(".objectBox-node"); + + node.at(0).simulate("mouseover"); + node.at(1).simulate("mouseover"); + node.at(2).simulate("mouseover"); + + expect(onDOMNodeMouseOver.mock.calls).toHaveLength(3); + expect(onDOMNodeMouseOver.mock.calls[0][0]).toBe(grips[0]); + expect(onDOMNodeMouseOver.mock.calls[1][0]).toBe(grips[1]); + expect(onDOMNodeMouseOver.mock.calls[2][0]).toBe(grips[2]); + }); + + it("calls the expected function on mouseout", () => { + const onDOMNodeMouseOut = jest.fn(); + const wrapper = renderRep({ onDOMNodeMouseOut }); + const node = wrapper.find(".objectBox-node"); + + node.at(0).simulate("mouseout"); + node.at(1).simulate("mouseout"); + node.at(2).simulate("mouseout"); + + expect(onDOMNodeMouseOut.mock.calls).toHaveLength(3); + expect(onDOMNodeMouseOut.mock.calls[0][0]).toBe(grips[0]); + expect(onDOMNodeMouseOut.mock.calls[1][0]).toBe(grips[1]); + expect(onDOMNodeMouseOut.mock.calls[2][0]).toBe(grips[2]); + }); + + it("calls the expected function on click", () => { + const onInspectIconClick = jest.fn(); + const wrapper = renderRep({ onInspectIconClick }); + const node = wrapper.find(".open-inspector"); + + node.at(0).simulate("click"); + node.at(1).simulate("click"); + node.at(2).simulate("click"); + + expect(onInspectIconClick.mock.calls).toHaveLength(3); + expect(onInspectIconClick.mock.calls[0][0]).toBe(grips[0]); + expect(onInspectIconClick.mock.calls[1][0]).toBe(grips[1]); + expect(onInspectIconClick.mock.calls[2][0]).toBe(grips[2]); + }); +}); + +describe("GripMap - Disconnected node-valued entries", () => { + const object = stubs.get("testDisconnectedNodeValuedMap"); + const renderRep = props => shallowRenderRep(object, props); + const grips = getSelectableInInspectorGrips(object); + + it("correctly selects GripMap Rep", () => { + expect(getRep(object)).toBe(GripMap.rep); + }); + + it("has the expected number of grips", () => { + expect(grips).toHaveLength(3); + }); + + it("no inspect icon when nodes are not connected to the DOM tree", () => { + const onInspectIconClick = jest.fn(); + const wrapper = renderRep({ onInspectIconClick }); + + const node = wrapper.find(".open-inspector"); + expect(node.exists()).toBe(false); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/grip.test.js b/devtools/client/shared/components/test/node/components/reps/grip.test.js new file mode 100644 index 0000000000..3ef69ee052 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/grip.test.js @@ -0,0 +1,750 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ + +const { shallow } = require("enzyme"); +const { + getRep, + Rep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const Grip = require("resource://devtools/client/shared/components/reps/reps/grip.js"); +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); +const gripArrayStubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); + +const { + expectActorAttribute, + getSelectableInInspectorGrips, + getGripLengthBubbleText, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); +const { maxLengthMap } = Grip; + +function shallowRenderRep(object, props = {}) { + return shallow( + Grip.rep({ + object, + ...props, + }) + ); +} + +describe("Grip - empty object", () => { + // Test object: `{}` + const object = stubs.get("testBasic"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "Object { }"; + + let component = renderRep({ mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.TINY }); + expect(component.text()).toBe("{}"); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER }); + expect(component.text()).toBe("Object"); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.LONG }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + }); +}); + +describe("Grip - Boolean object", () => { + // Test object: `new Boolean(true)` + const object = stubs.get("testBooleanObject"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "Boolean { true }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("Boolean"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Boolean"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Grip - Number object", () => { + // Test object: `new Number(42)` + const object = stubs.get("testNumberObject"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "Number { 42 }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("Number"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Number"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Grip - String object", () => { + // Test object: `new String("foo")` + const object = stubs.get("testStringObject"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = 'String { "foo" }'; + + expect( + renderRep({ mode: undefined, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: undefined, shouldRenderTooltip: true }).prop("title") + ).toBe("String"); + expect( + renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }).text() + ).toBe("String"); + expect( + renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }).prop("title") + ).toBe("String"); + expect( + renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }).text() + ).toBe("String"); + expect( + renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }).prop("title") + ).toBe("String"); + expect( + renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }).prop("title") + ).toBe("String"); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).prop("title") + ).toBe("String"); + }); +}); + +describe("Grip - Proxy", () => { + // Test object: `new Proxy({a:1},[1,2,3])` + const object = stubs.get("testProxy"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const handler = object.preview.ownProperties["<handler>"].value; + const handlerLength = getGripLengthBubbleText(handler, { + mode: MODE.TINY, + }); + const out = `Proxy { <target>: {…}, <handler>: ${handlerLength} […] }`; + + expect(renderRep({ mode: undefined }).text()).toBe(out); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("Proxy"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Proxy"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(out); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(out); + }); +}); + +describe("Grip - ArrayBuffer", () => { + // Test object: `new ArrayBuffer(10)` + const object = stubs.get("testArrayBuffer"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "ArrayBuffer { byteLength: 10 }"; + + expect( + renderRep({ mode: undefined, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: undefined, shouldRenderTooltip: true }).prop("title") + ).toBe("ArrayBuffer"); + expect( + renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }).text() + ).toBe("ArrayBuffer"); + expect( + renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }).prop("title") + ).toBe("ArrayBuffer"); + expect( + renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }).text() + ).toBe("ArrayBuffer"); + expect( + renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }).prop("title") + ).toBe("ArrayBuffer"); + expect( + renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }).prop("title") + ).toBe("ArrayBuffer"); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).prop("title") + ).toBe("ArrayBuffer"); + }); +}); + +describe("Grip - SharedArrayBuffer", () => { + // Test object: `new SharedArrayBuffer(5)` + const object = stubs.get("testSharedArrayBuffer"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "SharedArrayBuffer { byteLength: 5 }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("SharedArrayBuffer"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("SharedArrayBuffer"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Grip - ApplicationCache", () => { + // Test object: `window.applicationCache` + const object = stubs.get("testApplicationCache"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = + "OfflineResourceList { status: 0, onchecking: null, onerror: null, … }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("OfflineResourceList"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("OfflineResourceList"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + const longOutput = + "OfflineResourceList { status: 0, onchecking: null, " + + "onerror: null, onnoupdate: null, ondownloading: null, " + + "onprogress: null, onupdateready: null, oncached: null, " + + "onobsolete: null, mozItems: DOMStringList [] }"; + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); +}); + +describe("Grip - Object with max props", () => { + // Test object: `{a: "a", b: "b", c: "c"}` + const object = stubs.get("testMaxProps"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = 'Object { a: "a", b: "b", c: "c" }'; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Grip - Object with more than short mode max props", () => { + // Test object: `{a: undefined, b: 1, more: 2, d: 3}`; + const object = stubs.get("testMoreProp"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "Object { b: 1, more: 2, d: 3, … }"; + + expect( + renderRep({ mode: undefined, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: undefined, shouldRenderTooltip: true }).prop("title") + ).toBe("Object"); + expect( + renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }).text() + ).toBe("{…}"); + expect( + renderRep({ mode: MODE.TINY, shouldRenderTooltip: true }).prop("title") + ).toBe("Object"); + expect( + renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }).text() + ).toBe("Object"); + expect( + renderRep({ mode: MODE.HEADER, shouldRenderTooltip: true }).prop("title") + ).toBe("Object"); + expect( + renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }).text() + ).toBe(defaultOutput); + expect( + renderRep({ mode: MODE.SHORT, shouldRenderTooltip: true }).prop("title") + ).toBe("Object"); + + const longOutput = "Object { a: undefined, b: 1, more: 2, d: 3 }"; + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).text() + ).toBe(longOutput); + expect( + renderRep({ mode: MODE.LONG, shouldRenderTooltip: true }).prop("title") + ).toBe("Object"); + }); +}); + +describe("Grip - Object with more than long mode max props", () => { + // Test object = `{p0: "0", p1: "1", p2: "2", …, p100: "100"}` + const object = stubs.get("testMoreThanMaxProps"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = 'Object { p0: "0", p1: "1", p2: "2", … }'; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + + const props = Array.from({ length: maxLengthMap.get(MODE.LONG) }).map( + (item, i) => `p${i}: "${i}"` + ); + const longOutput = `Object { ${props.join(", ")}, … }`; + expect(renderRep({ mode: MODE.LONG }).text()).toBe(longOutput); + }); +}); + +describe("Grip - Object with non-enumerable properties", () => { + // Test object: `Object.defineProperty({}, "foo", {enumerable : false});` + const object = stubs.get("testNonEnumerableProps"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "Object { … }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Grip - Object with nested object", () => { + // Test object: `{objProp: {id: 1}, strProp: "test string"}` + const object = stubs.get("testNestedObject"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = 'Object { objProp: {…}, strProp: "test string" }'; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + + // Check the custom title with nested objects to make sure nested objects + // are not displayed with their parent's title. + expect( + renderRep({ + mode: MODE.LONG, + title: "CustomTitle", + }).text() + ).toBe('CustomTitle { objProp: {…}, strProp: "test string" }'); + }); +}); + +describe("Grip - Object with nested array", () => { + // Test object: `{arrProp: ["foo", "bar", "baz"]}` + const object = stubs.get("testNestedArray"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const propLength = getGripLengthBubbleText( + object.preview.ownProperties.arrProp.value, + { mode: MODE.TINY } + ); + const defaultOutput = `Object { arrProp: ${propLength} […] }`; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Grip - Object with connected nodes", () => { + const object = stubs.get("testObjectWithNodes"); + const grips = getSelectableInInspectorGrips(object); + const renderRep = props => shallowRenderRep(object, props); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("has the expected number of node grip", () => { + expect(grips).toHaveLength(2); + }); + + it("renders as expected", () => { + const defaultOutput = + "Object { foo: button#btn-1.btn.btn-log, bar: button#btn-2.btn.btn-err }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); + + it("calls the expected function on mouseover", () => { + const onDOMNodeMouseOver = jest.fn(); + const wrapper = renderRep({ onDOMNodeMouseOver }); + const node = wrapper.find(".objectBox-node"); + + node.at(0).simulate("mouseover"); + node.at(1).simulate("mouseover"); + + expect(onDOMNodeMouseOver.mock.calls).toHaveLength(2); + expect(onDOMNodeMouseOver.mock.calls[0][0]).toBe(grips[0]); + expect(onDOMNodeMouseOver.mock.calls[1][0]).toBe(grips[1]); + }); + + it("calls the expected function on mouseout", () => { + const onDOMNodeMouseOut = jest.fn(); + const wrapper = renderRep({ onDOMNodeMouseOut }); + const node = wrapper.find(".objectBox-node"); + + node.at(0).simulate("mouseout"); + node.at(1).simulate("mouseout"); + + expect(onDOMNodeMouseOut.mock.calls).toHaveLength(2); + expect(onDOMNodeMouseOut.mock.calls[0][0]).toBe(grips[0]); + expect(onDOMNodeMouseOut.mock.calls[1][0]).toBe(grips[1]); + }); + + it("calls the expected function on click", () => { + const onInspectIconClick = jest.fn(); + const wrapper = renderRep({ onInspectIconClick }); + const node = wrapper.find(".open-inspector"); + + node.at(0).simulate("click"); + node.at(1).simulate("click"); + + expect(onInspectIconClick.mock.calls).toHaveLength(2); + expect(onInspectIconClick.mock.calls[0][0]).toBe(grips[0]); + expect(onInspectIconClick.mock.calls[1][0]).toBe(grips[1]); + }); +}); + +describe("Grip - Object with disconnected nodes", () => { + const object = stubs.get("testObjectWithDisconnectedNodes"); + const renderRep = props => shallowRenderRep(object, props); + const grips = getSelectableInInspectorGrips(object); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("has the expected number of grips", () => { + expect(grips).toHaveLength(2); + }); + + it("no inspect icon when nodes are not connected to the DOM tree", () => { + const onInspectIconClick = jest.fn(); + const wrapper = renderRep({ onInspectIconClick }); + + const node = wrapper.find(".open-inspector"); + expect(node.exists()).toBe(false); + }); +}); + +describe("Grip - Object with getter", () => { + const object = stubs.get("TestObjectWithGetter"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "Object { x: Getter }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Grip - Object with setter", () => { + const object = stubs.get("TestObjectWithSetter"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "Object { x: Setter }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Grip - Object with getter and setter", () => { + const object = stubs.get("TestObjectWithGetterAndSetter"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "Object { x: Getter & Setter }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Grip - Object with symbol properties", () => { + const object = stubs.get("TestObjectWithSymbolProperties"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = + 'Object { x: 10, Symbol(): "first unnamed symbol", ' + + 'Symbol(): "second unnamed symbol", … }'; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe( + 'Object { x: 10, Symbol(): "first unnamed symbol", ' + + 'Symbol(): "second unnamed symbol", Symbol("named"): "named symbol", ' + + "Symbol(Symbol.iterator): () }" + ); + }); +}); + +describe("Grip - Object with more than max symbol properties", () => { + const object = stubs.get("TestObjectWithMoreThanMaxSymbolProperties"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = + 'Object { Symbol("i-0"): "value-0", Symbol("i-1"): "value-1", ' + + 'Symbol("i-2"): "value-2", … }'; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe( + 'Object { Symbol("i-0"): "value-0", Symbol("i-1"): "value-1", ' + + 'Symbol("i-2"): "value-2", Symbol("i-3"): "value-3", ' + + 'Symbol("i-4"): "value-4", Symbol("i-5"): "value-5", ' + + 'Symbol("i-6"): "value-6", Symbol("i-7"): "value-7", ' + + 'Symbol("i-8"): "value-8", Symbol("i-9"): "value-9", … }' + ); + }); +}); + +describe("Grip - Without preview", () => { + // Test object: `[1, "foo", {}]` + const object = gripArrayStubs.get("testMaxProps").preview.items[2]; + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "Object { }"; + + let component = renderRep({ mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.TINY }); + expect(component.text()).toBe("{}"); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.HEADER }); + expect(component.text()).toBe("Object"); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ mode: MODE.LONG }); + expect(component.text()).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + }); +}); + +describe("Grip - Generator object", () => { + // Test object: + // function* genFunc() { + // var a = 5; while (a < 10) { yield a++; } + // }; + // genFunc(); + const object = stubs.get("Generator"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "Generator { }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("Generator"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Generator"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Grip - DeadObject object", () => { + // Test object (executed in a privileged content, like about:preferences): + // `var s = Cu.Sandbox(null);Cu.nukeSandbox(s);s;` + + const object = stubs.get("DeadObject"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallowRenderRep(object, props); + const defaultOutput = "DeadObject { }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("DeadObject"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("DeadObject"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +// TODO: Re-enable and fix this test. +describe.skip("Grip - Object with __proto__ property", () => { + const object = stubs.get("ObjectWith__proto__Property"); + + it("correctly selects Grip Rep", () => { + expect(getRep(object)).toBe(Grip.rep); + }); + + it("renders as expected", () => { + const renderRep = props => shallow(Rep({ object, ...props })); + const defaultOutput = "Object { __proto__: [] }"; + + expect(renderRep({ mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.TINY }).text()).toBe("{…}"); + expect(renderRep({ mode: MODE.HEADER }).text()).toBe("Object"); + expect(renderRep({ mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep({ mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +// Test that object that might look like raw objects or arrays are rendered +// as grips when the `noGrip` parameter is not passed. +describe("Object - noGrip prop", () => { + it("empty object", () => { + expect(getRep({})).toBe(Grip.rep); + }); + + it("object with custom property", () => { + expect(getRep({ foo: 123 })).toBe(Grip.rep); + }); + + it("empty array", () => { + expect(getRep([])).toBe(Grip.rep); + }); + + it("array with some item", () => { + expect(getRep([123])).toBe(Grip.rep); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/helper-tests.test.js b/devtools/client/shared/components/test/node/components/reps/helper-tests.test.js new file mode 100644 index 0000000000..6fc1be64a3 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/helper-tests.test.js @@ -0,0 +1,122 @@ +/* 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/>. */ + +"use strict"; + +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); +const { + getGripLengthBubbleText, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +describe("getGripLengthBubbleText - Zero length", () => { + const object = stubs.get("testBasic"); + + it("length bubble is invisible", () => { + const output = ""; + let text = getGripLengthBubbleText(object, { mode: undefined }); + expect(text).toBe(output); + + text = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(text).toBe(output); + + text = getGripLengthBubbleText(object, { mode: MODE.SHORT }); + expect(text).toBe(output); + + text = getGripLengthBubbleText(object, { mode: MODE.LONG }); + expect(text).toBe(output); + }); + + it("length bubble is visible", () => { + const output = "(0)"; + let text = getGripLengthBubbleText(object, { + mode: undefined, + showZeroLength: true, + }); + expect(text).toBe(output); + + text = getGripLengthBubbleText(object, { + mode: MODE.TINY, + showZeroLength: true, + }); + expect(text).toBe(output); + + text = getGripLengthBubbleText(object, { + mode: MODE.SHORT, + showZeroLength: true, + }); + expect(text).toBe(output); + + text = getGripLengthBubbleText(object, { + mode: MODE.LONG, + showZeroLength: true, + }); + expect(text).toBe(output); + }); +}); + +describe("getGripLengthBubbleText - Obvious length for some modes", () => { + const object = stubs.get("testMoreThanShortMaxProps"); + const visibleOutput = `(${object.preview.length})`; + + it("text renders as expected", () => { + let text = getGripLengthBubbleText(object, { mode: undefined }); + expect(text).toBe(visibleOutput); + + text = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(text).toBe(visibleOutput); + + text = getGripLengthBubbleText(object, { mode: MODE.SHORT }); + expect(text).toBe(visibleOutput); + + text = getGripLengthBubbleText(object, { mode: MODE.LONG }); + expect(text).toBe(visibleOutput); + + const visibilityThreshold = 5; + text = getGripLengthBubbleText(object, { + mode: undefined, + visibilityThreshold, + }); + expect(text).toBe(visibleOutput); + + text = getGripLengthBubbleText(object, { + mode: MODE.TINY, + visibilityThreshold, + }); + expect(text).toBe(visibleOutput); + + text = getGripLengthBubbleText(object, { + mode: MODE.SHORT, + visibilityThreshold, + }); + expect(text).toBe(visibleOutput); + + text = getGripLengthBubbleText(object, { + mode: MODE.LONG, + visibilityThreshold, + }); + expect(text).toBe(""); + }); +}); + +describe("getGripLengthBubbleText - Visible length", () => { + const object = stubs.get("testMoreThanLongMaxProps"); + const output = `(${object.preview.length})`; + + it("length bubble is always visible", () => { + let text = getGripLengthBubbleText(object, { mode: undefined }); + expect(text).toBe(output); + + text = getGripLengthBubbleText(object, { mode: MODE.TINY }); + expect(text).toBe(output); + + text = getGripLengthBubbleText(object, { mode: MODE.SHORT }); + expect(text).toBe(output); + + text = getGripLengthBubbleText(object, { mode: MODE.LONG }); + expect(text).toBe(output); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/infinity.test.js b/devtools/client/shared/components/test/node/components/reps/infinity.test.js new file mode 100644 index 0000000000..93fd310917 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/infinity.test.js @@ -0,0 +1,70 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { InfinityRep, Rep } = REPS; + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/infinity.js"); + +describe("testInfinity", () => { + const stub = stubs.get("Infinity"); + + it("Rep correctly selects Infinity Rep", () => { + expect(getRep(stub)).toBe(InfinityRep.rep); + }); + + it("Infinity rep has expected text content for Infinity", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + expect(renderedComponent.text()).toEqual("Infinity"); + }); + + it("Infinity rep has expected title content for Infinity", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + expect(renderedComponent.prop("title")).toEqual("Infinity"); + }); +}); + +describe("testNegativeInfinity", () => { + const stub = stubs.get("NegativeInfinity"); + + it("Rep correctly selects Infinity Rep", () => { + expect(getRep(stub)).toBe(InfinityRep.rep); + }); + + it("Infinity rep has expected text content for negative Infinity", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + expect(renderedComponent.text()).toEqual("-Infinity"); + }); + + it("Infinity rep has expected title content for negative Infinity", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + expect(renderedComponent.prop("title")).toEqual("-Infinity"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/long-string.test.js b/devtools/client/shared/components/test/node/components/reps/long-string.test.js new file mode 100644 index 0000000000..ea6bb43e97 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/long-string.test.js @@ -0,0 +1,135 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { + ELLIPSIS, +} = require("resource://devtools/client/shared/components/reps/reps/rep-utils.js"); + +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const { StringRep } = REPS; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/long-string.js"); + +function quoteNewlines(text) { + return text.split("\n").join("\\n"); +} + +describe("long StringRep", () => { + it("selects String Rep", () => { + const stub = stubs.get("testMultiline"); + + expect(getRep(stub)).toEqual(StringRep.rep); + }); + + it("renders with expected text content for multiline string", () => { + const stub = stubs.get("testMultiline"); + const renderedComponent = shallow( + StringRep.rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + quoteNewlines(`"${stub.initial}${ELLIPSIS}"`) + ); + expectActorAttribute(renderedComponent, stub.actor); + }); + + it( + "renders with expected text content for multiline string with " + + "specified number of characters", + () => { + const stub = stubs.get("testMultiline"); + const renderedComponent = shallow( + StringRep.rep({ + object: stub, + cropLimit: 20, + }) + ); + + expect(renderedComponent.text()).toEqual( + `"a\\naaaaaaaaaaaaaaaaaa${ELLIPSIS}"` + ); + } + ); + + it("renders with expected text for multiline string when open", () => { + const stub = stubs.get("testMultiline"); + const renderedComponent = shallow( + StringRep.rep({ + object: stub, + member: { open: true }, + cropLimit: 20, + }) + ); + + expect(renderedComponent.text()).toEqual( + quoteNewlines(`"${stub.initial}${ELLIPSIS}"`) + ); + }); + + it( + "renders with expected text content when grip has a fullText" + + "property and is open", + () => { + const stub = stubs.get("testLoadedFullText"); + const renderedComponent = shallow( + StringRep.rep({ + object: stub, + member: { open: true }, + cropLimit: 20, + }) + ); + + expect(renderedComponent.text()).toEqual( + quoteNewlines(`"${stub.fullText}"`) + ); + } + ); + + it( + "renders with expected text content when grip has a fullText " + + "property and is not open", + () => { + const stub = stubs.get("testLoadedFullText"); + const renderedComponent = shallow( + StringRep.rep({ + object: stub, + cropLimit: 20, + }) + ); + + expect(renderedComponent.text()).toEqual( + `"a\\naaaaaaaaaaaaaaaaaa${ELLIPSIS}"` + ); + } + ); + + it("expected to omit quotes", () => { + const stub = stubs.get("testMultiline"); + const renderedComponent = shallow( + StringRep.rep({ + object: stub, + cropLimit: 20, + useQuotes: false, + }) + ); + + expect(renderedComponent.html()).toEqual( + '<span data-link-actor-id="server1.conn1.child1/longString58" ' + + `class="objectBox objectBox-string">a\naaaaaaaaaaaaaaaaaa${ELLIPSIS}` + + "</span>" + ); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/nan.test.js b/devtools/client/shared/components/test/node/components/reps/nan.test.js new file mode 100644 index 0000000000..429e70acab --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/nan.test.js @@ -0,0 +1,43 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { NaNRep, Rep } = REPS; + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/nan.js"); + +describe("NaN", () => { + const stub = stubs.get("NaN"); + + it("selects NaN Rep as expected", () => { + expect(getRep(stub)).toBe(NaNRep.rep); + }); + + it("renders NaN Rep as expected", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + expect(renderedComponent).toMatchSnapshot(); + }); + + it("NaN rep renders with the correct title element", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + expect(renderedComponent.prop("title")).toBe("NaN"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/null.test.js b/devtools/client/shared/components/test/node/components/reps/null.test.js new file mode 100644 index 0000000000..af5f615cad --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/null.test.js @@ -0,0 +1,47 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { Null, Rep } = REPS; + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/null.js"); + +describe("testNull", () => { + const stub = stubs.get("Null"); + + it("Rep correctly selects Null Rep", () => { + expect(getRep(stub)).toBe(Null.rep); + }); + + it("Rep correctly selects Null Rep for plain JS null object", () => { + expect(getRep(null, undefined, true)).toBe(Null.rep); + }); + + it("Null rep has expected text content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + expect(renderedComponent.text()).toEqual("null"); + }); + + it("Null rep displays null for title", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + expect(renderedComponent.prop("title")).toEqual("null"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/number.test.js b/devtools/client/shared/components/test/node/components/reps/number.test.js new file mode 100644 index 0000000000..c0bbab3b0a --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/number.test.js @@ -0,0 +1,136 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { Number, Rep } = REPS; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/number.js"); + +describe("Int", () => { + const stub = stubs.get("Int"); + + it("correctly selects Number Rep for Integer value", () => { + expect(getRep(stub)).toBe(Number.rep); + }); + + it("renders with expected text content for integer", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("5"); + expect(renderedComponent.prop("title")).toBe("5"); + }); +}); + +describe("Boolean", () => { + const stubTrue = stubs.get("True"); + const stubFalse = stubs.get("False"); + + it("correctly selects Number Rep for boolean value", () => { + expect(getRep(stubTrue)).toBe(Number.rep); + }); + + it("renders with expected text content for boolean true", () => { + const renderedComponent = shallow( + Rep({ + object: stubTrue, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("true"); + expect(renderedComponent.prop("title")).toBe("true"); + }); + + it("renders with expected text content for boolean false", () => { + const renderedComponent = shallow( + Rep({ + object: stubFalse, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("false"); + expect(renderedComponent.prop("title")).toBe("false"); + }); +}); + +describe("Negative Zero", () => { + const stubNegativeZeroGrip = stubs.get("NegZeroGrip"); + const stubNegativeZeroValue = -0; + + it("correctly selects Number Rep for negative zero grip", () => { + expect(getRep(stubNegativeZeroGrip)).toBe(Number.rep); + }); + + it("correctly selects Number Rep for negative zero value", () => { + expect(getRep(stubNegativeZeroValue)).toBe(Number.rep); + }); + + it("renders with expected text content for negative zero grip", () => { + const renderedComponent = shallow( + Rep({ + object: stubNegativeZeroGrip, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("-0"); + expect(renderedComponent.prop("title")).toBe("-0"); + }); + + it("renders with expected text content for negative zero value", () => { + const renderedComponent = shallow( + Rep({ + object: stubNegativeZeroValue, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("-0"); + expect(renderedComponent.prop("title")).toBe("-0"); + }); +}); + +describe("Zero", () => { + it("correctly selects Number Rep for zero value", () => { + expect(getRep(0)).toBe(Number.rep); + }); + + it("renders with expected text content for zero value", () => { + const renderedComponent = shallow( + Rep({ + object: 0, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("0"); + expect(renderedComponent.prop("title")).toBe("0"); + }); +}); + +describe("Unsafe Int", () => { + it("renders with expected test content for a long number", () => { + const renderedComponent = shallow( + Rep({ + // eslint-disable-next-line no-loss-of-precision + object: 900719925474099122, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("900719925474099100"); + expect(renderedComponent.prop("title")).toBe("900719925474099100"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/object-with-text.test.js b/devtools/client/shared/components/test/node/components/reps/object-with-text.test.js new file mode 100644 index 0000000000..164024f7e6 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/object-with-text.test.js @@ -0,0 +1,66 @@ +/* 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/>. */ + +"use strict"; + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { shallow } = require("enzyme"); +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/object-with-text.js"); +const { ObjectWithText, Rep } = REPS; + +describe("Object with text - CSSStyleRule", () => { + const gripStub = stubs.get("ShadowRule"); + + // Test that correct rep is chosen + it("selects ObjectsWithText Rep", () => { + expect(getRep(gripStub)).toEqual(ObjectWithText.rep); + }); + + // Test rendering + it("renders with the correct text content", () => { + const renderedComponent = shallow( + Rep({ + object: gripStub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual('CSSStyleRule ".Shadow"'); + expect(renderedComponent.prop("title")).toEqual('CSSStyleRule ".Shadow"'); + expectActorAttribute(renderedComponent, gripStub.actor); + }); +}); + +describe("Object with text - CSSMediaRule", () => { + const gripStub = stubs.get("CSSMediaRule"); + + // Test that correct rep is chosen + it("selects ObjectsWithText Rep", () => { + expect(getRep(gripStub)).toEqual(ObjectWithText.rep); + }); + + // Test rendering + it("renders with the correct text content", () => { + const renderedComponent = shallow( + Rep({ + object: gripStub, + shouldRenderTooltip: true, + }) + ); + + const text = + 'CSSMediaRule "(min-height: 680px), screen and (orientation: portrait)"'; + expect(renderedComponent.text()).toEqual(text); + expect(renderedComponent.prop("title")).toEqual(text); + expectActorAttribute(renderedComponent, gripStub.actor); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/object-with-url.test.js b/devtools/client/shared/components/test/node/components/reps/object-with-url.test.js new file mode 100644 index 0000000000..055afbfde4 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/object-with-url.test.js @@ -0,0 +1,45 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { ObjectWithURL } = REPS; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/object-with-url.js"); +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +describe("ObjectWithURL", () => { + const stub = stubs.get("ObjectWithUrl"); + + it("selects the correct rep", () => { + expect(getRep(stub)).toEqual(ObjectWithURL.rep); + }); + + it("renders with correct class name and content", () => { + const renderedComponent = shallow( + ObjectWithURL.rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + expect(renderedComponent.text()).toBe( + "Location https://www.mozilla.org/en-US/" + ); + expect(renderedComponent.prop("title")).toBe( + "Location https://www.mozilla.org/en-US/" + ); + expect(renderedComponent.hasClass("objectBox-Location")).toBe(true); + + const innerNode = renderedComponent.find(".objectPropValue"); + expect(innerNode.text()).toBe("https://www.mozilla.org/en-US/"); + + expectActorAttribute(renderedComponent, stub.actor); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/object.test.js b/devtools/client/shared/components/test/node/components/reps/object.test.js new file mode 100644 index 0000000000..9b32e51d20 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/object.test.js @@ -0,0 +1,356 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { Obj } = REPS; +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); + +const renderComponent = (object, props) => { + return shallow(Obj.rep({ object, ...props })); +}; + +describe("Object - Basic", () => { + const object = {}; + const defaultOutput = "Object { }"; + + it("selects the correct rep", () => { + expect(getRep(object, undefined, true)).toBe(Obj.rep); + }); + + it("renders basic object as expected", () => { + expect(renderComponent(object, { mode: undefined }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: undefined }).prop("title")).toEqual( + "Object" + ); + + expect(renderComponent(object, { mode: MODE.TINY }).text()).toEqual("{}"); + expect(renderComponent(object, { mode: MODE.TINY }).prop("title")).toEqual( + "Object" + ); + + expect(renderComponent(object, { mode: MODE.SHORT }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.LONG }).text()).toEqual( + defaultOutput + ); + }); +}); + +describe("Object - Max props", () => { + const object = { a: "a", b: "b", c: "c" }; + const defaultOutput = 'Object { a: "a", b: "b", c: "c" }'; + + it("renders object with max props as expected", () => { + expect(renderComponent(object, { mode: undefined }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.TINY }).text()).toEqual("{…}"); + expect(renderComponent(object, { mode: MODE.SHORT }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.LONG }).text()).toEqual( + defaultOutput + ); + }); +}); + +describe("Object - Many props", () => { + const object = {}; + for (let i = 0; i < 100; i++) { + object[`p${i}`] = i; + } + const defaultOutput = "Object { p0: 0, p1: 1, p2: 2, … }"; + + it("renders object with many props as expected", () => { + expect(renderComponent(object, { mode: undefined }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.TINY }).text()).toEqual("{…}"); + expect(renderComponent(object, { mode: MODE.SHORT }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.LONG }).text()).toEqual( + defaultOutput + ); + }); +}); + +describe("Object - Uninteresting props", () => { + const object = { a: undefined, b: undefined, c: "c", d: 0 }; + const defaultOutput = 'Object { c: "c", d: 0, a: undefined, … }'; + + it("renders object with uninteresting props as expected", () => { + expect(renderComponent(object, { mode: undefined }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.TINY }).text()).toEqual("{…}"); + expect(renderComponent(object, { mode: MODE.SHORT }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.LONG }).text()).toEqual( + defaultOutput + ); + }); +}); + +describe("Object - Escaped property names", () => { + const object = { "": 1, "quote-this": 2, noquotes: 3 }; + const defaultOutput = 'Object { "": 1, "quote-this": 2, noquotes: 3 }'; + + it("renders object with escaped property names as expected", () => { + expect(renderComponent(object, { mode: undefined }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.TINY }).text()).toEqual("{…}"); + expect(renderComponent(object, { mode: MODE.SHORT }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.LONG }).text()).toEqual( + defaultOutput + ); + }); +}); + +describe("Object - Nested", () => { + const object = { + objProp: { + id: 1, + arr: [2], + }, + strProp: "test string", + arrProp: [1], + }; + const defaultOutput = + 'Object { strProp: "test string", objProp: {…},' + " arrProp: […] }"; + + it("renders nested object as expected", () => { + expect( + renderComponent(object, { mode: undefined, noGrip: true }).text() + ).toEqual(defaultOutput); + expect( + renderComponent(object, { mode: MODE.TINY, noGrip: true }).text() + ).toEqual("{…}"); + expect( + renderComponent(object, { mode: MODE.SHORT, noGrip: true }).text() + ).toEqual(defaultOutput); + expect( + renderComponent(object, { mode: MODE.LONG, noGrip: true }).text() + ).toEqual(defaultOutput); + }); +}); + +describe("Object - More prop", () => { + const object = { + a: undefined, + b: 1, + more: 2, + d: 3, + }; + const defaultOutput = "Object { b: 1, more: 2, d: 3, … }"; + + it("renders object with more properties as expected", () => { + expect(renderComponent(object, { mode: undefined }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.TINY }).text()).toEqual("{…}"); + expect(renderComponent(object, { mode: MODE.SHORT }).text()).toEqual( + defaultOutput + ); + expect(renderComponent(object, { mode: MODE.LONG }).text()).toEqual( + defaultOutput + ); + }); +}); + +describe("Object - Custom Title", () => { + const customTitle = "MyCustomObject"; + const object = { a: "a", b: "b", c: "c" }; + const defaultOutput = `${customTitle} { a: "a", b: "b", c: "c" }`; + + it("renders object with more properties as expected", () => { + expect( + renderComponent(object, { mode: undefined, title: customTitle }).text() + ).toEqual(defaultOutput); + expect( + renderComponent(object, { mode: undefined, title: customTitle }).prop( + "title" + ) + ).toEqual(customTitle); + expect( + renderComponent(object, { mode: MODE.TINY, title: customTitle }).text() + ).toEqual(customTitle); + expect( + renderComponent(object, { mode: MODE.TINY, title: customTitle }).prop( + "title" + ) + ).toEqual(customTitle); + expect( + renderComponent(object, { mode: MODE.SHORT, title: customTitle }).text() + ).toEqual(defaultOutput); + expect( + renderComponent(object, { mode: MODE.LONG, title: customTitle }).text() + ).toEqual(defaultOutput); + }); +}); + +// Test that object that might look like Grips are rendered as Object when +// passed the `noGrip` property. +describe("Object - noGrip prop", () => { + it("object with type property", () => { + expect(getRep({ type: "string" }, undefined, true)).toBe(Obj.rep); + }); + + it("object with actor property", () => { + expect(getRep({ actor: "fake/actorId" }, undefined, true)).toBe(Obj.rep); + }); + + it("Attribute grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/attribute.js"); + expect(getRep(stubs.get("Attribute"), undefined, true)).toBe(Obj.rep); + }); + + it("CommentNode grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/comment-node.js"); + expect(getRep(stubs.get("Comment"), undefined, true)).toBe(Obj.rep); + }); + + it("DateTime grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/date-time.js"); + expect(getRep(stubs.get("DateTime"), undefined, true)).toBe(Obj.rep); + }); + + it("Document grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/document.js"); + expect(getRep(stubs.get("Document"), undefined, true)).toBe(Obj.rep); + }); + + it("ElementNode grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/element-node.js"); + expect(getRep(stubs.get("BodyNode"), undefined, true)).toBe(Obj.rep); + }); + + it("Error grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/error.js"); + expect(getRep(stubs.get("SimpleError"), undefined, true)).toBe(Obj.rep); + }); + + it("Event grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/event.js"); + expect(getRep(stubs.get("testEvent"), undefined, true)).toBe(Obj.rep); + }); + + it("Function grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/function.js"); + expect(getRep(stubs.get("Named"), undefined, true)).toBe(Obj.rep); + }); + + it("Array grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js"); + expect(getRep(stubs.get("testMaxProps"), undefined, true)).toBe(Obj.rep); + }); + + it("Map grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-map.js"); + expect(getRep(stubs.get("testSymbolKeyedMap"), undefined, true)).toBe( + Obj.rep + ); + }); + + it("Object grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js"); + expect(getRep(stubs.get("testMaxProps"), undefined, true)).toBe(Obj.rep); + }); + + it("Infinity grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/infinity.js"); + expect(getRep(stubs.get("Infinity"), undefined, true)).toBe(Obj.rep); + }); + + it("LongString grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/long-string.js"); + expect(getRep(stubs.get("testMultiline"), undefined, true)).toBe(Obj.rep); + }); + + it("NaN grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/nan.js"); + expect(getRep(stubs.get("NaN"), undefined, true)).toBe(Obj.rep); + }); + + it("Null grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/null.js"); + expect(getRep(stubs.get("Null"), undefined, true)).toBe(Obj.rep); + }); + + it("Number grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/number.js"); + expect(getRep(stubs.get("NegZeroGrip"), undefined, true)).toBe(Obj.rep); + }); + + it("ObjectWithText grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/object-with-text.js"); + expect(getRep(stubs.get("ShadowRule"), undefined, true)).toBe(Obj.rep); + }); + + it("ObjectWithURL grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/object-with-url.js"); + expect(getRep(stubs.get("ObjectWithUrl"), undefined, true)).toBe(Obj.rep); + }); + + it("Promise grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/promise.js"); + expect(getRep(stubs.get("Pending"), undefined, true)).toBe(Obj.rep); + }); + + it("RegExp grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/regexp.js"); + expect(getRep(stubs.get("RegExp"), undefined, true)).toBe(Obj.rep); + }); + + it("Stylesheet grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/stylesheet.js"); + expect(getRep(stubs.get("StyleSheet"), undefined, true)).toBe(Obj.rep); + }); + + it("Symbol grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/symbol.js"); + expect(getRep(stubs.get("Symbol"), undefined, true)).toBe(Obj.rep); + }); + + it("TextNode grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/text-node.js"); + expect(getRep(stubs.get("testRendering"), undefined, true)).toBe(Obj.rep); + }); + + it("Undefined grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/undefined.js"); + expect(getRep(stubs.get("Undefined"), undefined, true)).toBe(Obj.rep); + }); + + it("Window grip", () => { + const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/window.js"); + expect(getRep(stubs.get("Window"), undefined, true)).toBe(Obj.rep); + }); + + it("Object with class property", () => { + const object = { + class: "Array", + }; + expect(getRep(object, undefined, true)).toBe(Obj.rep); + + expect( + renderComponent(object, { mode: MODE.SHORT, noGrip: true }).text() + ).toEqual('Object { class: "Array" }'); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/promise.test.js b/devtools/client/shared/components/test/node/components/reps/promise.test.js new file mode 100644 index 0000000000..5c340dccfd --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/promise.test.js @@ -0,0 +1,229 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { PromiseRep } = REPS; +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/promise.js"); +const { + expectActorAttribute, + getSelectableInInspectorGrips, + getGripLengthBubbleText, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const renderRep = (object, props) => { + return shallow(PromiseRep.rep({ object, ...props })); +}; + +describe("Promise - Pending", () => { + const object = stubs.get("Pending"); + const defaultOutput = 'Promise { <state>: "pending" }'; + + it("correctly selects PromiseRep Rep for pending Promise", () => { + expect(getRep(object)).toBe(PromiseRep.rep); + }); + + it("renders as expected", () => { + let component = renderRep(object, { + mode: undefined, + shouldRenderTooltip: true, + }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe("Promise"); + expectActorAttribute(component, object.actor); + + component = renderRep(object, { + mode: MODE.TINY, + shouldRenderTooltip: true, + }); + expect(component.text()).toBe('Promise { "pending" }'); + expect(component.prop("title")).toBe("Promise"); + expectActorAttribute(component, object.actor); + + component = renderRep(object, { + mode: MODE.HEADER, + shouldRenderTooltip: true, + }); + expect(component.text()).toBe("Promise"); + expect(component.prop("title")).toBe("Promise"); + expectActorAttribute(component, object.actor); + + component = renderRep(object, { + mode: MODE.SHORT, + shouldRenderTooltip: true, + }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe("Promise"); + expectActorAttribute(component, object.actor); + + component = renderRep(object, { + mode: MODE.LONG, + shouldRenderTooltip: true, + }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe("Promise"); + expectActorAttribute(component, object.actor); + }); +}); + +describe("Promise - fulfilled with string", () => { + const object = stubs.get("FulfilledWithString"); + const defaultOutput = 'Promise { <state>: "fulfilled", <value>: "foo" }'; + + it("selects PromiseRep Rep for Promise fulfilled with a string", () => { + expect(getRep(object)).toBe(PromiseRep.rep); + }); + + it("should render as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep(object, { mode: MODE.TINY }).text()).toBe( + 'Promise { "fulfilled" }' + ); + expect(renderRep(object, { mode: MODE.HEADER }).text()).toBe("Promise"); + expect(renderRep(object, { mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep(object, { mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Promise - fulfilled with object", () => { + const object = stubs.get("FulfilledWithObject"); + const defaultOutput = 'Promise { <state>: "fulfilled", <value>: {…} }'; + + it("selects PromiseRep Rep for Promise fulfilled with an object", () => { + expect(getRep(object)).toBe(PromiseRep.rep); + }); + + it("should render as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep(object, { mode: MODE.TINY }).text()).toBe( + 'Promise { "fulfilled" }' + ); + expect(renderRep(object, { mode: MODE.HEADER }).text()).toBe("Promise"); + expect(renderRep(object, { mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep(object, { mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Promise - fulfilled with array", () => { + const object = stubs.get("FulfilledWithArray"); + const length = getGripLengthBubbleText( + object.preview.ownProperties["<value>"].value, + { + mode: MODE.TINY, + } + ); + const out = `Promise { <state>: "fulfilled", <value>: ${length} […] }`; + + it("selects PromiseRep Rep for Promise fulfilled with an array", () => { + expect(getRep(object)).toBe(PromiseRep.rep); + }); + + it("should render as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe(out); + expect(renderRep(object, { mode: MODE.TINY }).text()).toBe( + 'Promise { "fulfilled" }' + ); + expect(renderRep(object, { mode: MODE.HEADER }).text()).toBe("Promise"); + expect(renderRep(object, { mode: MODE.SHORT }).text()).toBe(out); + expect(renderRep(object, { mode: MODE.LONG }).text()).toBe(out); + }); +}); + +describe("Promise - fulfilled with node", () => { + const stub = stubs.get("FulfilledWithNode"); + const grips = getSelectableInInspectorGrips(stub); + + it("has one node grip", () => { + expect(grips).toHaveLength(1); + }); + + it("calls the expected function on mouseover", () => { + const onDOMNodeMouseOver = jest.fn(); + const wrapper = renderRep(stub, { onDOMNodeMouseOver }); + const node = wrapper.find(".objectBox-node"); + + node.simulate("mouseover"); + + expect(onDOMNodeMouseOver.mock.calls).toHaveLength(1); + expect(onDOMNodeMouseOver).toHaveBeenCalledWith(grips[0]); + }); + + it("calls the expected function on mouseout", () => { + const onDOMNodeMouseOut = jest.fn(); + const wrapper = renderRep(stub, { onDOMNodeMouseOut }); + const node = wrapper.find(".objectBox-node"); + + node.simulate("mouseout"); + + expect(onDOMNodeMouseOut.mock.calls).toHaveLength(1); + expect(onDOMNodeMouseOut).toHaveBeenCalledWith(grips[0]); + }); + + it("no inspect icon when the node is not connected to the DOM tree", () => { + const renderedComponentWithoutInspectIcon = renderRep( + stubs.get("FulfilledWithDisconnectedNode") + ); + const node = renderedComponentWithoutInspectIcon.find(".open-inspector"); + + expect(node.exists()).toBe(false); + }); + + it("renders an inspect icon", () => { + const onInspectIconClick = jest.fn(); + const renderedComponent = renderRep(stub, { onInspectIconClick }); + const icon = renderedComponent.find(".open-inspector"); + + icon.simulate("click"); + + expect(icon.exists()).toBe(true); + expect(onInspectIconClick.mock.calls).toHaveLength(1); + }); +}); + +describe("Promise - rejected with number", () => { + const object = stubs.get("RejectedWithNumber"); + const defaultOutput = 'Promise { <state>: "rejected", <reason>: 123 }'; + + it("selects PromiseRep Rep for Promise rejected with an object", () => { + expect(getRep(object)).toBe(PromiseRep.rep); + }); + + it("should render as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep(object, { mode: MODE.TINY }).text()).toBe( + 'Promise { "rejected" }' + ); + expect(renderRep(object, { mode: MODE.HEADER }).text()).toBe("Promise"); + expect(renderRep(object, { mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep(object, { mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); + +describe("Promise - rejected with object", () => { + const object = stubs.get("RejectedWithObject"); + const defaultOutput = 'Promise { <state>: "rejected", <reason>: {…} }'; + + it("selects PromiseRep Rep for Promise rejected with an object", () => { + expect(getRep(object)).toBe(PromiseRep.rep); + }); + + it("should render as expected", () => { + expect(renderRep(object, { mode: undefined }).text()).toBe(defaultOutput); + expect(renderRep(object, { mode: MODE.TINY }).text()).toBe( + 'Promise { "rejected" }' + ); + expect(renderRep(object, { mode: MODE.HEADER }).text()).toBe("Promise"); + expect(renderRep(object, { mode: MODE.SHORT }).text()).toBe(defaultOutput); + expect(renderRep(object, { mode: MODE.LONG }).text()).toBe(defaultOutput); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/regexp.test.js b/devtools/client/shared/components/test/node/components/reps/regexp.test.js new file mode 100644 index 0000000000..7e1ea841b8 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/regexp.test.js @@ -0,0 +1,59 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { Rep, RegExp } = REPS; +const { + ELLIPSIS, +} = require("resource://devtools/client/shared/components/reps/reps/rep-utils.js"); +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/regexp.js"); +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +describe("test RegExp", () => { + const stub = stubs.get("RegExp"); + + it("selects RegExp Rep", () => { + expect(getRep(stub)).toEqual(RegExp.rep); + }); + + it("renders with expected text content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("/ab+c/i"); + expect(renderedComponent.prop("title")).toEqual("/ab+c/i"); + expectActorAttribute(renderedComponent, stub.actor); + }); + + it("renders regexp with longString displayString with expected text content", () => { + const longStringDisplayStringRegexpStub = stubs.get( + "longString displayString RegExp" + ); + const renderedComponent = shallow( + Rep({ + object: longStringDisplayStringRegexpStub, + }) + ); + + expect(renderedComponent.text()).toEqual( + `/${"ab ".repeat(333)}${ELLIPSIS}` + ); + expectActorAttribute( + renderedComponent, + longStringDisplayStringRegexpStub.actor + ); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/string-with-url.test.js b/devtools/client/shared/components/test/node/components/reps/string-with-url.test.js new file mode 100644 index 0000000000..a7f287a302 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/string-with-url.test.js @@ -0,0 +1,630 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ +const { mount } = require("enzyme"); +const { + REPS, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { Rep } = REPS; +const { + getGripLengthBubbleText, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +const renderRep = (string, props) => + mount( + Rep({ + object: string, + ...props, + }) + ); + +const testLinkClick = (link, openLink, url) => { + let syntheticEvent; + const preventDefault = jest.fn().mockImplementation(function () { + // This refers to the event object for which preventDefault is called (in + // this case it is the syntheticEvent that is passed to onClick and + // consequently to openLink). + syntheticEvent = this; + }); + + link.simulate("click", { preventDefault }); + // Prevent defaults behavior on click + expect(preventDefault).toHaveBeenCalled(); + expect(openLink).toHaveBeenCalledWith(url, syntheticEvent); +}; + +describe("test String with URL", () => { + it("renders a URL", () => { + const url = "http://example.com"; + const openLink = jest.fn(); + const element = renderRep(url, { openLink, useQuotes: false }); + expect(element.text()).toEqual(url); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders a href when openLink isn't defined", () => { + const url = "http://example.com"; + const element = renderRep(url, { useQuotes: false }); + expect(element.text()).toEqual(url); + const link = element.find("a"); + expect(link.prop("href")).toBe(null); + expect(link.prop("title")).toBe(url); + expect(link.prop("rel")).toBe("noopener noreferrer"); + }); + + it("renders a href when no openLink but isInContentPage is true", () => { + const url = "http://example.com"; + const element = renderRep(url, { useQuotes: false, isInContentPage: true }); + expect(element.text()).toEqual(url); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + expect(link.prop("rel")).toBe("noopener noreferrer"); + }); + + it("renders a simple quoted URL", () => { + const url = "http://example.com"; + const string = `'${url}'`; + const openLink = jest.fn(); + const element = renderRep(string, { openLink, useQuotes: false }); + expect(element.text()).toEqual(string); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders a double quoted URL", () => { + const url = "http://example.com"; + const string = `"${url}"`; + const openLink = jest.fn(); + const element = renderRep(string, { openLink, useQuotes: false }); + expect(element.text()).toEqual(string); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders a quoted URL when useQuotes is true", () => { + const url = "http://example.com"; + const string = `"${url}"`; + const openLink = jest.fn(); + const element = renderRep(string, { openLink, useQuotes: true }); + expect(element.text()).toEqual(`'"${url}"'`); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders a simple https URL", () => { + const url = "https://example.com"; + const openLink = jest.fn(); + const element = renderRep(url, { openLink, useQuotes: false }); + expect(element.text()).toEqual(url); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders a simple http URL with one slash", () => { + const url = "https:/example.com"; + const openLink = jest.fn(); + const element = renderRep(url, { openLink, useQuotes: false }); + expect(element.text()).toEqual(url); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders a URL with port", () => { + const url = "https://example.com:443"; + const openLink = jest.fn(); + const element = renderRep(url, { openLink, useQuotes: false }); + expect(element.text()).toEqual(url); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders a URL with non-empty path", () => { + const url = "http://example.com/foo"; + const openLink = jest.fn(); + const element = renderRep(url, { openLink, useQuotes: false }); + expect(element.text()).toEqual(url); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders a URL when surrounded by non-URL tokens", () => { + const url = "http://example.com"; + const string = `foo ${url} bar`; + const openLink = jest.fn(); + const element = renderRep(string, { openLink, useQuotes: false }); + expect(element.text()).toEqual(string); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders a URL and whitespace is be preserved", () => { + const url = "http://example.com"; + const string = `foo\n${url}\nbar\n`; + const openLink = jest.fn(); + const element = renderRep(string, { openLink, useQuotes: false }); + expect(element.text()).toEqual(string); + + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders multiple URLs", () => { + const url1 = "http://example.com"; + const url2 = "https://example.com/foo"; + const string = `${url1} ${url2}`; + const openLink = jest.fn(); + const element = renderRep(string, { openLink, useQuotes: false }); + expect(element.text()).toEqual(string); + const links = element.find("a"); + expect(links).toHaveLength(2); + + const firstLink = links.at(0); + expect(firstLink.prop("href")).toBe(url1); + expect(firstLink.prop("title")).toBe(url1); + testLinkClick(firstLink, openLink, url1); + + const secondLink = links.at(1); + expect(secondLink.prop("href")).toBe(url2); + expect(secondLink.prop("title")).toBe(url2); + testLinkClick(secondLink, openLink, url2); + }); + + it("renders multiple URLs with various spacing", () => { + const url1 = "http://example.com"; + const url2 = "https://example.com/foo"; + const string = ` ${url1} ${url2} ${url2} ${url1} `; + const element = renderRep(string, { useQuotes: false }); + expect(element.text()).toEqual(string); + const links = element.find("a"); + expect(links).toHaveLength(4); + }); + + it("renders a cropped URL", () => { + const url = "http://example.com"; + const openLink = jest.fn(); + const element = renderRep(url, { + openLink, + useQuotes: false, + cropLimit: 15, + }); + + expect(element.text()).toEqual("http://…ple.com"); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders a non-cropped URL", () => { + const url = "http://example.com/foobarbaz"; + const openLink = jest.fn(); + const element = renderRep(url, { + openLink, + useQuotes: false, + cropLimit: 50, + }); + + expect(element.text()).toEqual(url); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders URL on an open string", () => { + const url = "http://example.com"; + const openLink = jest.fn(); + const element = renderRep(url, { + openLink, + useQuotes: false, + member: { + open: true, + }, + }); + + expect(element.text()).toEqual(url); + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("renders URLs with a stripped string between", () => { + const text = "- http://example.fr --- http://example.us -"; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + cropLimit: 41, + }); + + expect(element.text()).toEqual("- http://example.fr … http://example.us -"); + const linkFr = element.find("a").at(0); + expect(linkFr.prop("href")).toBe("http://example.fr"); + expect(linkFr.prop("title")).toBe("http://example.fr"); + + const linkUs = element.find("a").at(1); + expect(linkUs.prop("href")).toBe("http://example.us"); + expect(linkUs.prop("title")).toBe("http://example.us"); + }); + + it("renders URLs with a cropped string between", () => { + const text = "- http://example.fr ---- http://example.us -"; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + cropLimit: 42, + }); + + expect(element.text()).toEqual( + "- http://example.fr -…- http://example.us -" + ); + const linkFr = element.find("a").at(0); + expect(linkFr.prop("href")).toBe("http://example.fr"); + expect(linkFr.prop("title")).toBe("http://example.fr"); + + const linkUs = element.find("a").at(1); + expect(linkUs.prop("href")).toBe("http://example.us"); + expect(linkUs.prop("title")).toBe("http://example.us"); + }); + + it("renders successive cropped URLs, 1 at the start, 1 at the end", () => { + const text = "- http://example-long.fr http://example.us -"; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + cropLimit: 20, + }); + + expect(element.text()).toEqual("- http://e…ample.us -"); + const linkFr = element.find("a").at(0); + expect(linkFr.prop("href")).toBe("http://example-long.fr"); + expect(linkFr.prop("title")).toBe("http://example-long.fr"); + + const linkUs = element.find("a").at(1); + expect(linkUs.prop("href")).toBe("http://example.us"); + expect(linkUs.prop("title")).toBe("http://example.us"); + }); + + it("renders successive URLs, one cropped in the middle", () => { + const text = + "- http://example-long.fr http://example.com http://example.us -"; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + cropLimit: 60, + }); + + expect(element.text()).toEqual( + "- http://example-long.fr http:…xample.com http://example.us -" + ); + const linkFr = element.find("a").at(0); + expect(linkFr.prop("href")).toBe("http://example-long.fr"); + expect(linkFr.prop("title")).toBe("http://example-long.fr"); + + const linkCom = element.find("a").at(1); + expect(linkCom.prop("href")).toBe("http://example.com"); + expect(linkCom.prop("title")).toBe("http://example.com"); + + const linkUs = element.find("a").at(2); + expect(linkUs.prop("href")).toBe("http://example.us"); + expect(linkUs.prop("title")).toBe("http://example.us"); + }); + + it("renders successive cropped URLs with cropped elements between", () => { + const text = + "- http://example.fr test http://example.es test http://example.us -"; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + cropLimit: 20, + }); + + expect(element.text()).toEqual("- http://e…ample.us -"); + const linkFr = element.find("a").at(0); + expect(linkFr.prop("href")).toBe("http://example.fr"); + expect(linkFr.prop("title")).toBe("http://example.fr"); + + const linkUs = element.find("a").at(1); + expect(linkUs.prop("href")).toBe("http://example.us"); + expect(linkUs.prop("title")).toBe("http://example.us"); + }); + + it("renders a cropped URL followed by a cropped string", () => { + const text = "http://example.fr abcdefghijkl"; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + cropLimit: 20, + }); + + expect(element.text()).toEqual("http://exa…cdefghijkl"); + const linkFr = element.find("a").at(0); + expect(linkFr.prop("href")).toBe("http://example.fr"); + expect(linkFr.prop("title")).toBe("http://example.fr"); + }); + + it("renders a cropped string followed by a cropped URL", () => { + const text = "abcdefghijkl stripped http://example.fr "; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + cropLimit: 20, + }); + + expect(element.text()).toEqual("abcdefghij…xample.fr "); + const linkFr = element.find("a").at(0); + expect(linkFr.prop("href")).toBe("http://example.fr"); + expect(linkFr.prop("title")).toBe("http://example.fr"); + }); + + it("renders URLs without unrelated characters", () => { + const text = + "global(http://example.com) and local(http://example.us)" + + " and maybe https://example.fr, “https://example.cz“, https://example.es?"; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + }); + + expect(element.text()).toEqual(text); + const linkCom = element.find("a").at(0); + expect(linkCom.prop("href")).toBe("http://example.com"); + + const linkUs = element.find("a").at(1); + expect(linkUs.prop("href")).toBe("http://example.us"); + + const linkFr = element.find("a").at(2); + expect(linkFr.prop("href")).toBe("https://example.fr"); + + const linkCz = element.find("a").at(3); + expect(linkCz.prop("href")).toBe("https://example.cz"); + + const linkEs = element.find("a").at(4); + expect(linkEs.prop("href")).toBe("https://example.es"); + }); + + it("renders a cropped URL with urlCropLimit", () => { + const xyzUrl = "http://xyz.com/abcdefghijklmnopqrst"; + const text = `${xyzUrl} is the best`; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + urlCropLimit: 20, + }); + + expect(element.text()).toEqual(text); + const link = element.find("a.cropped-url").at(0); + expect(link.prop("href")).toBe(xyzUrl); + expect(link.prop("title")).toBe(xyzUrl); + const linkParts = link.find("span"); + expect(linkParts.at(0).hasClass("cropped-url-start")).toBe(true); + expect(linkParts.at(0).text()).toEqual("http://xyz"); + expect(linkParts.at(1).hasClass("cropped-url-middle")).toBe(true); + expect(linkParts.at(2).hasClass("cropped-url-end")).toBe(true); + expect(linkParts.at(2).text()).toEqual("klmnopqrst"); + }); + + it("renders multiple cropped URL", () => { + const xyzUrl = "http://xyz.com/abcdefghijklmnopqrst"; + const abcUrl = "http://abc.com/abcdefghijklmnopqrst"; + const text = `${xyzUrl} is lit, not ${abcUrl}`; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + urlCropLimit: 20, + }); + + expect(element.text()).toEqual(`${xyzUrl} is lit, not ${abcUrl}`); + + const links = element.find("a.cropped-url"); + const xyzLink = links.at(0); + expect(xyzLink.prop("href")).toBe(xyzUrl); + expect(xyzLink.prop("title")).toBe(xyzUrl); + const xyzLinkParts = xyzLink.find("span"); + expect(xyzLinkParts.at(0).hasClass("cropped-url-start")).toBe(true); + expect(xyzLinkParts.at(0).text()).toEqual("http://xyz"); + expect(xyzLinkParts.at(1).hasClass("cropped-url-middle")).toBe(true); + expect(xyzLinkParts.at(2).hasClass("cropped-url-end")).toBe(true); + expect(xyzLinkParts.at(2).text()).toEqual("klmnopqrst"); + + const abc = links.at(1); + expect(abc.prop("href")).toBe(abcUrl); + expect(abc.prop("title")).toBe(abcUrl); + const abcLinkParts = abc.find("span"); + expect(abcLinkParts.at(0).hasClass("cropped-url-start")).toBe(true); + expect(abcLinkParts.at(0).text()).toEqual("http://abc"); + expect(abcLinkParts.at(1).hasClass("cropped-url-middle")).toBe(true); + expect(abcLinkParts.at(2).hasClass("cropped-url-end")).toBe(true); + expect(abcLinkParts.at(2).text()).toEqual("klmnopqrst"); + }); + + it("renders full URL if smaller than cropLimit", () => { + const xyzUrl = "http://example.com/"; + + const openLink = jest.fn(); + const element = renderRep(xyzUrl, { + openLink, + useQuotes: false, + urlCropLimit: 20, + }); + + expect(element.text()).toEqual(xyzUrl); + const link = element.find("a").at(0); + expect(link.prop("href")).toBe(xyzUrl); + expect(link.prop("title")).toBe(xyzUrl); + expect(link.find(".cropped-url-start").length).toBe(0); + }); + + it("renders cropped URL followed by cropped string with urlCropLimit", () => { + const text = "http://example.fr abcdefghijkl"; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + cropLimit: 20, + }); + + expect(element.text()).toEqual("http://exa…cdefghijkl"); + const linkFr = element.find("a").at(0); + expect(linkFr.prop("href")).toBe("http://example.fr"); + expect(linkFr.prop("title")).toBe("http://example.fr"); + }); + + it("does not render a link if the URL has no scheme", () => { + const url = "example.com"; + const element = renderRep(url, { useQuotes: false }); + expect(element.text()).toEqual(url); + expect(element.find("a").exists()).toBeFalsy(); + }); + + it("does not render a link if the URL has an invalid scheme", () => { + const url = "foo://example.com"; + const element = renderRep(url, { useQuotes: false }); + expect(element.text()).toEqual(url); + expect(element.find("a").exists()).toBeFalsy(); + }); + + it("does not render an invalid URL that requires cropping", () => { + const text = + "//www.youtubeinmp3.com/download/?video=https://www.youtube.com/watch?v=8vkfsCIfDFc"; + const openLink = jest.fn(); + const element = renderRep(text, { + openLink, + useQuotes: false, + cropLimit: 60, + }); + expect(element.text()).toEqual( + "//www.youtubeinmp3.com/downloa…outube.com/watch?v=8vkfsCIfDFc" + ); + expect(element.find("a").exists()).toBeFalsy(); + }); + + it("does render a link in a plain array", () => { + const url = "http://example.com/abcdefghijabcdefghij"; + const string = `${url} some other text`; + const object = [string]; + const openLink = jest.fn(); + const element = renderRep(object, { openLink, noGrip: true }); + expect(element.text()).toEqual(`[ "${string}" ]`); + + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("does render a link in a grip array", () => { + const object = + require("resource://devtools/client/shared/components/test/node/stubs/reps/grip-array.js").get( + '["http://example.com/abcdefghijabcdefghij some other text"]' + ); + const length = getGripLengthBubbleText(object); + const openLink = jest.fn(); + const element = renderRep(object, { openLink }); + + const url = "http://example.com/abcdefghijabcdefghij"; + const string = `${url} some other text`; + expect(element.text()).toEqual(`Array${length} [ "${string}" ]`); + + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("does render a link in a plain object", () => { + const url = "http://example.com/abcdefghijabcdefghij"; + const string = `${url} some other text`; + const object = { test: string }; + const openLink = jest.fn(); + const element = renderRep(object, { openLink, noGrip: true }); + expect(element.text()).toEqual(`Object { test: "${string}" }`); + + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("does render a link in a grip object", () => { + const object = + require("resource://devtools/client/shared/components/test/node/stubs/reps/grip.js").get( + '{test: "http://example.com/ some other text"}' + ); + const openLink = jest.fn(); + const element = renderRep(object, { openLink }); + + const url = "http://example.com/"; + const string = `${url} some other text`; + expect(element.text()).toEqual(`Object { test: "${string}" }`); + + const link = element.find("a"); + expect(link.prop("href")).toBe(url); + expect(link.prop("title")).toBe(url); + + testLinkClick(link, openLink, url); + }); + + it("does not render links for js URL", () => { + const url = "javascript:x=42"; + const string = `${url} some other text`; + + const openLink = jest.fn(); + const element = renderRep(string, { openLink, useQuotes: false }); + expect(element.text()).toEqual(string); + const link = element.find("a"); + expect(link.exists()).toBe(false); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/string.test.js b/devtools/client/shared/components/test/node/components/reps/string.test.js new file mode 100644 index 0000000000..4e179ecab0 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/string.test.js @@ -0,0 +1,257 @@ +/* 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/>. */ + +"use strict"; + +const { shallow, mount } = require("enzyme"); +const { + ELLIPSIS, +} = require("resource://devtools/client/shared/components/reps/reps/rep-utils.js"); +const { + REPS, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { Rep } = REPS; + +const renderRep = (string, props) => + mount( + Rep({ + object: string, + ...props, + }) + ); + +const testCases = [ + { + name: "testMultiline", + props: { + object: "aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n", + }, + result: + '"aaaaaaaaaaaaaaaaaaaaa\\nbbbbbbbbbbbbbbbbbbb\\ncccccccccccccccc\\n"', + }, + { + name: "testMultilineLimit", + props: { + object: "aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n", + cropLimit: 20, + }, + result: `\"aaaaaaaaa${ELLIPSIS}cccccc\\n\"`, + }, + { + name: "testMultilineOpen", + props: { + object: "aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n", + member: { open: true }, + }, + result: + '"aaaaaaaaaaaaaaaaaaaaa\\nbbbbbbbbbbbbbbbbbbb\\ncccccccccccccccc\\n"', + }, + { + name: "testUseQuotes", + props: { + object: "abc", + useQuotes: false, + }, + result: "abc", + }, + { + name: "testNonPrintableCharacters", + props: { + object: "a\x01b", + useQuotes: false, + }, + result: "a\ufffdb", + }, + { + name: "testQuoting", + props: { + object: + "\t\n\r\"'\\\x1f\x9f\ufeff\ufffe\ud8000\u2063\ufffc\u2028\ueeee\ufffd", + useQuotes: true, + }, + result: + "`\\t\\n\\r\"'\\\\\\u001f\\u009f\\ufeff\\ufffe\\ud8000\\u2063" + + "\\ufffc\\u2028\\ueeee\ufffd`", + }, + { + name: "testUnpairedSurrogate", + props: { + object: "\uDC23", + useQuotes: true, + }, + result: '"\\udc23"', + }, + { + name: "testValidSurrogate", + props: { + object: "\ud83d\udeec", + useQuotes: true, + }, + result: '"\ud83d\udeec"', + }, + { + name: "testNoEscapeWhitespace", + props: { + object: "line 1\r\nline 2\n\tline 3", + useQuotes: true, + escapeWhitespace: false, + }, + result: '"line 1\r\nline 2\n\tline 3"', + }, + { + name: "testIgnoreFullTextWhenOpen", + props: { + object: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + fullText: + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaa", + member: { open: true }, + }, + result: '"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"', + }, + { + name: "testIgnoreFullTextWithLimit", + props: { + object: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + fullText: + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaa", + cropLimit: 20, + }, + result: `\"aaaaaaaaa${ELLIPSIS}aaaaaaaa\"`, + }, + { + name: "testIgnoreFullTextWhenOpenWithLimit", + props: { + object: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + fullText: + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaa", + member: { open: true }, + cropLimit: 20, + }, + result: '"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"', + }, + { + name: "testEmptyStringWithoutQuotes", + props: { + object: "", + transformEmptyString: true, + useQuotes: false, + }, + result: "<empty string>", + }, + { + name: "testEmptyStringWithoutQuotesAndNoTransform", + props: { + object: "", + useQuotes: false, + transformEmptyString: false, + }, + result: "", + }, + { + name: "testEmptyStringWithQuotes", + props: { + object: "", + useQuotes: true, + transformEmptyString: true, + }, + result: `""`, + }, + { + name: "testEmptyStringWithQuotesAndNoTransforms", + props: { + object: "", + useQuotes: true, + transformEmptyString: false, + }, + result: `""`, + }, + { + name: "testQuotingSingleQuote", + props: { + object: "'", + useQuotes: true, + }, + result: `"'"`, + }, + { + name: "testQuotingDoubleQuote", + props: { + object: '"', + useQuotes: true, + }, + result: `'"'`, + }, + { + name: "testQuotingBacktick", + props: { + object: "`", + useQuotes: true, + }, + result: '"`"', + }, + { + name: "testQuotingSingleAndDoubleQuotes", + props: { + object: "'\"", + useQuotes: true, + }, + result: "`'\"`", + }, + { + name: "testQuotingSingleAndDoubleQuotesAnd${", + props: { + object: "'\"${", + useQuotes: true, + }, + result: '"\'\\"${"', + }, + { + name: "testQuotingSingleQuoteAndBacktick", + props: { + object: "'`", + useQuotes: true, + }, + result: '"\'`"', + }, + { + name: "testQuotingDoubleQuoteAndBacktick", + props: { + object: '"`', + useQuotes: true, + }, + result: "'\"`'", + }, + { + name: "testQuotingSingleAndDoubleQuotesAndBacktick", + props: { + object: "'\"`", + useQuotes: true, + }, + result: '"\'\\"`"', + }, +]; + +describe("test String", () => { + for (const testCase of testCases) { + it(`String rep ${testCase.name}`, () => { + const renderedComponent = shallow(Rep(testCase.props)); + expect(renderedComponent.text()).toEqual(testCase.result); + }); + } + + it("If shouldRenderTooltip, StringRep displays a tooltip title on the span element.", () => { + const tooltipText = "This is a tooltip"; + const element = renderRep(tooltipText, { shouldRenderTooltip: true }); + expect(element.prop("title")).toBe('"This is a tooltip"'); + }); + + it("If !shouldRenderTooltip, StringRep doesn't display a tooltip title.", () => { + const noTooltip = "There is no tooltip"; + const element = renderRep(noTooltip, { shouldRenderTooltip: false }); + expect(element.prop("title")).toBe(undefined); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/stylesheet.test.js b/devtools/client/shared/components/test/node/components/reps/stylesheet.test.js new file mode 100644 index 0000000000..31c40facfe --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/stylesheet.test.js @@ -0,0 +1,41 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { StyleSheet, Rep } = REPS; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/stylesheet.js"); +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +describe("Test StyleSheet", () => { + const stub = stubs.get("StyleSheet")._grip; + + it("selects the StyleSheet Rep", () => { + expect(getRep(stub)).toEqual(StyleSheet.rep); + }); + + it("renders with the expected text content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual( + "CSSStyleSheet https://example.com/styles.css" + ); + expect(renderedComponent.prop("title")).toEqual( + "CSSStyleSheet https://example.com/styles.css" + ); + expectActorAttribute(renderedComponent, stub.actor); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/symbol.test.js b/devtools/client/shared/components/test/node/components/reps/symbol.test.js new file mode 100644 index 0000000000..4959e9a813 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/symbol.test.js @@ -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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); +const { + REPS, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { Rep } = REPS; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/symbol.js"); +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +describe("test Symbol", () => { + const stub = stubs.get("Symbol"); + + it("renders with the expected content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual('Symbol("foo")'); + expect(renderedComponent.prop("title")).toBe("Symbol(foo)"); + expectActorAttribute(renderedComponent, stub.actor); + }); +}); + +describe("test Symbol without identifier", () => { + const stub = stubs.get("SymbolWithoutIdentifier"); + + it("renders the expected content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual("Symbol()"); + expectActorAttribute(renderedComponent, stub.actor); + }); +}); + +describe("test Symbol with long string", () => { + const stub = stubs.get("SymbolWithLongString"); + + it("renders the expected content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + + expect(renderedComponent.text()).toEqual( + 'Symbol("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa…")' + ); + expectActorAttribute(renderedComponent, stub.actor); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/test-helpers.js b/devtools/client/shared/components/test/node/components/reps/test-helpers.js new file mode 100644 index 0000000000..d601f71e88 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/test-helpers.js @@ -0,0 +1,116 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + lengthBubble, +} = require("resource://devtools/client/shared/components/reps/shared/grip-length-bubble.js"); +const { + maxLengthMap: arrayLikeMaxLengthMap, + getLength: getArrayLikeLength, +} = require("resource://devtools/client/shared/components/reps/reps/grip-array.js"); +const { + maxLengthMap: mapMaxLengths, + getLength: getMapLength, +} = require("resource://devtools/client/shared/components/reps/reps/grip-map.js"); +const { + getGripPreviewItems, +} = require("resource://devtools/client/shared/components/reps/reps/rep-utils.js"); +const nodeConstants = require("resource://devtools/client/shared/components/reps/shared/dom-node-constants.js"); + +/** + * Get an array of all the items from the grip in parameter (including the grip + * itself) which can be selected in the inspector. + * + * @param {Object} Grip + * @return {Array} Flat array of the grips which can be selected in the + * inspector + */ +function getSelectableInInspectorGrips(grip) { + const grips = new Set(getFlattenedGrips([grip])); + return [...grips].filter(isGripSelectableInInspector); +} + +/** + * Indicate if a Grip can be selected in the inspector, + * i.e. if it represents a node element. + * + * @param {Object} Grip + * @return {Boolean} + */ +function isGripSelectableInInspector(grip) { + return ( + grip && + typeof grip === "object" && + grip.preview && + [nodeConstants.TEXT_NODE, nodeConstants.ELEMENT_NODE].includes( + grip.preview.nodeType + ) + ); +} + +/** + * Get a flat array of all the grips and their preview items. + * + * @param {Array} Grips + * @return {Array} Flat array of the grips and their preview items + */ +function getFlattenedGrips(grips) { + return grips.reduce((res, grip) => { + const previewItems = getGripPreviewItems(grip); + const flatPreviewItems = previewItems.length + ? getFlattenedGrips(previewItems) + : []; + + return [...res, grip, ...flatPreviewItems]; + }, []); +} + +function expectActorAttribute(wrapper, expectedValue) { + const actorIdAttribute = "data-link-actor-id"; + const attrElement = wrapper.find(`[${actorIdAttribute}]`); + expect(attrElement.exists()).toBeTruthy(); + expect(attrElement.first().prop("data-link-actor-id")).toBe(expectedValue); +} + +function getGripLengthBubbleText(object, props) { + const component = lengthBubble({ + object, + maxLengthMap: arrayLikeMaxLengthMap, + getLength: getArrayLikeLength, + ...props, + }); + + return component ? shallow(component).text() : ""; +} + +function getMapLengthBubbleText(object, props) { + return getGripLengthBubbleText(object, { + maxLengthMap: mapMaxLengths, + getLength: getMapLength, + showZeroLength: true, + ...props, + }); +} + +function createGripMapEntry(key, value) { + return { + type: "mapEntry", + preview: { + key, + value, + }, + }; +} + +module.exports = { + createGripMapEntry, + expectActorAttribute, + getSelectableInInspectorGrips, + getGripLengthBubbleText, + getMapLengthBubbleText, +}; diff --git a/devtools/client/shared/components/test/node/components/reps/text-node.test.js b/devtools/client/shared/components/test/node/components/reps/text-node.test.js new file mode 100644 index 0000000000..13a8a89154 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/text-node.test.js @@ -0,0 +1,203 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { TextNode } = REPS; + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/text-node.js"); +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); +const { + ELLIPSIS, +} = require("resource://devtools/client/shared/components/reps/reps/rep-utils.js"); + +function quoteNewlines(text) { + return text.split("\n").join("\\n"); +} + +describe("TextNode", () => { + it("selects TextNode Rep as expected", () => { + expect(getRep(stubs.get("testRendering")._grip)).toBe(TextNode.rep); + }); + + it("renders as expected", () => { + const object = stubs.get("testRendering")._grip; + const renderRep = props => shallow(TextNode.rep({ object, ...props })); + + const defaultOutput = '#text "hello world"'; + + let component = renderRep({ shouldRenderTooltip: true, mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.TINY }); + expect(component.text()).toBe("#text"); + expect(component.prop("title")).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.HEADER }); + expect(component.text()).toBe("#text"); + expect(component.prop("title")).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.LONG }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultOutput); + expectActorAttribute(component, object.actor); + }); + + it("renders as expected with EOL", () => { + const object = stubs.get("testRenderingWithEOL")._grip; + const renderRep = props => shallow(TextNode.rep({ object, ...props })); + + const defaultOutput = quoteNewlines('#text "hello\nworld"'); + const defaultTooltip = '#text "hello\nworld"'; + + let component = renderRep({ shouldRenderTooltip: true, mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.TINY }); + expect(component.text()).toBe("#text"); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.HEADER }); + expect(component.text()).toBe("#text"); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.LONG }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultTooltip); + }); + + it("renders as expected with double quote", () => { + const object = stubs.get("testRenderingWithDoubleQuote")._grip; + const renderRep = props => shallow(TextNode.rep({ object, ...props })); + + const defaultOutput = "#text 'hello\"world'"; + const defaultTooltip = '#text "hello"world"'; + + let component = renderRep({ shouldRenderTooltip: true, mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.TINY }); + expect(component.text()).toBe("#text"); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.HEADER }); + expect(component.text()).toBe("#text"); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.LONG }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultTooltip); + }); + + it("renders as expected with long string", () => { + const object = stubs.get("testRenderingWithLongString")._grip; + const renderRep = props => shallow(TextNode.rep({ object, ...props })); + const initialString = object.preview.textContent.initial; + + const defaultOutput = `#text "${quoteNewlines(initialString)}${ELLIPSIS}"`; + const defaultTooltip = `#text "${initialString}"`; + + let component = renderRep({ shouldRenderTooltip: true, mode: undefined }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.TINY }); + expect(component.text()).toBe("#text"); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.HEADER }); + expect(component.text()).toBe("#text"); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.SHORT }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultTooltip); + + component = renderRep({ shouldRenderTooltip: true, mode: MODE.LONG }); + expect(component.text()).toBe(defaultOutput); + expect(component.prop("title")).toBe(defaultTooltip); + }); + + it("calls the expected function on mouseover", () => { + const object = stubs.get("testRendering")._grip; + const onDOMNodeMouseOver = jest.fn(); + const wrapper = shallow(TextNode.rep({ object, onDOMNodeMouseOver })); + + wrapper.simulate("mouseover"); + + expect(onDOMNodeMouseOver.mock.calls).toHaveLength(1); + expect(onDOMNodeMouseOver).toHaveBeenCalledWith(object); + }); + + it("calls the expected function on mouseout", () => { + const object = stubs.get("testRendering")._grip; + const onDOMNodeMouseOut = jest.fn(); + const wrapper = shallow(TextNode.rep({ object, onDOMNodeMouseOut })); + + wrapper.simulate("mouseout"); + + expect(onDOMNodeMouseOut.mock.calls).toHaveLength(1); + expect(onDOMNodeMouseOut).toHaveBeenCalledWith(object); + }); + + it("displays a button when the node is connected", () => { + const object = stubs.get("testRendering")._grip; + + const onInspectIconClick = jest.fn(); + const wrapper = shallow(TextNode.rep({ object, onInspectIconClick })); + + const inspectIconNode = wrapper.find(".open-inspector"); + expect(inspectIconNode !== null).toBe(true); + + const event = Symbol("click-event"); + inspectIconNode.simulate("click", event); + + // The function is called once + expect(onInspectIconClick.mock.calls).toHaveLength(1); + const [arg1, arg2] = onInspectIconClick.mock.calls[0]; + // First argument is the grip + expect(arg1).toBe(object); + // Second one is the event + expect(arg2).toBe(event); + }); + + it("does not display a button when the node is connected", () => { + const object = stubs.get("testRenderingDisconnected")._grip; + + const onInspectIconClick = jest.fn(); + const wrapper = shallow(TextNode.rep({ object, onInspectIconClick })); + expect(wrapper.find(".open-inspector")).toHaveLength(0); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/undefined.test.js b/devtools/client/shared/components/test/node/components/reps/undefined.test.js new file mode 100644 index 0000000000..56fa512f55 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/undefined.test.js @@ -0,0 +1,58 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { Undefined, Rep } = REPS; + +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/undefined.js"); +// Test that correct rep is chosen +describe("Test Undefined", () => { + const stub = stubs.get("Undefined"); + + it("selects Undefined as expected", () => { + expect(getRep(stub)).toBe(Undefined.rep); + }); + + it("Rep correctly selects Undefined Rep for plain JS undefined", () => { + expect(getRep(undefined, undefined, true)).toBe(Undefined.rep); + }); + + it("Undefined rep has expected text content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + expect(renderedComponent.text()).toEqual("undefined"); + }); + + it("Undefined rep has expected class names", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + expect(renderedComponent.hasClass("objectBox objectBox-undefined")).toEqual( + true + ); + }); + + it("Undefined rep has expected title", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + expect(renderedComponent.prop("title")).toEqual("undefined"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/reps/window.test.js b/devtools/client/shared/components/test/node/components/reps/window.test.js new file mode 100644 index 0000000000..9f789d3be6 --- /dev/null +++ b/devtools/client/shared/components/test/node/components/reps/window.test.js @@ -0,0 +1,197 @@ +/* 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/>. */ + +"use strict"; + +const { shallow } = require("enzyme"); + +const { + REPS, + getRep, +} = require("resource://devtools/client/shared/components/reps/reps/rep.js"); + +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { Rep, Window } = REPS; +const stubs = require("resource://devtools/client/shared/components/test/node/stubs/reps/window.js"); +const { + expectActorAttribute, +} = require("resource://devtools/client/shared/components/test/node/components/reps/test-helpers.js"); + +describe("test Window", () => { + const stub = stubs.get("Window")._grip; + + it("selects Window Rep correctly", () => { + expect(getRep(stub)).toBe(Window.rep); + }); + + it("renders with correct class name", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + + expect(renderedComponent.hasClass("objectBox-Window")).toBe(true); + expectActorAttribute(renderedComponent, stub.actor); + }); + + it("renders with correct content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual( + "Window data:text/html;charset=utf-8,stub generation" + ); + expect(renderedComponent.prop("title")).toEqual( + "Window data:text/html;charset=utf-8,stub generation" + ); + }); + + it("renders with correct inner HTML structure and content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + + expect(renderedComponent.find(".location").text()).toEqual( + "data:text/html;charset=utf-8,stub generation" + ); + }); + + it("renders with expected text in TINY mode", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + mode: MODE.TINY, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("Window"); + expect(renderedComponent.prop("title")).toEqual( + "Window data:text/html;charset=utf-8,stub generation" + ); + }); + + it("renders with expected text in LONG mode", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + mode: MODE.LONG, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual( + "Window data:text/html;charset=utf-8,stub generation" + ); + expect(renderedComponent.prop("title")).toEqual( + "Window data:text/html;charset=utf-8,stub generation" + ); + }); + + it("renders expected text in TINY mode with Custom display class", () => { + const renderedComponent = shallow( + Rep({ + object: { + ...stub, + displayClass: "Custom", + }, + mode: MODE.TINY, + }) + ); + + expect(renderedComponent.text()).toEqual("Custom"); + }); + + it("renders expected text in LONG mode with Custom display class", () => { + const renderedComponent = shallow( + Rep({ + object: { + ...stub, + displayClass: "Custom", + }, + mode: MODE.LONG, + title: "Custom", + }) + ); + + expect(renderedComponent.text()).toEqual( + "Custom data:text/html;charset=utf-8,stub generation" + ); + }); +}); + +describe("test cross-process iframe contentWindow", () => { + const stub = stubs.get("CrossOriginIframeContentWindow")._grip; + + it("selects Window Rep correctly", () => { + expect(getRep(stub)).toBe(Window.rep); + }); + + it("renders with correct class name", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + + expect(renderedComponent.hasClass("objectBox-Window")).toBe(true); + expectActorAttribute(renderedComponent, stub.actor); + }); + + it("renders with correct content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual( + "Window http://example.org/document-builder.sjs?html=example.org" + ); + expect(renderedComponent.prop("title")).toEqual( + "Window http://example.org/document-builder.sjs?html=example.org" + ); + }); +}); + +describe("test cross-process iframe top window", () => { + const stub = stubs.get("CrossOriginIframeTopWindow")._grip; + + it("selects Window Rep correctly", () => { + expect(getRep(stub)).toBe(Window.rep); + }); + + it("renders with correct class name", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + }) + ); + + expect(renderedComponent.hasClass("objectBox-Window")).toBe(true); + expectActorAttribute(renderedComponent, stub.actor); + }); + + it("renders with correct content", () => { + const renderedComponent = shallow( + Rep({ + object: stub, + shouldRenderTooltip: true, + }) + ); + + expect(renderedComponent.text()).toEqual("Window Restricted"); + expect(renderedComponent.prop("title")).toEqual("Window Restricted"); + }); +}); diff --git a/devtools/client/shared/components/test/node/components/tree.test.js b/devtools/client/shared/components/test/node/components/tree.test.js new file mode 100644 index 0000000000..c70b66e8ff --- /dev/null +++ b/devtools/client/shared/components/test/node/components/tree.test.js @@ -0,0 +1,911 @@ +/* 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/>. */ + +"use strict"; + +/* global jest */ + +const React = require("react"); +const { mount } = require("enzyme"); +const dom = require("react-dom-factories"); + +const { Component, createFactory } = React; +const Tree = createFactory( + require("resource://devtools/client/shared/components/Tree.js") +); + +function mountTree(overrides = {}) { + return mount( + createFactory( + class container extends Component { + constructor(props) { + super(props); + const state = { + expanded: overrides.expanded || new Set(), + focused: overrides.focused, + active: overrides.active, + }; + delete overrides.focused; + delete overrides.active; + 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 }; + }); + }, + onActivate: x => { + this.setState(previousState => { + return { active: 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, + active: this.state.active, + }, + 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("calls shouldItemUpdate when provided", () => { + const shouldItemUpdate = jest.fn((prev, next) => true); + const wrapper = mountTree({ + shouldItemUpdate, + }); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(shouldItemUpdate.mock.calls).toHaveLength(0); + + wrapper.find("Tree").first().instance().forceUpdate(); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(shouldItemUpdate.mock.calls).toHaveLength(2); + + expect(shouldItemUpdate.mock.calls[0][0]).toBe("A"); + expect(shouldItemUpdate.mock.calls[0][1]).toBe("A"); + expect(shouldItemUpdate.mock.results[0].value).toBe(true); + expect(shouldItemUpdate.mock.calls[1][0]).toBe("M"); + expect(shouldItemUpdate.mock.calls[1][1]).toBe("M"); + expect(shouldItemUpdate.mock.results[1].value).toBe(true); + }); + + it("active item - renders as expected when clicking away", () => { + const wrapper = mountTree({ + expanded: new Set("ABCDEFGHIJKLMNO".split("")), + focused: "G", + active: "G", + }); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").prop("id")).toBe("key-G"); + + getTreeNodes(wrapper).first().simulate("click"); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".focused").prop("id")).toBe("key-A"); + expect(wrapper.find(".active").exists()).toBe(false); + }); + + it("active item - renders as expected when tree blurs", () => { + const wrapper = mountTree({ + expanded: new Set("ABCDEFGHIJKLMNO".split("")), + focused: "G", + active: "G", + }); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").prop("id")).toBe("key-G"); + + wrapper.simulate("blur"); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").exists()).toBe(false); + }); + + it("active item - renders as expected when moving away with keyboard", () => { + const wrapper = mountTree({ + expanded: new Set("ABCDEFGHIJKLMNO".split("")), + focused: "L", + active: "L", + }); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").prop("id")).toBe("key-L"); + + simulateKeyDown(wrapper, "ArrowUp"); + expect(wrapper.find(".active").exists()).toBe(false); + }); + + it("active item - renders as expected when using keyboard and Enter", () => { + const wrapper = mountTree({ + expanded: new Set("ABCDEFGHIJKLMNO".split("")), + focused: "L", + }); + wrapper.getDOMNode().focus(); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").exists()).toBe(false); + + simulateKeyDown(wrapper, "Enter"); + expect(wrapper.find(".active").prop("id")).toBe("key-L"); + + expect(wrapper.getDOMNode().ownerDocument.activeElement).toBe( + wrapper.getDOMNode() + ); + + simulateKeyDown(wrapper, "Escape"); + expect(wrapper.find(".active").exists()).toBe(false); + expect(wrapper.getDOMNode().ownerDocument.activeElement).toBe( + wrapper.getDOMNode() + ); + }); + + it("active item - renders as expected when using keyboard and Space", () => { + const wrapper = mountTree({ + expanded: new Set("ABCDEFGHIJKLMNO".split("")), + focused: "L", + }); + wrapper.getDOMNode().focus(); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").exists()).toBe(false); + + simulateKeyDown(wrapper, " "); + expect(wrapper.find(".active").prop("id")).toBe("key-L"); + + simulateKeyDown(wrapper, "Escape"); + expect(wrapper.find(".active").exists()).toBe(false); + }); + + it("active item - focus is inside the tree node when possible", () => { + const wrapper = mountTree({ + expanded: new Set("ABCDEFGHIJKLMNO".split("")), + focused: "L", + renderItem: renderItemWithFocusableContent, + }); + wrapper.getDOMNode().focus(); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").exists()).toBe(false); + expect(wrapper.getDOMNode().ownerDocument.activeElement).toBe( + wrapper.getDOMNode() + ); + + simulateKeyDown(wrapper, "Enter"); + expect(wrapper.find(".active").prop("id")).toBe("key-L"); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.getDOMNode().ownerDocument.activeElement).toBe( + wrapper.find("#active-anchor").getDOMNode() + ); + }); + + it("active item - navigate inside the tree node", () => { + const wrapper = mountTree({ + expanded: new Set("ABCDEFGHIJKLMNO".split("")), + focused: "L", + renderItem: renderItemWithFocusableContent, + }); + wrapper.getDOMNode().focus(); + simulateKeyDown(wrapper, "Enter"); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").prop("id")).toBe("key-L"); + expect(wrapper.getDOMNode().ownerDocument.activeElement).toBe( + wrapper.find("#active-anchor").getDOMNode() + ); + + simulateKeyDown(wrapper, "Tab"); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").prop("id")).toBe("key-L"); + expect(wrapper.getDOMNode().ownerDocument.activeElement).toBe( + wrapper.find("#active-anchor").getDOMNode() + ); + + simulateKeyDown(wrapper, "Tab", { shiftKey: true }); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").prop("id")).toBe("key-L"); + expect(wrapper.getDOMNode().ownerDocument.activeElement).toBe( + wrapper.find("#active-anchor").getDOMNode() + ); + }); + + it("active item - focus is inside the tree node and then blur", () => { + const wrapper = mountTree({ + expanded: new Set("ABCDEFGHIJKLMNO".split("")), + focused: "L", + renderItem: renderItemWithFocusableContent, + }); + wrapper.getDOMNode().focus(); + simulateKeyDown(wrapper, "Enter"); + expect(formatTree(wrapper)).toMatchSnapshot(); + expect(wrapper.find(".active").prop("id")).toBe("key-L"); + expect(wrapper.getDOMNode().ownerDocument.activeElement).toBe( + wrapper.find("#active-anchor").getDOMNode() + ); + + wrapper.find("#active-anchor").simulate("blur"); + expect(wrapper.find(".active").exists()).toBe(false); + expect(wrapper.getDOMNode().ownerDocument.activeElement).toBe( + wrapper.getDOMNode().ownerDocument.body + ); + }); + + 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.current; + 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.current; + treeRef.focus = jest.fn(); + wrapper.simulate("blur"); + expect(treeRef.focus.mock.calls).toHaveLength(0); + }); + + 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, options) { + wrapper.simulate("keydown", { + key, + preventDefault: () => {}, + stopPropagation: () => {}, + ...options, + }); +} + +function renderItemWithFocusableContent(x, depth, focused, arrow) { + const children = [arrow, focused ? "[" : null, x]; + if (x === "L") { + children.push(dom.a({ id: "active-anchor", href: "#" }, " anchor")); + } + + if (focused) { + children.push("]"); + } + + return dom.div({}, ...children); +} + +/* + * 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", + }, +}; diff --git a/devtools/client/shared/components/test/node/jest.config.js b/devtools/client/shared/components/test/node/jest.config.js new file mode 100644 index 0000000000..9c5a211c25 --- /dev/null +++ b/devtools/client/shared/components/test/node/jest.config.js @@ -0,0 +1,16 @@ +/* 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 __dirname */ + +"use strict"; + +const sharedJestConfig = require(`${__dirname}/../../../test-helpers/shared-jest.config`); + +module.exports = { + ...sharedJestConfig, + setupFiles: ["<rootDir>/setup.js"], + snapshotSerializers: ["enzyme-to-json/serializer"], + testURL: "http://localhost/", +}; diff --git a/devtools/client/shared/components/test/node/package.json b/devtools/client/shared/components/test/node/package.json new file mode 100644 index 0000000000..9db588176e --- /dev/null +++ b/devtools/client/shared/components/test/node/package.json @@ -0,0 +1,27 @@ +{ + "name": "devtools-client-shared-components-tests", + "license": "MPL-2.0", + "version": "0.0.1", + "engines": { + "node": ">=8.9.4" + }, + "scripts": { + "test": "jest", + "test-ci": "jest --json" + }, + "dependencies": { + "@babel/plugin-proposal-class-properties": "7.10.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-optional-chaining": "^7.8.3", + "babel-plugin-transform-amd-to-commonjs": "1.4.0", + "enzyme": "^3.9.0", + "enzyme-adapter-react-16": "^1.13.2", + "enzyme-to-json": "^3.3.5", + "jest": "^24.6.0", + "jsdom": "20.0.0", + "react": "16.4.1", + "react-dom": "16.4.1", + "react-dom-factories": "1.0.2", + "react-test-renderer": "16.4.1" + } +} diff --git a/devtools/client/shared/components/test/node/setup.js b/devtools/client/shared/components/test/node/setup.js new file mode 100644 index 0000000000..570e4462ae --- /dev/null +++ b/devtools/client/shared/components/test/node/setup.js @@ -0,0 +1,15 @@ +/* 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/>. */ + +"use strict"; + +// Configure enzyme with React 16 adapter. +const Enzyme = require("enzyme"); +const Adapter = require("enzyme-adapter-react-16"); +Enzyme.configure({ adapter: new Adapter() }); + +const { + setMocksInGlobal, +} = require("resource://devtools/client/shared/test-helpers/shared-node-helpers.js"); +setMocksInGlobal(); diff --git a/devtools/client/shared/components/test/node/stubs/object-inspector/grip.js b/devtools/client/shared/components/test/node/stubs/object-inspector/grip.js new file mode 100644 index 0000000000..5df79fd62d --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/object-inspector/grip.js @@ -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; diff --git a/devtools/client/shared/components/test/node/stubs/object-inspector/map.js b/devtools/client/shared/components/test/node/stubs/object-inspector/map.js new file mode 100644 index 0000000000..ed97b51c88 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/object-inspector/map.js @@ -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; diff --git a/devtools/client/shared/components/test/node/stubs/object-inspector/performance.js b/devtools/client/shared/components/test/node/stubs/object-inspector/performance.js new file mode 100644 index 0000000000..5488070f14 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/object-inspector/performance.js @@ -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; diff --git a/devtools/client/shared/components/test/node/stubs/reps/accessible.js b/devtools/client/shared/components/test/node/stubs/reps/accessible.js new file mode 100644 index 0000000000..3c9834e6d3 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/accessible.js @@ -0,0 +1,74 @@ +/* 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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("Document", { + actor: "server1.conn1.child1/accessible31", + typeName: "accessible", + preview: { + name: "New Tab", + role: "document", + isConnected: true, + }, +}); + +stubs.set("ButtonMenu", { + actor: "server1.conn1.child1/accessible38", + typeName: "accessible", + preview: { + name: "New to Nightly? Let’s get started.", + role: "buttonmenu", + isConnected: true, + }, +}); + +stubs.set("NoName", { + actor: "server1.conn1.child1/accessible93", + typeName: "accessible", + preview: { + name: null, + role: "text container", + isConnected: true, + }, +}); + +stubs.set("NoPreview", { + actor: "server1.conn1.child1/accessible93", + typeName: "accessible", +}); + +stubs.set("DisconnectedAccessible", { + actor: null, + typeName: "accessible", + preview: { + name: null, + role: "section", + isConnected: false, + }, +}); + +const name = "a".repeat(1000); +stubs.set("AccessibleWithLongName", { + actor: "server1.conn1.child1/accessible98", + typeName: "accessible", + preview: { + name, + role: "text leaf", + isConnected: true, + }, +}); + +stubs.set("PushButton", { + actor: "server1.conn1.child1/accessible157", + typeName: "accessible", + preview: { + name: "Search", + role: "pushbutton", + isConnected: true, + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/accessor.js b/devtools/client/shared/components/test/node/stubs/reps/accessor.js new file mode 100644 index 0000000000..cee4a836ce --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/accessor.js @@ -0,0 +1,85 @@ +/* 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/>. */ + +"use strict"; + +const stubs = new Map(); + +stubs.set("getter", { + configurable: true, + enumerable: true, + get: { + type: "object", + actor: "server2.conn1.child1/obj106", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + name: "get x", + displayName: "get x", + location: { + url: "debugger eval code", + line: 1, + }, + }, + set: { + type: "undefined", + }, +}); + +stubs.set("setter", { + configurable: true, + enumerable: true, + get: { + type: "undefined", + }, + set: { + type: "object", + actor: "server2.conn1.child1/obj116", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + name: "set x", + displayName: "set x", + location: { + url: "debugger eval code", + line: 1, + }, + }, +}); + +stubs.set("getter setter", { + configurable: true, + enumerable: true, + get: { + type: "object", + actor: "server2.conn1.child1/obj127", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + name: "get x", + displayName: "get x", + location: { + url: "debugger eval code", + line: 1, + }, + }, + set: { + type: "object", + actor: "server2.conn1.child1/obj128", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + name: "set x", + displayName: "set x", + location: { + url: "debugger eval code", + line: 1, + }, + }, +}); +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/attribute.js b/devtools/client/shared/components/test/node/stubs/reps/attribute.js new file mode 100644 index 0000000000..d820da07f7 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/attribute.js @@ -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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`Attribute`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal12884901889/obj24", + "class": "Attr", + "ownPropertyLength": 0, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "DOMNode", + "nodeType": 2, + "nodeName": "class", + "isConnected": false, + "value": "autocomplete-suggestions" + }, + "contentDomReference": { + "browsingContextId": 51, + "id": 0.4985715593006155 + } + }, + "actorID": "server0.conn0.windowGlobal12884901889/obj24" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/big-int.js b/devtools/client/shared/components/test/node/stubs/reps/big-int.js new file mode 100644 index 0000000000..423145359b --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/big-int.js @@ -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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("1n", { + type: "BigInt", + text: "1", +}); + +stubs.set("-2n", { + type: "BigInt", + text: "-2", +}); + +stubs.set("0n", { + type: "BigInt", + text: "0", +}); + +stubs.set("[1n,-2n,0n]", { + type: "object", + actor: "server1.conn15.child1/obj27", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 3, + items: [ + { + type: "BigInt", + text: "1", + }, + { + type: "BigInt", + text: "-2", + }, + { + type: "BigInt", + text: "0", + }, + ], + }, +}); + +stubs.set("new Set([1n,-2n,0n])", { + type: "object", + actor: "server1.conn15.child1/obj29", + class: "Set", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "ArrayLike", + length: 3, + items: [ + { + type: "BigInt", + text: "1", + }, + { + type: "BigInt", + text: "-2", + }, + { + type: "BigInt", + text: "0", + }, + ], + }, +}); + +stubs.set("new Map([ [1n, -1n], [-2n, 0n], [0n, -2n]])", { + type: "object", + actor: "server1.conn15.child1/obj32", + class: "Map", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 3, + entries: [ + [ + { + type: "BigInt", + text: "1", + }, + { + type: "BigInt", + text: "-1", + }, + ], + [ + { + type: "BigInt", + text: "-2", + }, + { + type: "BigInt", + text: "0", + }, + ], + [ + { + type: "BigInt", + text: "0", + }, + { + type: "BigInt", + text: "-2", + }, + ], + ], + }, +}); + +stubs.set("({simple: 1n, negative: -2n, zero: 0n})", { + type: "object", + actor: "server1.conn15.child1/obj34", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 3, + preview: { + kind: "Object", + ownProperties: { + simple: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "BigInt", + text: "1", + }, + }, + negative: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "BigInt", + text: "-2", + }, + }, + zero: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "BigInt", + text: "0", + }, + }, + }, + ownSymbols: [], + ownPropertiesLength: 3, + ownSymbolsLength: 0, + safeGetterValues: {}, + }, +}); + +stubs.set("Promise.resolve(1n)", { + type: "object", + actor: "server1.conn15.child1/obj36", + class: "Promise", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: { + "<state>": { + value: "fulfilled", + }, + "<value>": { + value: { + type: "BigInt", + text: "1", + }, + }, + }, + ownPropertiesLength: 2, + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/browser_dummy.js b/devtools/client/shared/components/test/node/stubs/reps/browser_dummy.js new file mode 100644 index 0000000000..8a9353cd7e --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/browser_dummy.js @@ -0,0 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This file is a fake test so we can have support files in the stubs.ini, which are then +// referenced as support files in the webconsole mochitest ini file. + +"use strict"; + +add_task(function() { + ok(true, "this is not a test"); +}); diff --git a/devtools/client/shared/components/test/node/stubs/reps/comment-node.js b/devtools/client/shared/components/test/node/stubs/reps/comment-node.js new file mode 100644 index 0000000000..5ee0970283 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/comment-node.js @@ -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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`Comment`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal4294967299/obj26", + "class": "Comment", + "ownPropertyLength": 0, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "DOMNode", + "nodeType": 8, + "nodeName": "#comment", + "isConnected": false, + "textContent": "test\nand test\nand test\nand test\nand test\nand test\nand test" + }, + "contentDomReference": { + "browsingContextId": 51, + "id": 0.7876406289746626 + } + }, + "actorID": "server0.conn0.windowGlobal4294967299/obj26" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/date-time.js b/devtools/client/shared/components/test/node/stubs/reps/date-time.js new file mode 100644 index 0000000000..9027397104 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/date-time.js @@ -0,0 +1,47 @@ +/* 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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`DateTime`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal4294967299/obj28", + "class": "Date", + "ownPropertyLength": 0, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "timestamp": 1459372644859 + } + }, + "actorID": "server0.conn0.windowGlobal4294967299/obj28" +}); + +stubs.set(`InvalidDateTime`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal4294967299/obj30", + "class": "Date", + "ownPropertyLength": 0, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "timestamp": { + "type": "NaN" + } + } + }, + "actorID": "server0.conn0.windowGlobal4294967299/obj30" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/document-type.js b/devtools/client/shared/components/test/node/stubs/reps/document-type.js new file mode 100644 index 0000000000..a499670b58 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/document-type.js @@ -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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("html", { + type: "object", + actor: "server1.conn7.child1/obj195", + class: "DocumentType", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 10, + nodeName: "html", + isConnected: true, + }, +}); + +stubs.set("unnamed", { + type: "object", + actor: "server1.conn7.child1/obj195", + class: "DocumentType", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 10, + nodeName: "", + isConnected: true, + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/document.js b/devtools/client/shared/components/test/node/stubs/reps/document.js new file mode 100644 index 0000000000..555bbff2d1 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/document.js @@ -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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("Document", { + type: "object", + class: "HTMLDocument", + actor: "server1.conn17.obj115", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "DOMNode", + nodeType: 9, + nodeName: "#document", + location: "https://www.mozilla.org/en-US/firefox/new/", + }, +}); + +stubs.set("Location-less Document", { + type: "object", + actor: "server1.conn6.child1/obj31", + class: "HTMLDocument", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "DOMNode", + nodeType: 9, + nodeName: "#document", + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/element-node.js b/devtools/client/shared/components/test/node/stubs/reps/element-node.js new file mode 100644 index 0000000000..b0b4369b3c --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/element-node.js @@ -0,0 +1,292 @@ +/* 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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("BodyNode", { + type: "object", + actor: "server1.conn1.child1/obj30", + class: "HTMLBodyElement", + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "body", + attributes: { + class: "body-class", + id: "body-id", + }, + attributesLength: 2, + }, +}); + +stubs.set("DocumentElement", { + type: "object", + actor: "server1.conn1.child1/obj40", + class: "HTMLHtmlElement", + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "html", + attributes: { + dir: "ltr", + lang: "en-US", + }, + attributesLength: 2, + }, +}); + +stubs.set("Node", { + type: "object", + actor: "server1.conn2.child1/obj116", + class: "HTMLInputElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "input", + isConnected: true, + attributes: { + id: "newtab-customize-button", + dir: "ltr", + title: "Customize your New Tab page", + class: "bar baz", + value: "foo", + type: "button", + }, + attributesLength: 6, + }, +}); + +stubs.set("DisconnectedNode", { + type: "object", + actor: "server1.conn2.child1/obj116", + class: "HTMLInputElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "input", + isConnected: false, + attributes: { + id: "newtab-customize-button", + dir: "ltr", + title: "Customize your New Tab page", + class: "bar baz", + value: "foo", + type: "button", + }, + attributesLength: 6, + }, +}); + +stubs.set("NodeWithLeadingAndTrailingSpacesClassName", { + type: "object", + actor: "server1.conn3.child1/obj59", + class: "HTMLBodyElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "body", + attributes: { + id: "nightly-whatsnew", + class: " html-ltr ", + }, + attributesLength: 2, + }, +}); + +stubs.set("NodeWithSpacesInClassName", { + type: "object", + actor: "server1.conn3.child1/obj59", + class: "HTMLBodyElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "body", + attributes: { + class: "a b c", + }, + attributesLength: 1, + }, +}); + +stubs.set("NodeWithoutAttributes", { + type: "object", + actor: "server1.conn1.child1/obj32", + class: "HTMLParagraphElement", + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "p", + attributes: {}, + attributesLength: 1, + }, +}); + +stubs.set("LotsOfAttributes", { + type: "object", + actor: "server1.conn2.child1/obj30", + class: "HTMLParagraphElement", + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "p", + attributes: { + id: "lots-of-attributes", + a: "", + b: "", + c: "", + d: "", + e: "", + f: "", + g: "", + h: "", + i: "", + j: "", + k: "", + l: "", + m: "", + n: "", + }, + attributesLength: 15, + }, +}); + +stubs.set("SvgNode", { + type: "object", + actor: "server1.conn1.child1/obj42", + class: "SVGClipPathElement", + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "clipPath", + attributes: { + id: "clip", + class: "svg-element", + }, + attributesLength: 0, + }, +}); + +stubs.set("SvgNodeInXHTML", { + type: "object", + actor: "server1.conn3.child1/obj34", + class: "SVGCircleElement", + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "svg:circle", + attributes: { + class: "svg-element", + cx: "0", + cy: "0", + r: "5", + }, + attributesLength: 3, + }, +}); + +stubs.set("NodeWithLongAttribute", { + type: "object", + actor: "server1.conn1.child1/obj32", + class: "HTMLParagraphElement", + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "p", + attributes: { + "data-test": "a".repeat(100), + }, + attributesLength: 1, + }, +}); + +const initialText = "a".repeat(1000); +stubs.set("NodeWithLongStringAttribute", { + type: "object", + actor: "server1.conn1.child1/obj28", + class: "HTMLDivElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "div", + isConnected: false, + attributes: { + "data-test": { + type: "longString", + initial: initialText, + length: 50000, + actor: "server1.conn1.child1/longString29", + }, + }, + attributesLength: 1, + }, +}); + +stubs.set("MarkerPseudoElement", { + type: "object", + actor: "server1.conn1.child1/obj26", + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "_moz_generated_content_marker", + attributes: {}, + attributesLength: 0, + isMarkerPseudoElement: true, + }, +}); + +stubs.set("BeforePseudoElement", { + type: "object", + actor: "server1.conn1.child1/obj27", + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "_moz_generated_content_before", + attributes: {}, + attributesLength: 0, + isBeforePseudoElement: true, + }, +}); + +stubs.set("AfterPseudoElement", { + type: "object", + actor: "server1.conn1.child1/obj28", + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "_moz_generated_content_after", + attributes: {}, + attributesLength: 0, + isAfterPseudoElement: true, + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/error.js b/devtools/client/shared/components/test/node/stubs/reps/error.js new file mode 100644 index 0000000000..ea4b7ac4ee --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/error.js @@ -0,0 +1,396 @@ +/* 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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("SimpleError", { + type: "object", + actor: "server1.conn1.child1/obj1020", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "Error", + message: "Error message", + stack: "@debugger eval code:1:13\n", + fileName: "debugger eval code", + lineNumber: 1, + columnNumber: 13, + }, +}); + +stubs.set("MultilineStackError", { + type: "object", + actor: "server1.conn1.child1/obj1021", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "Error", + message: "bar", + stack: + "errorBar@debugger eval code:6:15\n" + + "errorFoo@debugger eval code:3:3\n" + + "@debugger eval code:8:1\n", + fileName: "debugger eval code", + lineNumber: 6, + columnNumber: 15, + }, +}); + +stubs.set("ErrorWithoutStacktrace", { + type: "object", + actor: "server1.conn1.child1/obj1020", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "Error", + message: "Error message", + }, +}); + +stubs.set("EvalError", { + type: "object", + actor: "server1.conn1.child1/obj1022", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "EvalError", + message: "EvalError message", + stack: "@debugger eval code:10:13\n", + fileName: "debugger eval code", + lineNumber: 10, + columnNumber: 13, + }, +}); + +stubs.set("InternalError", { + type: "object", + actor: "server1.conn1.child1/obj1023", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "InternalError", + message: "InternalError message", + stack: "@debugger eval code:11:13\n", + fileName: "debugger eval code", + lineNumber: 11, + columnNumber: 13, + }, +}); + +stubs.set("RangeError", { + type: "object", + actor: "server1.conn1.child1/obj1024", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "RangeError", + message: "RangeError message", + stack: "@debugger eval code:12:13\n", + fileName: "debugger eval code", + lineNumber: 12, + columnNumber: 13, + }, +}); + +stubs.set("ReferenceError", { + type: "object", + actor: "server1.conn1.child1/obj1025", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "ReferenceError", + message: "ReferenceError message", + stack: "@debugger eval code:13:13\n", + fileName: "debugger eval code", + lineNumber: 13, + columnNumber: 13, + }, +}); + +stubs.set("SyntaxError", { + type: "object", + actor: "server1.conn1.child1/obj1026", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "SyntaxError", + message: "SyntaxError message", + stack: "@debugger eval code:14:13\n", + fileName: "debugger eval code", + lineNumber: 14, + columnNumber: 13, + }, +}); + +stubs.set("TypeError", { + type: "object", + actor: "server1.conn1.child1/obj1027", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "TypeError", + message: "TypeError message", + stack: "@debugger eval code:15:13\n", + fileName: "debugger eval code", + lineNumber: 15, + columnNumber: 13, + }, +}); + +stubs.set("URIError", { + type: "object", + actor: "server1.conn1.child1/obj1028", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "URIError", + message: "URIError message", + stack: "@debugger eval code:16:13\n", + fileName: "debugger eval code", + lineNumber: 16, + columnNumber: 13, + }, +}); + +/** + * Example code: + * try { + * var foo = document.querySelector("foo;()bar!"); + * } catch (ex) { + * ex; + * } + */ +stubs.set("DOMException", { + type: "object", + actor: "server2.conn2.child3/obj32", + class: "DOMException", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMException", + name: "SyntaxError", + message: "'foo;()bar!' is not a valid selector", + code: 12, + result: 2152923148, + filename: "debugger eval code", + lineNumber: 1, + columnNumber: 0, + }, +}); + +stubs.set("base-loader Error", { + type: "object", + actor: "server1.conn1.child1/obj1020", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "Error", + message: "Error message", + stack: + "onPacket@resource://devtools/shared/base-loader.sys.mjs -> resource://devtools/client/debugger-client.js:856:9\n" + + "send/<@resource://devtools/shared/base-loader.sys.mjs -> resource://devtools/shared/transport/transport.js:569:13\n" + + "exports.makeInfallible/<@resource://devtools/shared/base-loader.sys.mjs -> resource://devtools/shared/ThreadSafeDevToolsUtils.js:109:14\n" + + "exports.makeInfallible/<@resource://devtools/shared/base-loader.sys.mjs -> resource://devtools/shared/ThreadSafeDevToolsUtils.js:109:14\n", + fileName: "debugger-client.js", + lineNumber: 859, + columnNumber: 9, + }, +}); + +stubs.set("longString stack Error", { + type: "object", + actor: "server1.conn2.child1/obj33", + class: "Error", + isError: true, + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "Error", + message: "", + stack: { + type: "longString", + initial: + "NgForOf.prototype.ngOnChanges@webpack-internal:///./node_modules/@angular/common/esm5/common.js:2656:27\n checkAndUpdateDirectiveInline@webpack-internal:///./node_modules/@angular/core/esm5/core.js:12581:9\n checkAndUpdateNodeInline@webpack-internal:///./node_modules/@angular/core/esm5/core.js:14109:20\n checkAndUpdateNode@webpack-internal:///./node_modules/@angular/core/esm5/core.js:14052:16\n debugCheckAndUpdateNode@webpack-internal:///./node_modules/@angular/core/esm5/core.js:14945:55\n debugCheckDirectivesFn@webpack-internal:///./node_modules/@angular/core/esm5/core.js:14886:13\n View_MetaTableComponent_6/<@ng:///AppModule/MetaTableComponent.ngfactory.js:98:5\n debugUpdateDirectives@webpack-internal:///./node_modules/@angular/core/esm5/core.js:14871:12\n checkAndUpdateView@webpack-internal:///./node_modules/@angular/core/esm5/core.js:14018:5\n callViewAction@webpack-internal:///./node_modules/@angular/core/esm5/core.js:14369:21\n execEmbeddedViewsAction@webpack-internal:///./node_modules/@an", + length: 11907, + actor: "server1.conn2.child1/longString31", + }, + fileName: "debugger eval code", + lineNumber: 1, + columnNumber: 5, + }, +}); + +stubs.set("longString stack Error - cut-off location", { + type: "object", + actor: "server1.conn1.child1/obj33", + class: "Error", + isError: true, + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 6, + preview: { + kind: "Error", + name: "InternalError", + message: "too much recursion", + stack: { + type: "longString", + initial: + "execute/AppComponent</AppComponent.prototype.doStuff@https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:32:1\nexecute/AppComponent</AppComponent.prototype.doStuff@https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21\nexecute/AppComponent</AppComponent.prototype.doStuff@https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21\nexecute/AppComponent</AppComponent.prototype.doStuff@https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21\nexecute/AppComponent</AppComponent.prototype.doStuff@https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21\nexecute/AppComponent</AppComponent.prototype.doStuff@https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21\nexecute/AppComponent</AppComponent.prototype.doStuff@https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21\nexecute/AppComponent</AppComponent.prototype.doStuff@https://an", + length: 17151, + actor: "server1.conn1.child1/longString27", + }, + fileName: + "https://c.staticblitz.com/assets/engineblock-bc7b07e99ec5c6739c766b4898e4cff5acfddc137ccb7218377069c32731f1d0.js line 1 > eval", + lineNumber: 32, + columnNumber: 1, + }, +}); + +stubs.set("Error with V8-like stack", { + type: "object", + actor: "server1.conn1.child1/obj1020", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "Error", + message: "BOOM", + stack: "Error: BOOM\ngetAccount@http://moz.com/script.js:1:2", + fileName: "http://moz.com/script.js:1:2", + lineNumber: 1, + columnNumber: 2, + }, +}); + +stubs.set("Error with invalid stack", { + type: "object", + actor: "server1.conn1.child1/obj1020", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "Error", + message: "bad stack", + stack: "bar\nbaz\nfoo\n\n\n\n\n\n\n", + fileName: "http://moz.com/script.js:1:2", + lineNumber: 1, + columnNumber: 2, + }, +}); + +stubs.set("Error with undefined-grip stack", { + type: "object", + actor: "server0.conn0.child1/obj88", + class: "Error", + isError: true, + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "InternalError", + message: "too much recursion", + stack: { + type: "undefined", + }, + fileName: "debugger eval code", + lineNumber: 13, + columnNumber: 13, + }, +}); + +stubs.set("Error with undefined-grip name", { + type: "object", + actor: "server0.conn0.child1/obj88", + class: "Error", + isError: true, + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: { + type: "undefined", + }, + message: "too much recursion", + stack: "@debugger eval code:16:13\n", + fileName: "debugger eval code", + lineNumber: 13, + columnNumber: 13, + }, +}); + +stubs.set("Error with undefined-grip message", { + type: "object", + actor: "server0.conn0.child1/obj88", + class: "Error", + isError: true, + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "Error", + message: { type: "undefined" }, + stack: "@debugger eval code:16:13\n", + fileName: "debugger eval code", + lineNumber: 13, + columnNumber: 13, + }, +}); + +stubs.set("Error with stack having frames with multiple @", { + type: "object", + actor: "server1.conn1.child1/obj1021", + class: "Error", + isError: true, + ownPropertyLength: 4, + preview: { + kind: "Error", + name: "Error", + message: "bar", + stack: + "errorBar@https://example.com/turbo/from-npm.js@0.8.26/dist/from-npm.js:814:31\n" + + "errorFoo@https://example.com/turbo/from-npm.js@0.8.26/dist/from-npm.js:815:31\n" + + "@https://example.com/turbo/from-npm.js@0.8.26/dist/from-npm.js:816:31\n", + fileName: "from-npm.js", + lineNumber: 6, + columnNumber: 15, + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/event.js b/devtools/client/shared/components/test/node/stubs/reps/event.js new file mode 100644 index 0000000000..c0c49c5e42 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/event.js @@ -0,0 +1,269 @@ +/* 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/>. */ + +"use strict"; + +const stubs = new Map(); + +stubs.set("testEvent", { + type: "object", + class: "Event", + actor: "server1.conn23.obj35", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "DOMEvent", + type: "beforeprint", + properties: { + isTrusted: true, + currentTarget: { + type: "object", + class: "Window", + actor: "server1.conn23.obj37", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 760, + preview: { + kind: "ObjectWithURL", + url: "http://example.com", + }, + }, + eventPhase: 2, + bubbles: false, + cancelable: false, + defaultPrevented: false, + timeStamp: 1466780008434005, + originalTarget: { + type: "object", + class: "Window", + actor: "server1.conn23.obj38", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 760, + preview: { + kind: "ObjectWithURL", + url: "http://example.com", + }, + }, + explicitOriginalTarget: { + type: "object", + class: "Window", + actor: "server1.conn23.obj39", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 760, + preview: { + kind: "ObjectWithURL", + url: "http://example.com", + }, + }, + NONE: 0, + }, + target: { + type: "object", + class: "Window", + actor: "server1.conn23.obj36", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 760, + preview: { + kind: "ObjectWithURL", + url: "http://example.com", + }, + }, + }, +}); + +stubs.set("testMouseEvent", { + type: "object", + class: "MouseEvent", + actor: "server1.conn20.obj39", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "DOMEvent", + type: "click", + properties: { + buttons: 0, + clientX: 62, + clientY: 18, + layerX: 0, + layerY: 0, + }, + target: { + type: "object", + class: "HTMLDivElement", + actor: "server1.conn20.obj40", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "div", + isConnected: true, + attributes: { + id: "test", + }, + attributesLength: 1, + }, + }, + }, +}); + +stubs.set("testKeyboardEvent", { + type: "object", + class: "KeyboardEvent", + actor: "server1.conn21.obj49", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "DOMEvent", + type: "keyup", + properties: { + key: "Control", + charCode: 0, + keyCode: 17, + }, + target: { + type: "object", + class: "HTMLBodyElement", + actor: "server1.conn21.obj50", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "body", + attributes: {}, + attributesLength: 0, + }, + }, + eventKind: "key", + modifiers: [], + }, +}); + +stubs.set("testKeyboardEventWithModifiers", { + type: "object", + class: "KeyboardEvent", + actor: "server1.conn21.obj49", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "DOMEvent", + type: "keyup", + properties: { + key: "M", + charCode: 0, + keyCode: 77, + }, + target: { + type: "object", + class: "HTMLBodyElement", + actor: "server1.conn21.obj50", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "body", + attributes: {}, + attributesLength: 0, + }, + }, + eventKind: "key", + modifiers: ["Meta", "Shift"], + }, +}); + +stubs.set("testMessageEvent", { + type: "object", + class: "MessageEvent", + actor: "server1.conn3.obj34", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "DOMEvent", + type: "message", + properties: { + isTrusted: false, + data: "test data", + origin: "null", + lastEventId: "", + source: { + type: "object", + class: "Window", + actor: "server1.conn3.obj36", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 760, + preview: { + kind: "ObjectWithURL", + url: "", + }, + }, + ports: { + type: "object", + class: "Array", + actor: "server1.conn3.obj37", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + }, + currentTarget: { + type: "object", + class: "Window", + actor: "server1.conn3.obj38", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 760, + preview: { + kind: "ObjectWithURL", + url: "", + }, + }, + eventPhase: 2, + bubbles: false, + cancelable: false, + }, + target: { + type: "object", + class: "Window", + actor: "server1.conn3.obj35", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 760, + preview: { + kind: "ObjectWithURL", + url: "http://example.com", + }, + }, + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/failure.js b/devtools/client/shared/components/test/node/stubs/reps/failure.js new file mode 100644 index 0000000000..f839d5eab5 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/failure.js @@ -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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("Failure", { + type: "object", + class: "RegExp", + actor: "server1.conn22.obj39", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + get displayString() { + throw new Error("failure"); + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/function.js b/devtools/client/shared/components/test/node/stubs/reps/function.js new file mode 100644 index 0000000000..a0ba6ddf8d --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/function.js @@ -0,0 +1,227 @@ +/* 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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("Named", { + type: "object", + class: "Function", + actor: "server1.conn6.obj35", + extensible: true, + frozen: false, + sealed: false, + isAsync: false, + isGenerator: false, + name: "testName", + displayName: "testName", + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("UserNamed", { + type: "object", + class: "Function", + actor: "server1.conn6.obj35", + extensible: true, + frozen: false, + sealed: false, + isAsync: false, + isGenerator: false, + name: "testName", + userDisplayName: "testUserName", + displayName: "testName", + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("VarNamed", { + type: "object", + class: "Function", + actor: "server1.conn7.obj41", + extensible: true, + frozen: false, + sealed: false, + isAsync: false, + isGenerator: false, + displayName: "testVarName", + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("Anon", { + type: "object", + class: "Function", + actor: "server1.conn7.obj45", + extensible: true, + frozen: false, + sealed: false, + isAsync: false, + isGenerator: false, + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("LongName", { + type: "object", + class: "Function", + actor: "server1.conn7.obj67", + extensible: true, + frozen: false, + sealed: false, + isAsync: false, + isGenerator: false, + name: + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" + + "ooooooooooooooooooooooooooooooooooong", + displayName: + "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" + + "oooooooooooooooooooooooooooooooooooooooooong", + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("AsyncFunction", { + type: "object", + class: "Function", + actor: "server1.conn7.obj45", + extensible: true, + frozen: false, + sealed: false, + isAsync: true, + isGenerator: false, + name: "waitUntil2017", + displayName: "waitUntil2017", + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("AnonAsyncFunction", { + type: "object", + class: "Function", + actor: "server1.conn7.obj45", + extensible: true, + frozen: false, + sealed: false, + isAsync: true, + isGenerator: false, + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("GeneratorFunction", { + type: "object", + class: "Function", + actor: "server1.conn7.obj45", + extensible: true, + frozen: false, + sealed: false, + isAsync: false, + isGenerator: true, + name: "fib", + displayName: "fib", + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("AnonGeneratorFunction", { + type: "object", + class: "Function", + actor: "server1.conn7.obj45", + extensible: true, + frozen: false, + sealed: false, + isAsync: false, + isGenerator: true, + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("getRandom", { + type: "object", + actor: "server1.conn7.child1/obj984", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 3, + name: "getRandom", + displayName: "getRandom", + location: { + url: "https://nchevobbe.github.io/demo/console-test-app.html", + line: 314, + }, +}); + +stubs.set("EvaledInDebuggerFunction", { + type: "object", + actor: "server1.conn2.child1/obj29", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 3, + name: "evaledInDebugger", + displayName: "evaledInDebugger", + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("ObjectProperty", { + type: "object", + class: "Function", + actor: "server1.conn7.obj45", + extensible: true, + frozen: false, + sealed: false, + isAync: false, + isGenerator: false, + name: "$", + displayName: "jQuery", + location: { + url: "debugger eval code", + line: 1, + }, +}); + +stubs.set("EmptyClass", { + actor: "server0.conn0.child1/obj27", + class: "Function", + displayName: "EmptyClass", + extensible: true, + frozen: false, + isAsync: false, + isClassConstructor: true, + isGenerator: false, + location: { + url: "debugger eval code", + line: 1, + }, + name: "EmptyClass", + parameterNames: [], + sealed: false, + type: "object", +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/grip-array.js b/devtools/client/shared/components/test/node/stubs/reps/grip-array.js new file mode 100644 index 0000000000..93b07fa136 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/grip-array.js @@ -0,0 +1,1087 @@ +/* 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/>. */ + +"use strict"; + +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { + maxLengthMap, +} = require("resource://devtools/client/shared/components/reps/reps/grip-array.js"); +const stubs = new Map(); + +stubs.set("testBasic", { + type: "object", + class: "Array", + actor: "server1.conn0.obj35", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "ArrayLike", + length: 0, + items: [], + }, +}); + +stubs.set("DOMTokenList", { + type: "object", + actor: "server2.conn4.child12/obj39", + class: "DOMTokenList", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "ArrayLike", + length: 0, + items: [], + }, +}); + +stubs.set("testMaxProps", { + type: "object", + class: "Array", + actor: "server1.conn1.obj35", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 3, + items: [ + 1, + "foo", + { + type: "object", + class: "Object", + actor: "server1.conn1.obj36", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + }, + ], + }, +}); + +stubs.set("testMoreThanShortMaxProps", { + type: "object", + class: "Array", + actor: "server1.conn1.obj35", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: maxLengthMap.get(MODE.SHORT) + 1, + preview: { + kind: "ArrayLike", + length: maxLengthMap.get(MODE.SHORT) + 1, + items: new Array(maxLengthMap.get(MODE.SHORT) + 1).fill("test string"), + }, +}); + +stubs.set("testMoreThanLongMaxProps", { + type: "object", + class: "Array", + actor: "server1.conn1.obj35", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: maxLengthMap.get(MODE.LONG) + 1, + items: new Array(maxLengthMap.get(MODE.LONG) + 1).fill("test string"), + }, +}); + +stubs.set("testPreviewLimit", { + type: "object", + class: "Array", + actor: "server1.conn1.obj31", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 12, + preview: { + kind: "ArrayLike", + length: 11, + items: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + }, +}); + +stubs.set("testRecursiveArray", { + type: "object", + class: "Array", + actor: "server1.conn3.obj42", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + preview: { + kind: "ArrayLike", + length: 1, + items: [ + { + type: "object", + class: "Array", + actor: "server1.conn3.obj43", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + preview: { + kind: "ArrayLike", + length: 1, + }, + }, + ], + }, +}); + +stubs.set("testNamedNodeMap", { + type: "object", + class: "NamedNodeMap", + actor: "server1.conn3.obj42", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 6, + preview: { + kind: "ArrayLike", + length: 3, + items: [ + { + type: "object", + class: "Attr", + actor: "server1.conn3.obj43", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 2, + nodeName: "class", + value: "myclass", + }, + }, + { + type: "object", + class: "Attr", + actor: "server1.conn3.obj44", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 2, + nodeName: "cellpadding", + value: "7", + }, + }, + { + type: "object", + class: "Attr", + actor: "server1.conn3.obj44", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 2, + nodeName: "border", + value: "3", + }, + }, + ], + }, +}); + +stubs.set("testNodeList", { + type: "object", + actor: "server1.conn1.child1/obj51", + class: "NodeList", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 3, + preview: { + kind: "ArrayLike", + length: 3, + items: [ + { + type: "object", + actor: "server1.conn1.child1/obj52", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-1", + class: "btn btn-log", + type: "button", + }, + attributesLength: 3, + }, + }, + { + type: "object", + actor: "server1.conn1.child1/obj53", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-2", + class: "btn btn-err", + type: "button", + }, + attributesLength: 3, + }, + }, + { + type: "object", + actor: "server1.conn1.child1/obj54", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-3", + class: "btn btn-count", + type: "button", + }, + attributesLength: 3, + }, + }, + ], + }, +}); + +stubs.set("testDisconnectedNodeList", { + type: "object", + actor: "server1.conn1.child1/obj51", + class: "NodeList", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 3, + preview: { + kind: "ArrayLike", + length: 3, + items: [ + { + type: "object", + actor: "server1.conn1.child1/obj52", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: false, + attributes: { + id: "btn-1", + class: "btn btn-log", + type: "button", + }, + attributesLength: 3, + }, + }, + { + type: "object", + actor: "server1.conn1.child1/obj53", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: false, + attributes: { + id: "btn-2", + class: "btn btn-err", + type: "button", + }, + attributesLength: 3, + }, + }, + { + type: "object", + actor: "server1.conn1.child1/obj54", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: false, + attributes: { + id: "btn-3", + class: "btn btn-count", + type: "button", + }, + attributesLength: 3, + }, + }, + ], + }, +}); + +stubs.set("testDocumentFragment", { + type: "object", + actor: "server1.conn1.child1/obj45", + class: "DocumentFragment", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 11, + nodeName: "#document-fragment", + childNodesLength: 5, + childNodes: [ + { + type: "object", + actor: "server1.conn1.child1/obj46", + class: "HTMLLIElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "li", + attributes: { + id: "li-0", + class: "list-element", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server1.conn1.child1/obj47", + class: "HTMLLIElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "li", + attributes: { + id: "li-1", + class: "list-element", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server1.conn1.child1/obj48", + class: "HTMLLIElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "li", + attributes: { + id: "li-2", + class: "list-element", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server1.conn1.child1/obj49", + class: "HTMLLIElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "li", + attributes: { + id: "li-3", + class: "list-element", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server1.conn1.child1/obj50", + class: "HTMLLIElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "li", + attributes: { + id: "li-4", + class: "list-element", + }, + attributesLength: 2, + }, + }, + ], + }, +}); + +stubs.set("Array(5)", { + type: "object", + actor: "server1.conn4.child1/obj33", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "ArrayLike", + length: 5, + items: [null, null, null, null, null], + }, +}); + +stubs.set("[,1,2,3]", { + type: "object", + actor: "server1.conn4.child1/obj35", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 4, + items: [null, 1, 2, 3], + }, +}); + +stubs.set("[,,,3,4,5]", { + type: "object", + actor: "server1.conn4.child1/obj37", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 6, + items: [null, null, null, 3, 4, 5], + }, +}); + +stubs.set("[0,1,,3,4,5]", { + type: "object", + actor: "server1.conn4.child1/obj65", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 6, + preview: { + kind: "ArrayLike", + length: 6, + items: [0, 1, null, 3, 4, 5], + }, +}); + +stubs.set("[0,1,,,,5]", { + type: "object", + actor: "server1.conn4.child1/obj83", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 6, + items: [0, 1, null, null, null, 5], + }, +}); + +stubs.set("[0,,2,,4,5]", { + type: "object", + actor: "server1.conn4.child1/obj85", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 5, + preview: { + kind: "ArrayLike", + length: 6, + items: [0, null, 2, null, 4, 5], + }, +}); + +stubs.set("[0,,,3,,,,7,8]", { + type: "object", + actor: "server1.conn4.child1/obj87", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 5, + preview: { + kind: "ArrayLike", + length: 9, + items: [0, null, null, 3, null, null, null, 7, 8], + }, +}); + +stubs.set("[0,1,2,3,4,,]", { + type: "object", + actor: "server1.conn4.child1/obj89", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 6, + preview: { + kind: "ArrayLike", + length: 6, + items: [0, 1, 2, 3, 4, null], + }, +}); + +stubs.set("[0,1,2,,,,]", { + type: "object", + actor: "server1.conn13.child1/obj88", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 6, + items: [0, 1, 2, null, null, null], + }, +}); + +// We can have cases where we don't have the array items in the preview, +// (e.g. in the packet for `Promise.resolve([1, 2, 3])`), but we have the +// length of the array. +stubs.set("testItemsNotInPreview", { + type: "object", + actor: "server2.conn0.child1/obj135", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 3, + }, +}); + +stubs.set("new Set([1,2,3,4])", { + type: "object", + actor: "server2.conn8.child18/obj30", + class: "Set", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "ArrayLike", + length: 4, + items: [1, 2, 3, 4], + }, +}); + +stubs.set("new Set([0,1,2,…,19])", { + type: "object", + actor: "server2.conn8.child18/obj42", + class: "Set", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "ArrayLike", + length: 20, + items: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + }, +}); + +stubs.set("new WeakSet(document.querySelectorAll('button:nth-child(3n)'))", { + type: "object", + actor: "server2.conn11.child18/obj107", + class: "WeakSet", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "ArrayLike", + length: 4, + items: [ + { + type: "object", + actor: "server2.conn11.child18/obj108", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "g", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj109", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "E", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj110", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "l", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj111", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "r", + }, + attributesLength: 2, + }, + }, + ], + }, +}); + +stubs.set("new WeakSet(document.querySelectorAll('div, button'))", { + type: "object", + actor: "server2.conn11.child18/obj172", + class: "WeakSet", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "ArrayLike", + length: 12, + items: [ + { + type: "object", + actor: "server2.conn11.child18/obj173", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "L", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj174", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "E", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj175", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "t", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj176", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "G", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj177", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "g", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj178", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "e", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj179", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "T", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj180", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "l", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj181", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "C", + }, + attributesLength: 2, + }, + }, + { + type: "object", + actor: "server2.conn11.child18/obj182", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + type: "button", + "data-key": "c", + }, + attributesLength: 2, + }, + }, + ], + }, +}); + +stubs.set('["http://example.com/abcdefghijabcdefghij some other text"]', { + type: "object", + actor: "server2.conn3.child17/obj37", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + preview: { + kind: "ArrayLike", + length: 1, + items: ["http://example.com/abcdefghijabcdefghij some other text"], + }, +}); + +stubs.set("Array(234)", { + type: "object", + actor: "server4.conn2.child19/obj668", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 235, + preview: { + kind: "ArrayLike", + length: 234, + items: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + }, +}); + +stubs.set("Array(23456)", { + type: "object", + actor: "server4.conn2.child19/obj668", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 23457, + preview: { + kind: "ArrayLike", + length: 23456, + items: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + }, +}); + +stubs.set("TestArrayWithGetter", { + type: "object", + actor: "server0.conn0.windowGlobal13/obj21", + class: "Array", + ownPropertyLength: 2, + extensible: true, + frozen: false, + sealed: false, + isError: false, + preview: { + kind: "ArrayLike", + length: 1, + items: [{ + type: "accessor", + get: { + type: "object", + actor: "server0.conn0.windowGlobal13/obj22", + } + }] + } +}); + +stubs.set("TestArrayWithSetter", { + type: "object", + actor: "server0.conn0.windowGlobal13/obj24", + class: "Array", + ownPropertyLength: 2, + extensible: true, + frozen: false, + sealed: false, + isError: false, + preview: { + kind: "ArrayLike", + length: 1, + items: [{ + type: "accessor", + set: { + type: "object", + actor: "server0.conn0.windowGlobal13/obj25", + } + }] + } +}); + +stubs.set("TestArrayWithGetterAndSetter", { + type: "object", + actor: "server0.conn0.windowGlobal13/obj28", + class: "Array", + ownPropertyLength: 2, + extensible: true, + frozen: false, + sealed: false, + isError: false, + preview: { + kind: "ArrayLike", + length: 1, + items: [{ + type: "accessor", + get: { + type: "object", + actor: "server0.conn0.windowGlobal13/obj29", + }, + set: { + type: "object", + actor: "server0.conn0.windowGlobal13/obj30", + } + }] + } +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/grip-entry.js b/devtools/client/shared/components/test/node/stubs/reps/grip-entry.js new file mode 100644 index 0000000000..6c21389845 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/grip-entry.js @@ -0,0 +1,16 @@ +/* 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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("A → 0", { + type: "mapEntry", + preview: { + key: "A", + value: 0, + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/grip-map.js b/devtools/client/shared/components/test/node/stubs/reps/grip-map.js new file mode 100644 index 0000000000..8c2af0956f --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/grip-map.js @@ -0,0 +1,908 @@ +/* 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/>. */ + +"use strict"; + +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { + maxLengthMap, +} = require("resource://devtools/client/shared/components/reps/reps/grip-map.js"); + +const stubs = new Map(); + +stubs.set("testEmptyMap", { + type: "object", + actor: "server1.conn1.child1/obj97", + class: "Map", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 0, + entries: [], + }, +}); + +stubs.set("testSymbolKeyedMap", { + type: "object", + actor: "server1.conn1.child1/obj118", + class: "Map", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 2, + entries: [ + [ + { + type: "symbol", + name: "a", + }, + "value-a", + ], + [ + { + type: "symbol", + name: "b", + }, + "value-b", + ], + ], + }, +}); + +stubs.set("testWeakMap", { + type: "object", + actor: "server1.conn1.child1/obj115", + class: "WeakMap", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 1, + entries: [ + [ + { + type: "object", + actor: "server1.conn1.child1/obj116", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + }, + "value-a", + ], + ], + }, +}); + +stubs.set("testMaxEntries", { + type: "object", + actor: "server1.conn1.child1/obj109", + class: "Map", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 3, + entries: [ + ["key-a", "value-a"], + ["key-b", "value-b"], + ["key-c", "value-c"], + ], + }, +}); + +stubs.set("testMoreThanMaxEntries", { + type: "object", + class: "Map", + actor: "server1.conn0.obj332", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: maxLengthMap.get(MODE.LONG) + 1, + entries: Array.from({ length: 10 }).map((_, i) => { + return [`key-${i}`, `value-${i}`]; + }), + }, +}); + +stubs.set("testUninterestingEntries", { + type: "object", + actor: "server1.conn1.child1/obj111", + class: "Map", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 4, + entries: [ + [ + "key-a", + { + type: "null", + }, + ], + [ + "key-b", + { + type: "undefined", + }, + ], + ["key-c", "value-c"], + ["key-d", 4], + ], + }, +}); + +stubs.set("testDisconnectedNodeValuedMap", { + type: "object", + actor: "server1.conn1.child1/obj213", + class: "Map", + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 3, + entries: [ + [ + "item-0", + { + type: "object", + actor: "server1.conn1.child1/obj214", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: false, + attributes: { + id: "btn-1", + class: "btn btn-log", + type: "button", + }, + attributesLength: 3, + }, + }, + ], + [ + "item-1", + { + type: "object", + actor: "server1.conn1.child1/obj215", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: false, + attributes: { + id: "btn-2", + class: "btn btn-err", + type: "button", + }, + attributesLength: 3, + }, + }, + ], + [ + "item-2", + { + type: "object", + actor: "server1.conn1.child1/obj216", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: false, + attributes: { + id: "btn-3", + class: "btn btn-count", + type: "button", + }, + attributesLength: 3, + }, + }, + ], + ], + }, +}); + +stubs.set("testNodeValuedMap", { + type: "object", + actor: "server1.conn1.child1/obj213", + class: "Map", + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 3, + entries: [ + [ + "item-0", + { + type: "object", + actor: "server1.conn1.child1/obj214", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-1", + class: "btn btn-log", + type: "button", + }, + attributesLength: 3, + }, + }, + ], + [ + "item-1", + { + type: "object", + actor: "server1.conn1.child1/obj215", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-2", + class: "btn btn-err", + type: "button", + }, + attributesLength: 3, + }, + }, + ], + [ + "item-2", + { + type: "object", + actor: "server1.conn1.child1/obj216", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-3", + class: "btn btn-count", + type: "button", + }, + attributesLength: 3, + }, + }, + ], + ], + }, +}); + +stubs.set("testNodeKeyedMap", { + type: "object", + actor: "server1.conn1.child1/obj223", + class: "WeakMap", + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 3, + entries: [ + [ + { + type: "object", + actor: "server1.conn1.child1/obj224", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-1", + class: "btn btn-log", + type: "button", + }, + attributesLength: 3, + }, + }, + "item-0", + ], + [ + { + type: "object", + actor: "server1.conn1.child1/obj225", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-3", + class: "btn btn-count", + type: "button", + }, + attributesLength: 3, + }, + }, + "item-2", + ], + [ + { + type: "object", + actor: "server1.conn1.child1/obj226", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-2", + class: "btn btn-err", + type: "button", + }, + attributesLength: 3, + }, + }, + "item-1", + ], + ], + }, +}); + +stubs.set("20-entries Map", { + type: "object", + actor: "server4.conn2.child19/obj777", + class: "Map", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 20, + entries: [ + [ + { + type: "object", + actor: "server4.conn2.child19/obj778", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "1", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj779", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "2", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj780", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "3", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj781", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "4", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj782", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "5", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj783", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "6", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj784", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "7", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj785", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "8", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj786", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "9", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj787", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "10", + }, + ], + ], + }, +}); + +stubs.set("234-entries Map", { + type: "object", + actor: "server4.conn2.child19/obj789", + class: "Map", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 234, + entries: [ + [ + { + type: "object", + actor: "server4.conn2.child19/obj790", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "1", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj791", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "2", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj792", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "3", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj793", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "4", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj794", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "5", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj795", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "6", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj796", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "7", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj797", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "8", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj798", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "9", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj799", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "10", + }, + ], + ], + }, +}); + +stubs.set("23456-entries Map", { + type: "object", + actor: "server4.conn2.child19/obj803", + class: "Map", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "MapLike", + size: 23456, + entries: [ + [ + { + type: "object", + actor: "server4.conn2.child19/obj804", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "1", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj805", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "2", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj806", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "3", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj807", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "4", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj808", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "5", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj809", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "6", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj810", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "7", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj811", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "8", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj812", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "9", + }, + ], + [ + { + type: "object", + actor: "server4.conn2.child19/obj813", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + { + type: "symbol", + name: "10", + }, + ], + ], + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/grip.js b/devtools/client/shared/components/test/node/stubs/reps/grip.js new file mode 100644 index 0000000000..69a24013ef --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/grip.js @@ -0,0 +1,1057 @@ +/* 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/>. */ + +"use strict"; + +const { + MODE, +} = require("resource://devtools/client/shared/components/reps/reps/constants.js"); +const { + maxLengthMap, +} = require("resource://devtools/client/shared/components/reps/reps/grip.js"); + +const stubs = new Map(); + +stubs.set("testBasic", { + type: "object", + class: "Object", + actor: "server1.conn0.obj304", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: {}, + ownPropertiesLength: 0, + safeGetterValues: {}, + }, +}); + +stubs.set("testMaxProps", { + type: "object", + class: "Object", + actor: "server1.conn0.obj337", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 3, + preview: { + kind: "Object", + ownProperties: { + a: { + configurable: true, + enumerable: true, + writable: true, + value: "a", + }, + b: { + configurable: true, + enumerable: true, + writable: true, + value: "b", + }, + c: { + configurable: true, + enumerable: true, + writable: true, + value: "c", + }, + }, + ownPropertiesLength: 3, + safeGetterValues: {}, + }, +}); + +const longModeMaxLength = maxLengthMap.get(MODE.LONG); + +stubs.set("testMoreThanMaxProps", { + type: "object", + class: "Object", + actor: "server1.conn0.obj332", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: longModeMaxLength + 1, + preview: { + kind: "Object", + ownProperties: Array.from({ length: longModeMaxLength }).reduce( + (res, item, index) => ({ + ...res, + [`p${index}`]: { + configurable: true, + enumerable: true, + writable: true, + value: index.toString(), + }, + }), + {} + ), + ownPropertiesLength: longModeMaxLength + 1, + safeGetterValues: {}, + }, +}); + +stubs.set("testUninterestingProps", { + type: "object", + class: "Object", + actor: "server1.conn0.obj342", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "Object", + ownProperties: { + a: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "undefined", + }, + }, + b: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "undefined", + }, + }, + c: { + configurable: true, + enumerable: true, + writable: true, + value: "c", + }, + d: { + configurable: true, + enumerable: true, + writable: true, + value: 1, + }, + }, + ownPropertiesLength: 4, + safeGetterValues: {}, + }, +}); +stubs.set("testNonEnumerableProps", { + type: "object", + actor: "server1.conn1.child1/obj30", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "Object", + ownProperties: {}, + ownPropertiesLength: 1, + safeGetterValues: {}, + }, +}); +stubs.set("testNestedObject", { + type: "object", + class: "Object", + actor: "server1.conn0.obj145", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + preview: { + kind: "Object", + ownProperties: { + objProp: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "object", + class: "Object", + actor: "server1.conn0.obj146", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + }, + }, + strProp: { + configurable: true, + enumerable: true, + writable: true, + value: "test string", + }, + }, + ownPropertiesLength: 2, + safeGetterValues: {}, + }, +}); + +stubs.set("testNestedArray", { + type: "object", + class: "Object", + actor: "server1.conn0.obj326", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "Object", + ownProperties: { + arrProp: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "object", + class: "Array", + actor: "server1.conn0.obj327", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 3, + }, + }, + }, + }, + ownPropertiesLength: 1, + safeGetterValues: {}, + }, +}); + +stubs.set("testMoreProp", { + type: "object", + class: "Object", + actor: "server1.conn0.obj342", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "Object", + ownProperties: { + a: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "undefined", + }, + }, + b: { + configurable: true, + enumerable: true, + writable: true, + value: 1, + }, + more: { + configurable: true, + enumerable: true, + writable: true, + value: 2, + }, + d: { + configurable: true, + enumerable: true, + writable: true, + value: 3, + }, + }, + ownPropertiesLength: 4, + safeGetterValues: {}, + }, +}); +stubs.set("testBooleanObject", { + type: "object", + actor: "server1.conn1.child1/obj57", + class: "Boolean", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: {}, + ownPropertiesLength: 0, + safeGetterValues: {}, + wrappedValue: true, + }, +}); +stubs.set("testNumberObject", { + type: "object", + actor: "server1.conn1.child1/obj59", + class: "Number", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: {}, + ownPropertiesLength: 0, + safeGetterValues: {}, + wrappedValue: 42, + }, +}); +stubs.set("testStringObject", { + type: "object", + actor: "server1.conn1.child1/obj61", + class: "String", + ownPropertyLength: 4, + preview: { + kind: "Object", + ownProperties: {}, + ownPropertiesLength: 4, + safeGetterValues: {}, + wrappedValue: "foo", + }, +}); +stubs.set("testProxy", { + type: "object", + actor: "server1.conn1.child1/obj47", + class: "Proxy", + preview: { + kind: "Object", + ownProperties: { + "<target>": { + value: { + type: "object", + actor: "server1.conn1.child1/obj48", + class: "Object", + ownPropertyLength: 1, + }, + }, + "<handler>": { + value: { + type: "object", + actor: "server1.conn1.child1/obj49", + class: "Array", + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 3, + }, + }, + }, + }, + ownPropertiesLength: 2, + }, +}); +stubs.set("testProxySlots", { + proxyTarget: { + type: "object", + actor: "server1.conn1.child1/obj48", + class: "Object", + ownPropertyLength: 1, + }, + proxyHandler: { + type: "object", + actor: "server1.conn1.child1/obj49", + class: "Array", + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 3, + }, + }, +}); +stubs.set("testArrayBuffer", { + type: "object", + actor: "server1.conn1.child1/obj170", + class: "ArrayBuffer", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: {}, + ownPropertiesLength: 0, + safeGetterValues: { + byteLength: { + getterValue: 10, + getterPrototypeLevel: 1, + enumerable: false, + writable: true, + }, + }, + }, +}); +stubs.set("testSharedArrayBuffer", { + type: "object", + actor: "server1.conn1.child1/obj171", + class: "SharedArrayBuffer", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: {}, + ownPropertiesLength: 0, + safeGetterValues: { + byteLength: { + getterValue: 5, + getterPrototypeLevel: 1, + enumerable: false, + writable: true, + }, + }, + }, +}); +stubs.set("testApplicationCache", { + type: "object", + actor: "server2.conn1.child2/obj45", + class: "OfflineResourceList", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: {}, + ownPropertiesLength: 0, + safeGetterValues: { + status: { + getterValue: 0, + getterPrototypeLevel: 1, + enumerable: true, + writable: true, + }, + onchecking: { + getterValue: { + type: "null", + }, + getterPrototypeLevel: 1, + enumerable: true, + writable: true, + }, + onerror: { + getterValue: { + type: "null", + }, + getterPrototypeLevel: 1, + enumerable: true, + writable: true, + }, + onnoupdate: { + getterValue: { + type: "null", + }, + getterPrototypeLevel: 1, + enumerable: true, + writable: true, + }, + ondownloading: { + getterValue: { + type: "null", + }, + getterPrototypeLevel: 1, + enumerable: true, + writable: true, + }, + onprogress: { + getterValue: { + type: "null", + }, + getterPrototypeLevel: 1, + enumerable: true, + writable: true, + }, + onupdateready: { + getterValue: { + type: "null", + }, + getterPrototypeLevel: 1, + enumerable: true, + writable: true, + }, + oncached: { + getterValue: { + type: "null", + }, + getterPrototypeLevel: 1, + enumerable: true, + writable: true, + }, + onobsolete: { + getterValue: { + type: "null", + }, + getterPrototypeLevel: 1, + enumerable: true, + writable: true, + }, + mozItems: { + getterValue: { + type: "object", + actor: "server2.conn1.child2/obj46", + class: "DOMStringList", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "ArrayLike", + length: 0, + }, + }, + getterPrototypeLevel: 1, + enumerable: true, + writable: true, + }, + }, + }, +}); +stubs.set("testObjectWithNodes", { + type: "object", + actor: "server1.conn1.child1/obj214", + class: "Object", + ownPropertyLength: 2, + preview: { + kind: "Object", + ownProperties: { + foo: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "object", + actor: "server1.conn1.child1/obj215", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-1", + class: "btn btn-log", + type: "button", + }, + attributesLength: 3, + }, + }, + }, + bar: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "object", + actor: "server1.conn1.child1/obj216", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-2", + class: "btn btn-err", + type: "button", + }, + attributesLength: 3, + }, + }, + }, + }, + ownPropertiesLength: 2, + safeGetterValues: {}, + }, +}); +stubs.set("testObjectWithDisconnectedNodes", { + type: "object", + actor: "server1.conn1.child1/obj214", + class: "Object", + ownPropertyLength: 2, + preview: { + kind: "Object", + ownProperties: { + foo: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "object", + actor: "server1.conn1.child1/obj215", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + attributes: { + id: "btn-1", + class: "btn btn-log", + type: "button", + }, + attributesLength: 3, + }, + }, + }, + bar: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "object", + actor: "server1.conn1.child1/obj216", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + attributes: { + id: "btn-2", + class: "btn btn-err", + type: "button", + }, + attributesLength: 3, + }, + }, + }, + }, + ownPropertiesLength: 2, + safeGetterValues: {}, + }, +}); + +// Packet for `({get x(){}})` +stubs.set("TestObjectWithGetter", { + type: "object", + actor: "server2.conn1.child1/obj105", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "Object", + ownProperties: { + x: { + configurable: true, + enumerable: true, + get: { + type: "object", + actor: "server2.conn1.child1/obj106", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + name: "get x", + displayName: "get x", + location: { + url: "debugger eval code", + line: 1, + }, + }, + set: { + type: "undefined", + }, + }, + }, + ownPropertiesLength: 1, + safeGetterValues: {}, + }, +}); + +// Packet for `({set x(s){}})` +stubs.set("TestObjectWithSetter", { + type: "object", + actor: "server2.conn1.child1/obj115", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "Object", + ownProperties: { + x: { + configurable: true, + enumerable: true, + get: { + type: "undefined", + }, + set: { + type: "object", + actor: "server2.conn1.child1/obj116", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + name: "set x", + displayName: "set x", + location: { + url: "debugger eval code", + line: 1, + }, + }, + }, + }, + ownPropertiesLength: 1, + safeGetterValues: {}, + }, +}); + +// Packet for `({get x(){}, set x(s){}})` +stubs.set("TestObjectWithGetterAndSetter", { + type: "object", + actor: "server2.conn1.child1/obj126", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "Object", + ownProperties: { + x: { + configurable: true, + enumerable: true, + get: { + type: "object", + actor: "server2.conn1.child1/obj127", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + name: "get x", + displayName: "get x", + location: { + url: "debugger eval code", + line: 1, + }, + }, + set: { + type: "object", + actor: "server2.conn1.child1/obj128", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + name: "set x", + displayName: "set x", + location: { + url: "debugger eval code", + line: 1, + }, + }, + }, + }, + ownPropertiesLength: 1, + safeGetterValues: {}, + }, +}); + +// Packet for : +// ({ +// [Symbol()]: "first unnamed symbol", +// [Symbol()]: "second unnamed symbol", +// [Symbol("named")] : "named symbol", +// [Symbol.iterator] : function* () {yield 1;yield 2;}, +// x: 10, +// }) +stubs.set("TestObjectWithSymbolProperties", { + type: "object", + actor: "server2.conn1.child1/obj30", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "Object", + ownProperties: { + x: { + configurable: true, + enumerable: true, + writable: true, + value: 10, + }, + }, + ownSymbols: [ + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "first unnamed symbol", + }, + type: "symbol", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "second unnamed symbol", + }, + type: "symbol", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "named symbol", + }, + type: "symbol", + name: "named", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "object", + actor: "server2.conn1.child1/obj31", + class: "Function", + extensible: true, + frozen: false, + sealed: false, + location: { + url: "debugger eval code", + line: 1, + }, + }, + }, + type: "symbol", + name: "Symbol.iterator", + }, + ], + ownPropertiesLength: 1, + ownSymbolsLength: 4, + safeGetterValues: {}, + }, +}); + +// Packet for : +// x = {}; +// for(let i = 0; i < 11; i++) { +// x[Symbol(`i-${i}`)] = `value-${i}` +// } +// x; +stubs.set("TestObjectWithMoreThanMaxSymbolProperties", { + type: "object", + actor: "server2.conn1.child1/obj39", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: {}, + ownSymbols: [ + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "value-0", + }, + type: "symbol", + name: "i-0", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "value-1", + }, + type: "symbol", + name: "i-1", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "value-2", + }, + type: "symbol", + name: "i-2", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "value-3", + }, + type: "symbol", + name: "i-3", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "value-4", + }, + type: "symbol", + name: "i-4", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "value-5", + }, + type: "symbol", + name: "i-5", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "value-6", + }, + type: "symbol", + name: "i-6", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "value-7", + }, + type: "symbol", + name: "i-7", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "value-8", + }, + type: "symbol", + name: "i-8", + }, + { + descriptor: { + configurable: true, + enumerable: true, + writable: true, + value: "value-9", + }, + type: "symbol", + name: "i-9", + }, + ], + ownPropertiesLength: 0, + ownSymbolsLength: 11, + }, +}); + +stubs.set('{test: "http://example.com/ some other text"}', { + type: "object", + actor: "server2.conn4.child17/obj30", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "Object", + ownProperties: { + test: { + configurable: true, + enumerable: true, + writable: true, + value: "http://example.com/ some other text", + }, + }, + ownSymbols: [], + ownPropertiesLength: 1, + ownSymbolsLength: 0, + safeGetterValues: {}, + }, +}); + +stubs.set("Generator", { + type: "object", + actor: "server1.conn2.child1/obj33", + class: "Generator", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: {}, + ownSymbols: [], + ownPropertiesLength: 0, + ownSymbolsLength: 0, + safeGetterValues: {}, + }, +}); + +stubs.set("DeadObject", { + type: "object", + actor: "server1.conn7.child2/obj41", + class: "DeadObject", + extensible: true, + frozen: false, + sealed: false, +}); + +// Packet for : +// var obj = Object.create(null); obj.__proto__ = []; obj; +stubs.set("ObjectWith__proto__Property", { + type: "object", + actor: "server1.conn1.child1/obj31", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "Object", + ownProperties: { + ["__proto__"]: { + configurable: true, + enumerable: true, + writable: true, + value: { + type: "object", + actor: "server1.conn1.child1/obj32", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + preview: { + kind: "ArrayLike", + length: 0, + }, + }, + }, + }, + ownSymbols: [], + ownPropertiesLength: 1, + ownSymbolsLength: 0, + safeGetterValues: {}, + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/infinity.js b/devtools/client/shared/components/test/node/stubs/reps/infinity.js new file mode 100644 index 0000000000..9948f218ef --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/infinity.js @@ -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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`Infinity`, { + "type": "Infinity" +}); + +stubs.set(`NegativeInfinity`, { + "type": "-Infinity" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/long-string.js b/devtools/client/shared/components/test/node/stubs/reps/long-string.js new file mode 100644 index 0000000000..26715c4017 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/long-string.js @@ -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/>. */ + +"use strict"; + +const multilineFullText = `a\n${Array(20000) + .fill("a") + .join("")}`; +const fullTextLength = multilineFullText.length; +const initialText = multilineFullText.substring(0, 10000); + +const stubs = new Map(); + +stubs.set("testMultiline", { + type: "longString", + initial: initialText, + length: fullTextLength, + actor: "server1.conn1.child1/longString58", +}); + +stubs.set("testUnloadedFullText", { + type: "longString", + initial: Array(10000) + .fill("a") + .join(""), + length: 20000, + actor: "server1.conn1.child1/longString58", +}); + +stubs.set("testLoadedFullText", { + type: "longString", + fullText: multilineFullText, + initial: initialText, + length: fullTextLength, + actor: "server1.conn1.child1/longString58", +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/nan.js b/devtools/client/shared/components/test/node/stubs/reps/nan.js new file mode 100644 index 0000000000..51b67dd128 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/nan.js @@ -0,0 +1,15 @@ +/* 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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`NaN`, { + "type": "NaN" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/null.js b/devtools/client/shared/components/test/node/stubs/reps/null.js new file mode 100644 index 0000000000..5f7ccf5f0c --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/null.js @@ -0,0 +1,15 @@ +/* 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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`Null`, { + "type": "null" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/number.js b/devtools/client/shared/components/test/node/stubs/reps/number.js new file mode 100644 index 0000000000..217a3fa0da --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/number.js @@ -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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`Int`, 5); + +stubs.set(`True`, true); + +stubs.set(`False`, false); + +stubs.set(`NegZeroGrip`, { + "type": "-0" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/object-with-text.js b/devtools/client/shared/components/test/node/stubs/reps/object-with-text.js new file mode 100644 index 0000000000..ea17d01d7e --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/object-with-text.js @@ -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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("ShadowRule", { + type: "object", + class: "CSSStyleRule", + actor: "server1.conn3.obj273", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "ObjectWithText", + text: ".Shadow", + }, +}); + +stubs.set("CSSMediaRule", { + type: "object", + actor: "server2.conn8.child17/obj30", + class: "CSSMediaRule", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "ObjectWithText", + text: "(min-height: 680px), screen and (orientation: portrait)", + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/object-with-url.js b/devtools/client/shared/components/test/node/stubs/reps/object-with-url.js new file mode 100644 index 0000000000..179faf0874 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/object-with-url.js @@ -0,0 +1,22 @@ +/* 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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("ObjectWithUrl", { + type: "object", + class: "Location", + actor: "server1.conn2.obj272", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 15, + preview: { + kind: "ObjectWithURL", + url: "https://www.mozilla.org/en-US/", + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/promise.js b/devtools/client/shared/components/test/node/stubs/reps/promise.js new file mode 100644 index 0000000000..6dc71cd230 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/promise.js @@ -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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("Pending", { + type: "object", + actor: "server1.conn1.child1/obj54", + class: "Promise", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: { + "<state>": { + value: "pending", + }, + }, + ownPropertiesLength: 1, + }, +}); + +stubs.set("FulfilledWithNumber", { + type: "object", + actor: "server1.conn1.child1/obj55", + class: "Promise", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: { + "<state>": { + value: "fulfilled", + }, + "<value>": { + value: 42, + }, + }, + ownPropertiesLength: 2, + }, +}); + +stubs.set("FulfilledWithString", { + type: "object", + actor: "server1.conn1.child1/obj56", + class: "Promise", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: { + "<state>": { + value: "fulfilled", + }, + "<value>": { + value: "foo", + }, + }, + ownPropertiesLength: 2, + }, +}); + +stubs.set("FulfilledWithObject", { + type: "object", + actor: "server1.conn1.child1/obj59", + class: "Promise", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: { + "<state>": { + value: "fulfilled", + }, + "<value>": { + value: { + type: "object", + actor: "server1.conn1.child1/obj60", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 2, + }, + }, + }, + ownPropertiesLength: 2, + }, +}); + +stubs.set("FulfilledWithArray", { + type: "object", + actor: "server1.conn1.child1/obj57", + class: "Promise", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: { + "<state>": { + value: "fulfilled", + }, + "<value>": { + value: { + type: "object", + actor: "server1.conn1.child1/obj58", + class: "Array", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 4, + preview: { + kind: "ArrayLike", + length: 3, + }, + }, + }, + }, + ownPropertiesLength: 2, + }, +}); + +stubs.set("FulfilledWithNode", { + type: "object", + actor: "server1.conn1.child1/obj217", + class: "Promise", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: { + "<state>": { + value: "fulfilled", + }, + "<value>": { + value: { + type: "object", + actor: "server1.conn1.child1/obj218", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: true, + attributes: { + id: "btn-1", + class: "btn btn-log", + type: "button", + }, + attributesLength: 3, + }, + }, + }, + }, + ownPropertiesLength: 2, + }, +}); + +stubs.set("FulfilledWithDisconnectedNode", { + type: "object", + actor: "server1.conn1.child1/obj217", + class: "Promise", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: { + "<state>": { + value: "fulfilled", + }, + "<value>": { + value: { + type: "object", + actor: "server1.conn1.child1/obj218", + class: "HTMLButtonElement", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 0, + preview: { + kind: "DOMNode", + nodeType: 1, + nodeName: "button", + isConnected: false, + attributes: { + id: "btn-1", + class: "btn btn-log", + type: "button", + }, + attributesLength: 3, + }, + }, + }, + }, + ownPropertiesLength: 2, + }, +}); + +stubs.set("RejectedWithNumber", { + type: "object", + actor: "server0.conn0.child3/obj27", + class: "Promise", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: { + "<state>": { + value: "rejected", + }, + "<reason>": { + value: 123, + }, + }, + ownPropertiesLength: 2, + }, +}); + +stubs.set("RejectedWithObject", { + type: "object", + actor: "server0.conn0.child3/obj67", + class: "Promise", + ownPropertyLength: 0, + preview: { + kind: "Object", + ownProperties: { + "<state>": { + value: "rejected", + }, + "<reason>": { + value: { + type: "object", + actor: "server1.conn1.child1/obj68", + class: "Object", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + }, + }, + }, + ownPropertiesLength: 2, + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/regexp.js b/devtools/client/shared/components/test/node/stubs/reps/regexp.js new file mode 100644 index 0000000000..0dd5b06e97 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/regexp.js @@ -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/>. */ + +"use strict"; + +const stubs = new Map(); +stubs.set("RegExp", { + type: "object", + class: "RegExp", + actor: "server1.conn22.obj39", + extensible: true, + frozen: false, + sealed: false, + ownPropertyLength: 1, + displayString: "/ab+c/i", +}); + +stubs.set("longString displayString RegExp", { + type: "object", + actor: "server0.conn0.child2/obj79", + class: "RegExp", + ownPropertyLength: 1, + extensible: true, + frozen: false, + sealed: false, + displayString: { + type: "longString", + actor: "server0.conn0.child2/longstractor78", + length: 30002, + initial: + "/ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ", + }, +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/stubs.toml b/devtools/client/shared/components/test/node/stubs/reps/stubs.toml new file mode 100644 index 0000000000..9cf1570cbe --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/stubs.toml @@ -0,0 +1,20 @@ +[DEFAULT] +tags = "devtools" +subsuite = "devtools" +support-files = [ + "attribute.js", + "comment-node.js", + "date-time.js", + "infinity.js", + "nan.js", + "null.js", + "number.js", + "stylesheet.js", + "symbol.js", + "text-node.js", + "undefined.js", + "window.js", +] + +["browser_dummy.js"] +skip-if = ["true"] #This is only here so we can expose the support files in other toml files. diff --git a/devtools/client/shared/components/test/node/stubs/reps/stylesheet.js b/devtools/client/shared/components/test/node/stubs/reps/stylesheet.js new file mode 100644 index 0000000000..eeca646dd2 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/stylesheet.js @@ -0,0 +1,29 @@ +/* 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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`StyleSheet`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal4294967299/obj40", + "class": "CSSStyleSheet", + "ownPropertyLength": 0, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "ObjectWithURL", + "url": "https://example.com/styles.css" + } + }, + "actorID": "server0.conn0.windowGlobal4294967299/obj40" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/symbol.js b/devtools/client/shared/components/test/node/stubs/reps/symbol.js new file mode 100644 index 0000000000..d7f0ace128 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/symbol.js @@ -0,0 +1,33 @@ +/* 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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`Symbol`, { + "type": "symbol", + "actor": "server0.conn0.windowGlobal4294967299/symbol40", + "name": "foo" +}); + +stubs.set(`SymbolWithoutIdentifier`, { + "type": "symbol", + "actor": "server0.conn0.windowGlobal4294967299/symbol42" +}); + +stubs.set(`SymbolWithLongString`, { + "type": "symbol", + "actor": "server0.conn0.windowGlobal4294967299/symbol44", + "name": { + "type": "longString", + "actor": "server0.conn0.windowGlobal4294967299/longstractor45", + "length": 20000, + "initial": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/text-node.js b/devtools/client/shared/components/test/node/stubs/reps/text-node.js new file mode 100644 index 0000000000..eaf893cdbb --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/text-node.js @@ -0,0 +1,141 @@ +/* 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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`testRendering`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal2147483651/obj40", + "class": "Text", + "ownPropertyLength": 0, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "DOMNode", + "nodeType": 3, + "nodeName": "#text", + "isConnected": true, + "textContent": "hello world" + }, + "contentDomReference": { + "browsingContextId": 51, + "id": 0.5296372099388534 + } + }, + "actorID": "server0.conn0.windowGlobal2147483651/obj40" +}); + +stubs.set(`testRenderingDisconnected`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal2147483651/obj42", + "class": "Text", + "ownPropertyLength": 0, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "DOMNode", + "nodeType": 3, + "nodeName": "#text", + "isConnected": false, + "textContent": "hello world" + }, + "contentDomReference": { + "browsingContextId": 51, + "id": 0.6969799823627325 + } + }, + "actorID": "server0.conn0.windowGlobal2147483651/obj42" +}); + +stubs.set(`testRenderingWithEOL`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal2147483651/obj44", + "class": "Text", + "ownPropertyLength": 0, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "DOMNode", + "nodeType": 3, + "nodeName": "#text", + "isConnected": false, + "textContent": "hello\nworld" + }, + "contentDomReference": { + "browsingContextId": 51, + "id": 0.7640922670176581 + } + }, + "actorID": "server0.conn0.windowGlobal2147483651/obj44" +}); + +stubs.set(`testRenderingWithDoubleQuote`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal2147483651/obj46", + "class": "Text", + "ownPropertyLength": 0, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "DOMNode", + "nodeType": 3, + "nodeName": "#text", + "isConnected": false, + "textContent": "hello\"world" + }, + "contentDomReference": { + "browsingContextId": 51, + "id": 0.113013948491126 + } + }, + "actorID": "server0.conn0.windowGlobal2147483651/obj46" +}); + +stubs.set(`testRenderingWithLongString`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal2147483651/obj48", + "class": "Text", + "ownPropertyLength": 0, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "DOMNode", + "nodeType": 3, + "nodeName": "#text", + "isConnected": false, + "textContent": { + "type": "longString", + "actor": "server0.conn0.windowGlobal2147483651/longstractor49", + "length": 20002, + "initial": "a\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + }, + "contentDomReference": { + "browsingContextId": 51, + "id": 0.792316936882363 + } + }, + "actorID": "server0.conn0.windowGlobal2147483651/obj48" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/undefined.js b/devtools/client/shared/components/test/node/stubs/reps/undefined.js new file mode 100644 index 0000000000..7acd7be3cf --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/undefined.js @@ -0,0 +1,15 @@ +/* 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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`Undefined`, { + "type": "undefined" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/window.js b/devtools/client/shared/components/test/node/stubs/reps/window.js new file mode 100644 index 0000000000..6386e91054 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/window.js @@ -0,0 +1,65 @@ +/* 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/>. */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +stubs.set(`Window`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal2147483651/obj35", + "class": "Window", + "ownPropertyLength": 806, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "ObjectWithURL", + "url": "data:text/html;charset=utf-8,stub generation" + } + }, + "actorID": "server0.conn0.windowGlobal2147483651/obj35" +}); + +stubs.set(`CrossOriginIframeContentWindow`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal10737418241/obj59", + "class": "Window", + "ownPropertyLength": 14, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "ObjectWithURL", + "url": "http://example.org/document-builder.sjs?html=example.org" + } + }, + "actorID": "server0.conn0.windowGlobal10737418241/obj59" +}); + +stubs.set(`CrossOriginIframeTopWindow`, { + "_grip": { + "type": "object", + "actor": "server0.conn0.windowGlobal12884901889/obj20", + "class": "Window", + "ownPropertyLength": 16, + "extensible": true, + "frozen": false, + "sealed": false, + "isError": false, + "preview": { + "kind": "ObjectWithURL", + "url": "Restricted" + } + }, + "actorID": "server0.conn0.windowGlobal12884901889/obj20" +}); + +module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/yarn.lock b/devtools/client/shared/components/test/node/yarn.lock new file mode 100644 index 0000000000..de7f467a56 --- /dev/null +++ b/devtools/client/shared/components/test/node/yarn.lock @@ -0,0 +1,4209 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/core@^7.1.0": + version "7.11.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.6.tgz#3a9455dc7387ff1bac45770650bc13ba04a15651" + integrity sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.11.6" + "@babel/helper-module-transforms" "^7.11.0" + "@babel/helpers" "^7.10.4" + "@babel/parser" "^7.11.5" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.11.5" + "@babel/types" "^7.11.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.11.5", "@babel/generator@^7.11.6", "@babel/generator@^7.4.0": + version "7.11.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620" + integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA== + dependencies: + "@babel/types" "^7.11.5" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-create-class-features-plugin@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" + integrity sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-member-expression-to-functions" "^7.10.5" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" + integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q== + dependencies: + "@babel/types" "^7.11.0" + +"@babel/helper-module-imports@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" + integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-module-transforms@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" + integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/template" "^7.10.4" + "@babel/types" "^7.11.0" + lodash "^4.17.19" + +"@babel/helper-optimise-call-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" + integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + +"@babel/helper-replace-supers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" + integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-simple-access@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" + integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== + dependencies: + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-skip-transparent-expression-wrappers@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz#eec162f112c2f58d3af0af125e3bb57665146729" + integrity sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q== + dependencies: + "@babel/types" "^7.11.0" + +"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== + dependencies: + "@babel/types" "^7.11.0" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/helpers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" + integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== + dependencies: + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5", "@babel/parser@^7.4.3": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" + integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== + +"@babel/plugin-proposal-class-properties@7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" + integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" + integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.8.3": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz#de5866d0646f6afdaab8a566382fe3a221755076" + integrity sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-object-rest-spread@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/template@^7.10.4", "@babel/template@^7.4.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5", "@babel/traverse@^7.4.3": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" + integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.11.5" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/parser" "^7.11.5" + "@babel/types" "^7.11.5" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.3.0", "@babel/types@^7.4.0": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d" + integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@jest/console@^24.7.1", "@jest/console@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" + integrity sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ== + dependencies: + "@jest/source-map" "^24.9.0" + chalk "^2.0.1" + slash "^2.0.0" + +"@jest/core@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.9.0.tgz#2ceccd0b93181f9c4850e74f2a9ad43d351369c4" + integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A== + dependencies: + "@jest/console" "^24.7.1" + "@jest/reporters" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-changed-files "^24.9.0" + jest-config "^24.9.0" + jest-haste-map "^24.9.0" + jest-message-util "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-resolve-dependencies "^24.9.0" + jest-runner "^24.9.0" + jest-runtime "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + jest-watcher "^24.9.0" + micromatch "^3.1.10" + p-each-series "^1.0.0" + realpath-native "^1.1.0" + rimraf "^2.5.4" + slash "^2.0.0" + strip-ansi "^5.0.0" + +"@jest/environment@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.9.0.tgz#21e3afa2d65c0586cbd6cbefe208bafade44ab18" + integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ== + dependencies: + "@jest/fake-timers" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + +"@jest/fake-timers@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" + integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A== + dependencies: + "@jest/types" "^24.9.0" + jest-message-util "^24.9.0" + jest-mock "^24.9.0" + +"@jest/reporters@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.9.0.tgz#86660eff8e2b9661d042a8e98a028b8d631a5b43" + integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.2" + istanbul-lib-coverage "^2.0.2" + istanbul-lib-instrument "^3.0.1" + istanbul-lib-report "^2.0.4" + istanbul-lib-source-maps "^3.0.1" + istanbul-reports "^2.2.6" + jest-haste-map "^24.9.0" + jest-resolve "^24.9.0" + jest-runtime "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.6.0" + node-notifier "^5.4.2" + slash "^2.0.0" + source-map "^0.6.0" + string-length "^2.0.0" + +"@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" + integrity sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.1.15" + source-map "^0.6.0" + +"@jest/test-result@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.9.0.tgz#11796e8aa9dbf88ea025757b3152595ad06ba0ca" + integrity sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA== + dependencies: + "@jest/console" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/istanbul-lib-coverage" "^2.0.0" + +"@jest/test-sequencer@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31" + integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A== + dependencies: + "@jest/test-result" "^24.9.0" + jest-haste-map "^24.9.0" + jest-runner "^24.9.0" + jest-runtime "^24.9.0" + +"@jest/transform@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.9.0.tgz#4ae2768b296553fadab09e9ec119543c90b16c56" + integrity sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^24.9.0" + babel-plugin-istanbul "^5.1.0" + chalk "^2.0.1" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.1.15" + jest-haste-map "^24.9.0" + jest-regex-util "^24.9.0" + jest-util "^24.9.0" + micromatch "^3.1.10" + pirates "^4.0.1" + realpath-native "^1.1.0" + slash "^2.0.0" + source-map "^0.6.1" + write-file-atomic "2.4.1" + +"@jest/types@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" + integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^13.0.0" + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + +"@types/babel__core@^7.1.0": + version "7.1.10" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.10.tgz#ca58fc195dd9734e77e57c6f2df565623636ab40" + integrity sha512-x8OM8XzITIMyiwl5Vmo2B1cR1S1Ipkyv4mdlbJjMa1lmuKvKY9FrBbEANIaMlnWn5Rf7uO+rC/VgYabNkE17Hw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8" + integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.3.tgz#b8aaeba0a45caca7b56a5de9459872dde3727214" + integrity sha512-uCoznIPDmnickEi6D0v11SBpW0OuVqHJCa7syXqQHy5uktSCreIlt0iglsCnmvz8yCb38hGcWeseA8cWJSwv5Q== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.15.tgz#db9e4238931eb69ef8aab0ad6523d4d4caa39d03" + integrity sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A== + dependencies: + "@babel/types" "^7.3.0" + +"@types/cheerio@^0.22.22": + version "0.22.22" + resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.22.tgz#ae71cf4ca59b8bbaf34c99af7a5d6c8894988f5f" + integrity sha512-05DYX4zU96IBfZFY+t3Mh88nlwSMtmmzSYaQkKN48T495VV1dkHSah6qYyDTN5ngaS0i0VonH37m+RuzSM0YiA== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" + integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + +"@types/node@*": + version "14.11.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.8.tgz#fe2012f2355e4ce08bca44aeb3abbb21cf88d33f" + integrity sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw== + +"@types/stack-utils@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" + integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== + +"@types/yargs-parser@*": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + +"@types/yargs@^13.0.0": + version "13.0.11" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.11.tgz#def2f0c93e4bdf2c61d7e34899b17e34be28d3b1" + integrity sha512-NRqD6T4gktUrDi1o1wLH3EKC1o2caCr7/wR87ODcbVITQF106OM3sFN92ysZ++wqelOd1CTzatnOBRDYYG6wGQ== + dependencies: + "@types/yargs-parser" "*" + +abab@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + +abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +acorn-globals@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-walk@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^5.5.3: + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== + +acorn@^6.0.1: + version "6.4.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.7.1: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +airbnb-prop-types@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz#b96274cefa1abb14f623f804173ee97c13971dc2" + integrity sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg== + dependencies: + array.prototype.find "^2.1.1" + function.prototype.name "^1.1.2" + is-regex "^1.1.0" + object-is "^1.1.2" + object.assign "^4.1.0" + object.entries "^1.1.2" + prop-types "^15.7.2" + prop-types-exact "^1.2.0" + react-is "^16.13.1" + +ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.0.0, ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= + +array-filter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" + integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +array.prototype.find@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c" + integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.4" + +array.prototype.flat@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" + integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== + +babel-jest@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" + integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw== + dependencies: + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/babel__core" "^7.1.0" + babel-plugin-istanbul "^5.1.0" + babel-preset-jest "^24.9.0" + chalk "^2.4.2" + slash "^2.0.0" + +babel-plugin-istanbul@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854" + integrity sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + find-up "^3.0.0" + istanbul-lib-instrument "^3.3.0" + test-exclude "^5.2.3" + +babel-plugin-jest-hoist@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756" + integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw== + dependencies: + "@types/babel__traverse" "^7.0.6" + +babel-plugin-transform-amd-to-commonjs@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-amd-to-commonjs/-/babel-plugin-transform-amd-to-commonjs-1.4.0.tgz#d9bc5003eaa26dbdd4e854e453f84903852af2ca" + integrity sha512-Xx0kYPn0LPyms+8n2KLn9yd2R5XMb2P1sNe4qn64/UQY5F2KFYlhhhyYUNm/BThfODAzl7rbaOsEfpU2M8iDKQ== + +babel-preset-jest@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc" + integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg== + dependencies: + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + babel-plugin-jest-hoist "^24.9.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browser-resolve@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== + dependencies: + resolve "1.1.7" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +cheerio@^1.0.0-rc.3: + version "1.0.0-rc.3" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" + integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.1" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^2.19.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +convert-source-map@^1.4.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +css-select@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-what@2.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" + integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== + +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssstyle@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== + dependencies: + cssom "0.3.x" + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + +data-urls@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.1.0, debug@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" + integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== + dependencies: + ms "2.1.2" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decimal.js@^10.3.1: + version "10.4.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.0.tgz#97a7448873b01e92e5ff9117d89a7bca8e63e0fe" + integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +detect-newline@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= + +diff-sequences@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" + integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== + +discontinuous-range@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" + integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo= + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +dom-serializer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + +domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.2.tgz#f3b6e549201e46f588b59463dd77187131fe6971" + integrity sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA== + +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== + dependencies: + webidl-conversions "^4.0.2" + +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +encoding@^0.1.11: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +entities@^1.1.1, entities@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" + integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== + +entities@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.3.1.tgz#c34062a94c865c322f9d67b4384e4169bcede6a4" + integrity sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg== + +enzyme-adapter-react-16@^1.13.2: + version "1.15.5" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.5.tgz#7a6f0093d3edd2f7025b36e7fbf290695473ee04" + integrity sha512-33yUJGT1nHFQlbVI5qdo5Pfqvu/h4qPwi1o0a6ZZsjpiqq92a3HjynDhwd1IeED+Su60HDWV8mxJqkTnLYdGkw== + dependencies: + enzyme-adapter-utils "^1.13.1" + enzyme-shallow-equal "^1.0.4" + has "^1.0.3" + object.assign "^4.1.0" + object.values "^1.1.1" + prop-types "^15.7.2" + react-is "^16.13.1" + react-test-renderer "^16.0.0-0" + semver "^5.7.0" + +enzyme-adapter-utils@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.1.tgz#59c1b734b0927543e3d8dc477299ec957feb312d" + integrity sha512-5A9MXXgmh/Tkvee3bL/9RCAAgleHqFnsurTYCbymecO4ohvtNO5zqIhHxV370t7nJAwaCfkgtffarKpC0GPt0g== + dependencies: + airbnb-prop-types "^2.16.0" + function.prototype.name "^1.1.2" + object.assign "^4.1.0" + object.fromentries "^2.0.2" + prop-types "^15.7.2" + semver "^5.7.1" + +enzyme-shallow-equal@^1.0.1, enzyme-shallow-equal@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz#b9256cb25a5f430f9bfe073a84808c1d74fced2e" + integrity sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q== + dependencies: + has "^1.0.3" + object-is "^1.1.2" + +enzyme-to-json@^3.3.5: + version "3.6.1" + resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.6.1.tgz#d60740950bc7ca6384dfe6fe405494ec5df996bc" + integrity sha512-15tXuONeq5ORoZjV/bUo2gbtZrN2IH+Z6DvL35QmZyKHgbY1ahn6wcnLd9Xv9OjiwbAXiiP8MRZwbZrCv1wYNg== + dependencies: + "@types/cheerio" "^0.22.22" + lodash "^4.17.15" + react-is "^16.12.0" + +enzyme@^3.9.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.11.0.tgz#71d680c580fe9349f6f5ac6c775bc3e6b7a79c28" + integrity sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw== + dependencies: + array.prototype.flat "^1.2.3" + cheerio "^1.0.0-rc.3" + enzyme-shallow-equal "^1.0.1" + function.prototype.name "^1.1.2" + has "^1.0.3" + html-element-map "^1.2.0" + is-boolean-object "^1.0.1" + is-callable "^1.1.5" + is-number-object "^1.0.4" + is-regex "^1.0.5" + is-string "^1.0.5" + is-subset "^0.1.1" + lodash.escape "^4.0.1" + lodash.isequal "^4.5.0" + object-inspect "^1.7.0" + object-is "^1.0.2" + object.assign "^4.1.0" + object.entries "^1.1.1" + object.values "^1.1.1" + raf "^3.4.1" + rst-selector-parser "^2.2.3" + string.prototype.trim "^1.2.1" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5: + version "1.17.7" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" + integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: + version "1.18.0-next.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" + integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.9.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +exec-sh@^0.3.2: + version "0.3.4" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" + integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca" + integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q== + dependencies: + "@jest/types" "^24.9.0" + ansi-styles "^3.2.0" + jest-get-type "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-regex-util "^24.9.0" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +fbjs@^0.8.16: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.13" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function.prototype.name@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45" + integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + functions-have-names "^1.2.0" + +functions-have-names@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.1.tgz#a981ac397fa0c9964551402cdc5533d7a4d52f91" + integrity sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA== + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +html-element-map@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.2.0.tgz#dfbb09efe882806af63d990cf6db37993f099f22" + integrity sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw== + dependencies: + array-filter "^1.0.0" + +html-encoding-sniffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== + dependencies: + whatwg-encoding "^1.0.1" + +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +htmlparser2@^3.9.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +iconv-lite@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" + integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +import-local@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + dependencies: + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-boolean-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" + integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-negative-zero@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= + +is-number-object@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" + integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + +is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== + dependencies: + has-symbols "^1.0.1" + +is-stream@^1.0.1, is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" + integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== + +istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" + integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== + dependencies: + "@babel/generator" "^7.4.0" + "@babel/parser" "^7.4.3" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.3" + "@babel/types" "^7.4.0" + istanbul-lib-coverage "^2.0.5" + semver "^6.0.0" + +istanbul-lib-report@^2.0.4: + version "2.0.8" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" + integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== + dependencies: + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + supports-color "^6.1.0" + +istanbul-lib-source-maps@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" + integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + rimraf "^2.6.3" + source-map "^0.6.1" + +istanbul-reports@^2.2.6: + version "2.2.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.7.tgz#5d939f6237d7b48393cc0959eab40cd4fd056931" + integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg== + dependencies: + html-escaper "^2.0.0" + +jest-changed-files@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" + integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg== + dependencies: + "@jest/types" "^24.9.0" + execa "^1.0.0" + throat "^4.0.0" + +jest-cli@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.9.0.tgz#ad2de62d07472d419c6abc301fc432b98b10d2af" + integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg== + dependencies: + "@jest/core" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + exit "^0.1.2" + import-local "^2.0.0" + is-ci "^2.0.0" + jest-config "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + prompts "^2.0.1" + realpath-native "^1.1.0" + yargs "^13.3.0" + +jest-config@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.9.0.tgz#fb1bbc60c73a46af03590719efa4825e6e4dd1b5" + integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^24.9.0" + "@jest/types" "^24.9.0" + babel-jest "^24.9.0" + chalk "^2.0.1" + glob "^7.1.1" + jest-environment-jsdom "^24.9.0" + jest-environment-node "^24.9.0" + jest-get-type "^24.9.0" + jest-jasmine2 "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + micromatch "^3.1.10" + pretty-format "^24.9.0" + realpath-native "^1.1.0" + +jest-diff@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" + integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== + dependencies: + chalk "^2.0.1" + diff-sequences "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-docblock@^24.3.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" + integrity sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA== + dependencies: + detect-newline "^2.1.0" + +jest-each@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05" + integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog== + dependencies: + "@jest/types" "^24.9.0" + chalk "^2.0.1" + jest-get-type "^24.9.0" + jest-util "^24.9.0" + pretty-format "^24.9.0" + +jest-environment-jsdom@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b" + integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + jest-util "^24.9.0" + jsdom "^11.5.1" + +jest-environment-node@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3" + integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + jest-util "^24.9.0" + +jest-get-type@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" + integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== + +jest-haste-map@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" + integrity sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ== + dependencies: + "@jest/types" "^24.9.0" + anymatch "^2.0.0" + fb-watchman "^2.0.0" + graceful-fs "^4.1.15" + invariant "^2.2.4" + jest-serializer "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.9.0" + micromatch "^3.1.10" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^1.2.7" + +jest-jasmine2@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0" + integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + co "^4.6.0" + expect "^24.9.0" + is-generator-fn "^2.0.0" + jest-each "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-runtime "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + pretty-format "^24.9.0" + throat "^4.0.0" + +jest-leak-detector@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a" + integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA== + dependencies: + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-matcher-utils@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" + integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== + dependencies: + chalk "^2.0.1" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-message-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" + integrity sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/stack-utils" "^1.0.1" + chalk "^2.0.1" + micromatch "^3.1.10" + slash "^2.0.0" + stack-utils "^1.0.1" + +jest-mock@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" + integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== + dependencies: + "@jest/types" "^24.9.0" + +jest-pnp-resolver@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^24.3.0, jest-regex-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636" + integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA== + +jest-resolve-dependencies@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz#ad055198959c4cfba8a4f066c673a3f0786507ab" + integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g== + dependencies: + "@jest/types" "^24.9.0" + jest-regex-util "^24.3.0" + jest-snapshot "^24.9.0" + +jest-resolve@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.9.0.tgz#dff04c7687af34c4dd7e524892d9cf77e5d17321" + integrity sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ== + dependencies: + "@jest/types" "^24.9.0" + browser-resolve "^1.11.3" + chalk "^2.0.1" + jest-pnp-resolver "^1.2.1" + realpath-native "^1.1.0" + +jest-runner@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42" + integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg== + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.4.2" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-config "^24.9.0" + jest-docblock "^24.3.0" + jest-haste-map "^24.9.0" + jest-jasmine2 "^24.9.0" + jest-leak-detector "^24.9.0" + jest-message-util "^24.9.0" + jest-resolve "^24.9.0" + jest-runtime "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.6.0" + source-map-support "^0.5.6" + throat "^4.0.0" + +jest-runtime@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac" + integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw== + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.9.0" + "@jest/source-map" "^24.3.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/yargs" "^13.0.0" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.1.15" + jest-config "^24.9.0" + jest-haste-map "^24.9.0" + jest-message-util "^24.9.0" + jest-mock "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + realpath-native "^1.1.0" + slash "^2.0.0" + strip-bom "^3.0.0" + yargs "^13.3.0" + +jest-serializer@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73" + integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ== + +jest-snapshot@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba" + integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + expect "^24.9.0" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-resolve "^24.9.0" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^24.9.0" + semver "^6.2.0" + +jest-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" + integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== + dependencies: + "@jest/console" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/source-map" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + callsites "^3.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.15" + is-ci "^2.0.0" + mkdirp "^0.5.1" + slash "^2.0.0" + source-map "^0.6.0" + +jest-validate@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab" + integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ== + dependencies: + "@jest/types" "^24.9.0" + camelcase "^5.3.1" + chalk "^2.0.1" + jest-get-type "^24.9.0" + leven "^3.1.0" + pretty-format "^24.9.0" + +jest-watcher@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.9.0.tgz#4b56e5d1ceff005f5b88e528dc9afc8dd4ed2b3b" + integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw== + dependencies: + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/yargs" "^13.0.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + jest-util "^24.9.0" + string-length "^2.0.0" + +jest-worker@^24.6.0, jest-worker@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" + integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== + dependencies: + merge-stream "^2.0.0" + supports-color "^6.1.0" + +jest@^24.6.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171" + integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw== + dependencies: + import-local "^2.0.0" + jest-cli "^24.9.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@20.0.0: + version "20.0.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.0.tgz#882825ac9cc5e5bbee704ba16143e1fa78361ebf" + integrity sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA== + dependencies: + abab "^2.0.6" + acorn "^8.7.1" + acorn-globals "^6.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.2" + decimal.js "^10.3.1" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "^7.0.0" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + ws "^8.8.0" + xml-name-validator "^4.0.0" + +jsdom@^11.5.1: + version "11.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" + integrity sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw== + dependencies: + abab "^2.0.0" + acorn "^5.5.3" + acorn-globals "^4.1.0" + array-equal "^1.0.0" + cssom ">= 0.3.2 < 0.4.0" + cssstyle "^1.0.0" + data-urls "^1.0.0" + domexception "^1.0.1" + escodegen "^1.9.1" + html-encoding-sniffer "^1.0.2" + left-pad "^1.3.0" + nwsapi "^2.0.7" + parse5 "4.0.0" + pn "^1.1.0" + request "^2.87.0" + request-promise-native "^1.0.5" + sax "^1.2.4" + symbol-tree "^3.2.2" + tough-cookie "^2.3.4" + w3c-hr-time "^1.0.1" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.3" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.1" + ws "^5.2.0" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + dependencies: + minimist "^1.2.5" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +left-pad@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" + integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash.escape@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" + integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg= + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash@^4.15.0, lodash@^4.17.15, lodash@^4.17.19: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +moo@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4" + integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nan@^2.12.1: + version "2.14.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nearley@^2.7.10: + version "2.19.7" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.7.tgz#eafbe3e2d8ccfe70adaa5c026ab1f9709c116218" + integrity sha512-Y+KNwhBPcSJKeyQCFjn8B/MIe+DDlhaaDgjVldhy5xtFewIbiQgcbZV8k2gCVwkI1ZsKCnjIYZbR+0Fim5QYgg== + dependencies: + commander "^2.19.0" + moo "^0.5.0" + railroad-diagrams "^1.0.0" + randexp "0.4.6" + semver "^5.4.1" + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-notifier@^5.4.2: + version "5.4.3" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50" + integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q== + dependencies: + growly "^1.3.0" + is-wsl "^1.1.0" + semver "^5.5.0" + shellwords "^0.1.1" + which "^1.3.0" + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +nth-check@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +nwsapi@^2.0.7: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +nwsapi@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c" + integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.7.0, object-inspect@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + +object-is@^1.0.2, object-is@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81" + integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0, object.assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" + integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.0" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.1, object.entries@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + has "^1.0.3" + +object.fromentries@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" + integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +p-each-series@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" + integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= + dependencies: + p-reduce "^1.0.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-limit@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-reduce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" + integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse5@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" + integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== + +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== + dependencies: + "@types/node" "*" + +parse5@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.0.0.tgz#51f74a5257f5fcc536389e8c2d0b3802e1bfa91a" + integrity sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g== + dependencies: + entities "^4.3.0" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" + integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +pretty-format@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" + integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== + dependencies: + "@jest/types" "^24.9.0" + ansi-regex "^4.0.0" + ansi-styles "^3.2.0" + react-is "^16.8.4" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + +prompts@^2.0.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" + integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.4" + +prop-types-exact@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869" + integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA== + dependencies: + has "^1.0.3" + object.assign "^4.1.0" + reflect.ownkeys "^0.2.0" + +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +railroad-diagrams@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" + integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234= + +randexp@0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" + integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ== + dependencies: + discontinuous-range "1.0.0" + ret "~0.1.10" + +react-dom-factories@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/react-dom-factories/-/react-dom-factories-1.0.2.tgz#eb7705c4db36fb501b3aa38ff759616aa0ff96e0" + integrity sha1-63cFxNs2+1AbOqOP91lhaqD/luA= + +react-dom@16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.1.tgz#7f8b0223b3a5fbe205116c56deb85de32685dad6" + integrity sha512-1Gin+wghF/7gl4Cqcvr1DxFX2Osz7ugxSwl6gBqCMpdrxHjIFUS7GYxrFftZ9Ln44FHw0JxCFD9YtZsrbR5/4A== + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + +react-is@^16.12.0, react-is@^16.13.1, react-is@^16.4.1, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-test-renderer@16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.1.tgz#f2fb30c2c7b517db6e5b10ed20bb6b0a7ccd8d70" + integrity sha512-wyyiPxRZOTpKnNIgUBOB6xPLTpIzwcQMIURhZvzUqZzezvHjaGNsDPBhMac5fIY3Jf5NuKxoGvV64zDSOECPPQ== + dependencies: + fbjs "^0.8.16" + object-assign "^4.1.1" + prop-types "^15.6.0" + react-is "^16.4.1" + +react-test-renderer@^16.0.0-0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1" + integrity sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ== + dependencies: + object-assign "^4.1.1" + prop-types "^15.6.2" + react-is "^16.8.6" + scheduler "^0.19.1" + +react@16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32" + integrity sha512-3GEs0giKp6E0Oh/Y9ZC60CmYgUPnp7voH9fbjWsvXtYFb4EWtgQub0ADSq0sJR0BbHc4FThLLtzlcFaFXIorwg== + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + +read-pkg-up@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" + integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== + dependencies: + find-up "^3.0.0" + read-pkg "^3.0.0" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +readable-stream@^3.1.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +realpath-native@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" + integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA== + dependencies: + util.promisify "^1.0.0" + +reflect.ownkeys@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" + integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== + dependencies: + lodash "^4.17.19" + +request-promise-native@^1.0.5: + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== + dependencies: + request-promise-core "1.1.4" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.87.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= + dependencies: + resolve-from "^3.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + +resolve@^1.10.0, resolve@^1.3.2: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rimraf@^2.5.4, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rst-selector-parser@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" + integrity sha1-gbIw6i/MYGbInjRy3nlChdmwPZE= + dependencies: + lodash.flattendeep "^4.4.0" + nearley "^2.7.10" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.0.0, semver@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +sisteransi@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.6: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.6" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" + integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" + integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +string-length@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" + integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= + dependencies: + astral-regex "^1.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string.prototype.trim@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.2.tgz#f538d0bacd98fc4297f0bef645226d5aaebf59f3" + integrity sha512-b5yrbl3BXIjHau9Prk7U0RRYcUYdN4wGSVaqoBQS50CCE3KBuYU0TYRNPFCP7aVoNMX87HKThdMRVIP3giclKg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.0" + +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +symbol-tree@^3.2.2, symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +test-exclude@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" + integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== + dependencies: + glob "^7.1.3" + minimatch "^3.0.4" + read-pkg-up "^4.0.0" + require-main-filename "^2.0.0" + +throat@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" + integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" + integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +ua-parser-js@^0.7.18: + version "0.7.22" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3" + integrity sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q== + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +uri-js@^4.2.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" + integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +w3c-hr-time@^1.0.1, w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923" + integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg== + dependencies: + xml-name-validator "^4.0.0" + +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + +whatwg-fetch@>=0.10.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz#e5f871572d6879663fa5674c8f833f15a8425ab3" + integrity sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ== + +whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^6.4.1: + version "6.5.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" + integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9, which@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529" + integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + dependencies: + async-limiter "~1.0.0" + +ws@^8.8.0: + version "8.8.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0" + integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^13.3.0: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" |