summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/components/test/node
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /devtools/client/shared/components/test/node
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/shared/components/test/node')
-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.snap2114
-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.snap1210
-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.js74
-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.js654
-rw-r--r--devtools/client/shared/components/test/node/components/reps/error.test.js748
-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.js705
-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.js377
-rw-r--r--devtools/client/shared/components/test/node/components/reps/grip.test.js705
-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.js216
-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.js186
-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.js131
-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.ini19
-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.js29
-rw-r--r--devtools/client/shared/components/test/node/yarn.lock4209
130 files changed, 30061 insertions, 0 deletions
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..2995d8de9b
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/window.test.js.snap
@@ -0,0 +1,2114 @@
+// 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"
+ />
+ </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"
+ />
+ </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"
+ />
+ </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"
+ />
+ </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"
+ />
+ </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..8cf04ea9c9
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/reps/__snapshots__/error.test.js.snap
@@ -0,0 +1,1210 @@
+// 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 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..df420f8b1b
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/reps/comment-node.test.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 { 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.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..f8cb2e401b
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/reps/element-node.test.js
@@ -0,0 +1,654 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 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..ebbbd8be48
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/reps/error.test.js
@@ -0,0 +1,748 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 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");
+ });
+});
+
+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");
+ });
+});
+
+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");
+ });
+});
+
+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");
+ });
+});
+
+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");
+ });
+});
+
+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");
+ });
+});
+
+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");
+ });
+});
+
+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");
+ });
+});
+
+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");
+ });
+});
+
+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();
+ });
+});
+
+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..a674c4275d
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/reps/grip-array.test.js
@@ -0,0 +1,705 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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..5ef582d56f
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/reps/grip-map.test.js
@@ -0,0 +1,377 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.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.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)})`);
+
+ 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.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.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.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..7debeead3b
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/reps/grip.test.js
@@ -0,0 +1,705 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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..03862d19f6
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/reps/promise.test.js
@@ -0,0 +1,216 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.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.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.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.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.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.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..dff4b15b0c
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/reps/text-node.test.js
@@ -0,0 +1,186 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.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.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.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.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..9645ca288c
--- /dev/null
+++ b/devtools/client/shared/components/test/node/components/reps/window.test.js
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"
+ );
+ });
+});
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.ini b/devtools/client/shared/components/test/node/stubs/reps/stubs.ini
new file mode 100644
index 0000000000..636ca34885
--- /dev/null
+++ b/devtools/client/shared/components/test/node/stubs/reps/stubs.ini
@@ -0,0 +1,19 @@
+[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 ini 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..67e76e5e32
--- /dev/null
+++ b/devtools/client/shared/components/test/node/stubs/reps/window.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(`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"
+});
+
+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"