summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/components/test
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/components/test')
-rw-r--r--devtools/client/shared/components/test/browser/browser.toml14
-rw-r--r--devtools/client/shared/components/test/browser/browser_notification_box_basic.js34
-rw-r--r--devtools/client/shared/components/test/browser/browser_reps_stubs.js406
-rw-r--r--devtools/client/shared/components/test/chrome/accordion.snapshots.js176
-rw-r--r--devtools/client/shared/components/test/chrome/chrome.toml87
-rw-r--r--devtools/client/shared/components/test/chrome/head.js379
-rw-r--r--devtools/client/shared/components/test/chrome/test_GridElementWidthResizer.html209
-rw-r--r--devtools/client/shared/components/test/chrome/test_GridElementWidthResizer_RTL.html210
-rw-r--r--devtools/client/shared/components/test/chrome/test_HSplitBox_01.html140
-rw-r--r--devtools/client/shared/components/test/chrome/test_accordion.html141
-rw-r--r--devtools/client/shared/components/test/chrome/test_frame_01.html361
-rw-r--r--devtools/client/shared/components/test/chrome/test_frame_02.html103
-rw-r--r--devtools/client/shared/components/test/chrome/test_list.html127
-rw-r--r--devtools/client/shared/components/test/chrome/test_list_keyboard.html283
-rw-r--r--devtools/client/shared/components/test/chrome/test_notification_box_01.html136
-rw-r--r--devtools/client/shared/components/test/chrome/test_notification_box_02.html73
-rw-r--r--devtools/client/shared/components/test/chrome/test_notification_box_03.html87
-rw-r--r--devtools/client/shared/components/test/chrome/test_notification_box_04.html67
-rw-r--r--devtools/client/shared/components/test/chrome/test_notification_box_05.html63
-rw-r--r--devtools/client/shared/components/test/chrome/test_searchbox-with-autocomplete.html301
-rw-r--r--devtools/client/shared/components/test/chrome/test_searchbox.html74
-rw-r--r--devtools/client/shared/components/test/chrome/test_sidebar_toggle.html59
-rw-r--r--devtools/client/shared/components/test/chrome/test_smart-trace-grouping.html141
-rw-r--r--devtools/client/shared/components/test/chrome/test_smart-trace-source-maps.html290
-rw-r--r--devtools/client/shared/components/test/chrome/test_smart-trace.html172
-rw-r--r--devtools/client/shared/components/test/chrome/test_stack-trace-source-maps.html98
-rw-r--r--devtools/client/shared/components/test/chrome/test_stack-trace.html100
-rw-r--r--devtools/client/shared/components/test/chrome/test_tabs_accessibility.html82
-rw-r--r--devtools/client/shared/components/test/chrome/test_tabs_menu.html84
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree-view_01.html290
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree-view_02.html136
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_01.html68
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_02.html49
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_03.html50
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_04.html133
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_05.html195
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_06.html340
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_07.html69
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_08.html61
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_09.html85
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_10.html57
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_11.html100
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_12.html146
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_13.html88
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_14.html245
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_15.html99
-rw-r--r--devtools/client/shared/components/test/chrome/test_tree_16.html145
-rw-r--r--devtools/client/shared/components/test/node/.eslintrc.js10
-rw-r--r--devtools/client/shared/components/test/node/__mocks__/Services.js14
-rw-r--r--devtools/client/shared/components/test/node/__mocks__/object-front.js55
-rw-r--r--devtools/client/shared/components/test/node/__mocks__/string-front.js15
-rw-r--r--devtools/client/shared/components/test/node/babel.config.js13
-rw-r--r--devtools/client/shared/components/test/node/components/__snapshots__/tree.test.js.snap1171
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/basic.test.js.snap63
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/classnames.test.js.snap351
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/entries.test.js.snap94
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/expand.test.js.snap175
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/getter-setter.test.js.snap51
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/keyboard-navigation.test.js.snap55
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/properties.test.js.snap19
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/proxy.test.js.snap9
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/window.test.js.snap2119
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/basic.test.js439
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/classnames.test.js53
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/create-long-string-front.test.js94
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/create-object-client.test.js114
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/entries.test.js137
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/events.test.js171
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/expand.test.js435
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/function.test.js90
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/getter-setter.test.js106
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/keyboard-navigation.test.js89
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/properties.test.js158
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/proxy.test.js133
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/should-item-update.test.js96
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/component/window.test.js96
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/test-utils.js231
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/__snapshots__/promises.test.js.snap49
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/create-node.test.js87
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/get-children.test.js278
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/get-closest-grip-node.test.js52
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/get-value.test.js91
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/make-node-for-properties.test.js295
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/make-numerical-buckets.test.js138
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/node-has-entries.test.js51
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/node-is-window.test.js20
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/node-supports-numerical-bucketing.test.js72
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/promises.test.js54
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-entries.test.js171
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-full-text.test.js56
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-indexed-properties.test.js259
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-non-indexed-properties.test.js222
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-prototype.test.js218
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-symbols.test.js218
-rw-r--r--devtools/client/shared/components/test/node/components/object-inspector/utils/should-render-roots-in-reps.test.js153
-rw-r--r--devtools/client/shared/components/test/node/components/reps/__snapshots__/accessor.test.js.snap3
-rw-r--r--devtools/client/shared/components/test/node/components/reps/__snapshots__/element-node.test.js.snap42
-rw-r--r--devtools/client/shared/components/test/node/components/reps/__snapshots__/error.test.js.snap1225
-rw-r--r--devtools/client/shared/components/test/node/components/reps/__snapshots__/nan.test.js.snap10
-rw-r--r--devtools/client/shared/components/test/node/components/reps/accessible.test.js321
-rw-r--r--devtools/client/shared/components/test/node/components/reps/accessor.test.js137
-rw-r--r--devtools/client/shared/components/test/node/components/reps/array.test.js117
-rw-r--r--devtools/client/shared/components/test/node/components/reps/attribute.test.js44
-rw-r--r--devtools/client/shared/components/test/node/components/reps/big-int.test.js106
-rw-r--r--devtools/client/shared/components/test/node/components/reps/comment-node.test.js80
-rw-r--r--devtools/client/shared/components/test/node/components/reps/date-time.test.js61
-rw-r--r--devtools/client/shared/components/test/node/components/reps/document-type.test.js51
-rw-r--r--devtools/client/shared/components/test/node/components/reps/document.test.js52
-rw-r--r--devtools/client/shared/components/test/node/components/reps/element-node.test.js668
-rw-r--r--devtools/client/shared/components/test/node/components/reps/error.test.js869
-rw-r--r--devtools/client/shared/components/test/node/components/reps/event.test.js160
-rw-r--r--devtools/client/shared/components/test/node/components/reps/failure.test.js66
-rw-r--r--devtools/client/shared/components/test/node/components/reps/function.test.js584
-rw-r--r--devtools/client/shared/components/test/node/components/reps/grip-array.test.js755
-rw-r--r--devtools/client/shared/components/test/node/components/reps/grip-entry.test.js191
-rw-r--r--devtools/client/shared/components/test/node/components/reps/grip-map.test.js390
-rw-r--r--devtools/client/shared/components/test/node/components/reps/grip.test.js750
-rw-r--r--devtools/client/shared/components/test/node/components/reps/helper-tests.test.js122
-rw-r--r--devtools/client/shared/components/test/node/components/reps/infinity.test.js70
-rw-r--r--devtools/client/shared/components/test/node/components/reps/long-string.test.js135
-rw-r--r--devtools/client/shared/components/test/node/components/reps/nan.test.js43
-rw-r--r--devtools/client/shared/components/test/node/components/reps/null.test.js47
-rw-r--r--devtools/client/shared/components/test/node/components/reps/number.test.js136
-rw-r--r--devtools/client/shared/components/test/node/components/reps/object-with-text.test.js66
-rw-r--r--devtools/client/shared/components/test/node/components/reps/object-with-url.test.js45
-rw-r--r--devtools/client/shared/components/test/node/components/reps/object.test.js356
-rw-r--r--devtools/client/shared/components/test/node/components/reps/promise.test.js229
-rw-r--r--devtools/client/shared/components/test/node/components/reps/regexp.test.js59
-rw-r--r--devtools/client/shared/components/test/node/components/reps/string-with-url.test.js630
-rw-r--r--devtools/client/shared/components/test/node/components/reps/string.test.js257
-rw-r--r--devtools/client/shared/components/test/node/components/reps/stylesheet.test.js41
-rw-r--r--devtools/client/shared/components/test/node/components/reps/symbol.test.js64
-rw-r--r--devtools/client/shared/components/test/node/components/reps/test-helpers.js116
-rw-r--r--devtools/client/shared/components/test/node/components/reps/text-node.test.js203
-rw-r--r--devtools/client/shared/components/test/node/components/reps/undefined.test.js58
-rw-r--r--devtools/client/shared/components/test/node/components/reps/window.test.js197
-rw-r--r--devtools/client/shared/components/test/node/components/tree.test.js911
-rw-r--r--devtools/client/shared/components/test/node/jest.config.js16
-rw-r--r--devtools/client/shared/components/test/node/package.json27
-rw-r--r--devtools/client/shared/components/test/node/setup.js15
-rw-r--r--devtools/client/shared/components/test/node/stubs/object-inspector/grip.js64
-rw-r--r--devtools/client/shared/components/test/node/stubs/object-inspector/map.js154
-rw-r--r--devtools/client/shared/components/test/node/stubs/object-inspector/performance.js784
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/accessible.js74
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/accessor.js85
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/attribute.js36
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/big-int.js196
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/browser_dummy.js11
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/comment-node.js36
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/date-time.js47
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/document-type.js40
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/document.js39
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/element-node.js292
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/error.js396
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/event.js269
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/failure.js21
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/function.js227
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/grip-array.js1087
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/grip-entry.js16
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/grip-map.js908
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/grip.js1057
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/infinity.js19
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/long-string.js39
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/nan.js15
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/null.js15
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/number.js21
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/object-with-text.js36
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/object-with-url.js22
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/promise.js244
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/regexp.js36
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/stubs.toml20
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/stylesheet.js29
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/symbol.js33
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/text-node.js141
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/undefined.js15
-rw-r--r--devtools/client/shared/components/test/node/stubs/reps/window.js65
-rw-r--r--devtools/client/shared/components/test/node/yarn.lock4209
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"
+ >
+ &lt;prototype&gt;
+ </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"
+ >
+ &lt;
+ </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"
+ >
+ &gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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"
+ >
+ &lt;anonymous&gt;
+ </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 &lt;anonymous&gt; 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&lt;/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&lt;/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&lt;/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&lt;/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&lt;/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&lt;/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&lt;/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"