summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/extensions/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/inspector/extensions/test/browser.toml15
-rw-r--r--devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js451
-rw-r--r--devtools/client/inspector/extensions/test/head.js21
-rw-r--r--devtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js224
4 files changed, 711 insertions, 0 deletions
diff --git a/devtools/client/inspector/extensions/test/browser.toml b/devtools/client/inspector/extensions/test/browser.toml
new file mode 100644
index 0000000000..2c6ad4538c
--- /dev/null
+++ b/devtools/client/inspector/extensions/test/browser.toml
@@ -0,0 +1,15 @@
+[DEFAULT]
+tags = "devtools"
+subsuite = "devtools"
+support-files = [
+ "head.js",
+ "head_devtools_inspector_sidebar.js",
+ "!/devtools/client/inspector/test/head.js",
+ "!/devtools/client/inspector/test/shared-head.js",
+ "!/devtools/client/shared/test/shared-head.js",
+ "!/devtools/client/shared/test/telemetry-test-helpers.js",
+ "!/devtools/client/shared/test/highlighter-test-actor.js",
+]
+
+["browser_inspector_extension_sidebar.js"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
diff --git a/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js b/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
new file mode 100644
index 0000000000..740b1fda13
--- /dev/null
+++ b/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
@@ -0,0 +1,451 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const SIDEBAR_ID = "an-extension-sidebar";
+const SIDEBAR_TITLE = "Sidebar Title";
+
+let extension;
+let fakeExtCallerInfo;
+
+let toolbox;
+let inspector;
+
+add_task(async function setupExtensionSidebar() {
+ extension = ExtensionTestUtils.loadExtension({
+ background() {
+ // This is just an empty extension used to ensure that the caller extension uuid
+ // actually exists.
+ },
+ });
+
+ await extension.startup();
+
+ fakeExtCallerInfo = {
+ url: WebExtensionPolicy.getByID(extension.id).getURL(
+ "fake-caller-script.js"
+ ),
+ lineNumber: 1,
+ addonId: extension.id,
+ };
+
+ const res = await openInspectorForURL("about:blank");
+ inspector = res.inspector;
+ toolbox = res.toolbox;
+
+ const onceSidebarCreated = toolbox.once(
+ `extension-sidebar-created-${SIDEBAR_ID}`
+ );
+ toolbox.registerInspectorExtensionSidebar(SIDEBAR_ID, {
+ title: SIDEBAR_TITLE,
+ });
+
+ const sidebar = await onceSidebarCreated;
+
+ // Test sidebar properties.
+ is(
+ sidebar,
+ inspector.getPanel(SIDEBAR_ID),
+ "Got an extension sidebar instance equal to the one saved in the inspector"
+ );
+ is(
+ sidebar.title,
+ SIDEBAR_TITLE,
+ "Got the expected title in the extension sidebar instance"
+ );
+ is(
+ sidebar.provider.props.title,
+ SIDEBAR_TITLE,
+ "Got the expeted title in the provider props"
+ );
+
+ // Test sidebar Redux state.
+ const inspectorStoreState = inspector.store.getState();
+ ok(
+ "extensionsSidebar" in inspectorStoreState,
+ "Got the extensionsSidebar sub-state in the inspector Redux store"
+ );
+ Assert.deepEqual(
+ inspectorStoreState.extensionsSidebar,
+ {},
+ "The extensionsSidebar should be initially empty"
+ );
+});
+
+add_task(async function testSidebarSetObject() {
+ const object = {
+ propertyName: {
+ nestedProperty: "propertyValue",
+ anotherProperty: "anotherValue",
+ },
+ };
+
+ const sidebar = inspector.getPanel(SIDEBAR_ID);
+ sidebar.setObject(object);
+
+ // Test updated sidebar Redux state.
+ const inspectorStoreState = inspector.store.getState();
+ is(
+ Object.keys(inspectorStoreState.extensionsSidebar).length,
+ 1,
+ "The extensionsSidebar state contains the newly registered extension sidebar state"
+ );
+ Assert.deepEqual(
+ inspectorStoreState.extensionsSidebar,
+ {
+ [SIDEBAR_ID]: {
+ viewMode: "object-treeview",
+ object,
+ },
+ },
+ "Got the expected state for the registered extension sidebar"
+ );
+
+ // Select the extension sidebar.
+ const waitSidebarSelected = toolbox.once(`inspector-sidebar-select`);
+ inspector.sidebar.show(SIDEBAR_ID);
+ await waitSidebarSelected;
+
+ const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+ // Test extension sidebar content.
+ ok(
+ sidebarPanelContent,
+ "Got a sidebar panel for the registered extension sidebar"
+ );
+
+ assertTreeView(sidebarPanelContent, {
+ expectedTreeTables: 1,
+ expectedStringCells: 2,
+ expectedNumberCells: 0,
+ });
+
+ // Test sidebar refreshed on further sidebar.setObject calls.
+ info("Change the inspected object in the extension sidebar object treeview");
+ sidebar.setObject({ aNewProperty: 123 });
+
+ assertTreeView(sidebarPanelContent, {
+ expectedTreeTables: 1,
+ expectedStringCells: 0,
+ expectedNumberCells: 1,
+ });
+});
+
+add_task(async function testSidebarSetExpressionResult() {
+ const { commands } = toolbox;
+ const sidebar = inspector.getPanel(SIDEBAR_ID);
+ const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+ info("Testing sidebar.setExpressionResult with rootTitle");
+
+ const expression = `
+ var obj = Object.create(null);
+ obj.prop1 = 123;
+ obj[Symbol('sym1')] = 456;
+ obj.cyclic = obj;
+ obj;
+ `;
+
+ const consoleFront = await toolbox.target.getFront("console");
+ let evalResult = await commands.inspectedWindowCommand.eval(
+ fakeExtCallerInfo,
+ expression,
+ {
+ consoleFront,
+ }
+ );
+
+ sidebar.setExpressionResult(evalResult, "Expected Root Title");
+
+ // Wait the ObjectInspector component to be rendered and test its content.
+ await testSetExpressionSidebarPanel(sidebarPanelContent, {
+ nodesLength: 4,
+ propertiesNames: ["cyclic", "prop1", "Symbol(sym1)"],
+ rootTitle: "Expected Root Title",
+ });
+
+ info("Testing sidebar.setExpressionResult without rootTitle");
+
+ sidebar.setExpressionResult(evalResult);
+
+ // Wait the ObjectInspector component to be rendered and test its content.
+ await testSetExpressionSidebarPanel(sidebarPanelContent, {
+ nodesLength: 4,
+ propertiesNames: ["cyclic", "prop1", "Symbol(sym1)"],
+ });
+
+ info("Test expanding the object");
+ const oi = sidebarPanelContent.querySelector(".tree");
+ const cyclicNode = oi.querySelectorAll(".node")[1];
+ ok(cyclicNode.innerText.includes("cyclic"), "Found the expected node");
+ cyclicNode.click();
+
+ await TestUtils.waitForCondition(
+ () => oi.querySelectorAll(".node").length === 7,
+ "Wait for the 'cyclic' node to be expanded"
+ );
+
+ await TestUtils.waitForCondition(
+ () => oi.querySelector(".tree-node.focused"),
+ "Wait for the 'cyclic' node to be focused"
+ );
+ ok(
+ oi.querySelector(".tree-node.focused").innerText.includes("cyclic"),
+ "'cyclic' node is focused"
+ );
+
+ info("Test keyboard navigation");
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, oi.ownerDocument.defaultView);
+ await TestUtils.waitForCondition(
+ () => oi.querySelectorAll(".node").length === 4,
+ "Wait for the 'cyclic' node to be collapsed"
+ );
+ ok(
+ oi.querySelector(".tree-node.focused").innerText.includes("cyclic"),
+ "'cyclic' node is still focused"
+ );
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, oi.ownerDocument.defaultView);
+ await TestUtils.waitForCondition(
+ () => oi.querySelectorAll(".tree-node")[2].classList.contains("focused"),
+ "Wait for the 'prop1' node to be focused"
+ );
+
+ ok(
+ oi.querySelector(".tree-node.focused").innerText.includes("prop1"),
+ "'prop1' node is focused"
+ );
+
+ info(
+ "Testing sidebar.setExpressionResult for an expression returning a longstring"
+ );
+ evalResult = await commands.inspectedWindowCommand.eval(
+ fakeExtCallerInfo,
+ `"ab ".repeat(10000)`,
+ {
+ consoleFront,
+ }
+ );
+ sidebar.setExpressionResult(evalResult);
+
+ await TestUtils.waitForCondition(() => {
+ const longStringEl = sidebarPanelContent.querySelector(
+ ".tree .objectBox-string"
+ );
+ return (
+ longStringEl && longStringEl.textContent.includes("ab ".repeat(10000))
+ );
+ }, "Wait for the longString to be render with its full text");
+ ok(true, "The longString is expanded and its full text is displayed");
+
+ info(
+ "Testing sidebar.setExpressionResult for an expression returning a primitive"
+ );
+ evalResult = await commands.inspectedWindowCommand.eval(
+ fakeExtCallerInfo,
+ `1 + 2`,
+ {
+ consoleFront,
+ }
+ );
+ sidebar.setExpressionResult(evalResult);
+ const numberEl = await TestUtils.waitForCondition(
+ () => sidebarPanelContent.querySelector(".objectBox-number"),
+ "Wait for the result number element to be rendered"
+ );
+ is(numberEl.textContent, "3", `The "1 + 2" expression was evaluated as "3"`);
+});
+
+add_task(async function testSidebarDOMNodeHighlighting() {
+ const { commands } = toolbox;
+ const sidebar = inspector.getPanel(SIDEBAR_ID);
+ const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+ const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } =
+ getHighlighterTestHelpers(inspector);
+
+ const expression = "({ body: document.body })";
+
+ const consoleFront = await toolbox.target.getFront("console");
+ const evalResult = await commands.inspectedWindowCommand.eval(
+ fakeExtCallerInfo,
+ expression,
+ {
+ consoleFront,
+ }
+ );
+
+ sidebar.setExpressionResult(evalResult);
+
+ // Wait the DOM node to be rendered inside the component.
+ await waitForObjectInspector(sidebarPanelContent, "node");
+
+ // Wait for the object to be expanded so we only target the "body" property node, and
+ // not the root object element.
+ await TestUtils.waitForCondition(
+ () =>
+ sidebarPanelContent.querySelectorAll(".object-inspector .tree-node")
+ .length > 1
+ );
+
+ // Get and verify the DOMNode and the "open inspector"" icon
+ // rendered inside the ObjectInspector.
+
+ assertObjectInspector(sidebarPanelContent, {
+ expectedDOMNodes: 2,
+ expectedOpenInspectors: 2,
+ });
+
+ // Test highlight DOMNode on mouseover.
+ info("Highlight the node by moving the cursor on it");
+
+ const onNodeHighlight = waitForHighlighterTypeShown(
+ inspector.highlighters.TYPES.BOXMODEL
+ );
+
+ moveMouseOnObjectInspectorDOMNode(sidebarPanelContent);
+
+ const { nodeFront } = await onNodeHighlight;
+ is(nodeFront.displayName, "body", "The correct node was highlighted");
+
+ // Test unhighlight DOMNode on mousemove.
+ info("Unhighlight the node by moving away from the node");
+ const onNodeUnhighlight = waitForHighlighterTypeHidden(
+ inspector.highlighters.TYPES.BOXMODEL
+ );
+
+ moveMouseOnPanelCenter(sidebarPanelContent);
+
+ await onNodeUnhighlight;
+ info("The node is no longer highlighted");
+});
+
+add_task(async function testSidebarDOMNodeOpenInspector() {
+ const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+ // Test DOMNode selected in the inspector when "open inspector"" icon clicked.
+ info("Unselect node in the inspector");
+ let onceNewNodeFront = inspector.selection.once("new-node-front");
+ inspector.selection.setNodeFront(null);
+ let nodeFront = await onceNewNodeFront;
+ is(nodeFront, null, "The inspector selection should have been unselected");
+
+ info(
+ "Select the ObjectInspector DOMNode in the inspector panel by clicking on it"
+ );
+
+ // In test mode, shown highlighters are not automatically hidden after a delay to
+ // prevent intermittent test failures from race conditions.
+ // Restore this behavior just for this test because it is explicitly checked.
+ const HIGHLIGHTER_AUTOHIDE_TIMER = inspector.HIGHLIGHTER_AUTOHIDE_TIMER;
+ inspector.HIGHLIGHTER_AUTOHIDE_TIMER = 1000;
+ registerCleanupFunction(() => {
+ // Restore the value to disable autohiding to not impact other tests.
+ inspector.HIGHLIGHTER_AUTOHIDE_TIMER = HIGHLIGHTER_AUTOHIDE_TIMER;
+ });
+
+ const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } =
+ getHighlighterTestHelpers(inspector);
+
+ // Once we click the open-inspector icon we expect a new node front to be selected
+ // and the node to have been highlighted and unhighlighted.
+ const onNodeHighlight = waitForHighlighterTypeShown(
+ inspector.highlighters.TYPES.BOXMODEL
+ );
+ const onNodeUnhighlight = waitForHighlighterTypeHidden(
+ inspector.highlighters.TYPES.BOXMODEL
+ );
+ onceNewNodeFront = inspector.selection.once("new-node-front");
+
+ clickOpenInspectorIcon(sidebarPanelContent);
+
+ nodeFront = await onceNewNodeFront;
+ is(nodeFront.displayName, "body", "The correct node has been selected");
+ const { nodeFront: highlightedNodeFront } = await onNodeHighlight;
+ is(
+ highlightedNodeFront.displayName,
+ "body",
+ "The correct node was highlighted"
+ );
+
+ await onNodeUnhighlight;
+});
+
+add_task(async function testSidebarSetExtensionPage() {
+ const sidebar = inspector.getPanel(SIDEBAR_ID);
+ const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+ info("Testing sidebar.setExtensionPage");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.allow_unsafe_parent_loads", true]],
+ });
+
+ const expectedURL =
+ "data:text/html,<!DOCTYPE html><html><body><h1>Extension Page";
+
+ sidebar.setExtensionPage(expectedURL);
+
+ await testSetExtensionPageSidebarPanel(sidebarPanelContent, expectedURL);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function teardownExtensionSidebar() {
+ info("Remove the sidebar instance");
+
+ toolbox.unregisterInspectorExtensionSidebar(SIDEBAR_ID);
+
+ ok(
+ !inspector.sidebar.getTabPanel(SIDEBAR_ID),
+ "The rendered extension sidebar has been removed"
+ );
+
+ const inspectorStoreState = inspector.store.getState();
+
+ Assert.deepEqual(
+ inspectorStoreState.extensionsSidebar,
+ {},
+ "The extensions sidebar Redux store data has been cleared"
+ );
+
+ await extension.unload();
+
+ toolbox = null;
+ inspector = null;
+ extension = null;
+});
+
+add_task(async function testActiveTabOnNonExistingSidebar() {
+ // Set a fake non existing sidebar id in the activeSidebar pref,
+ // to simulate the scenario where an extension has installed a sidebar
+ // which has been saved in the preference but it doesn't exist anymore.
+ await SpecialPowers.pushPrefEnv({
+ set: [["devtools.inspector.activeSidebar", "unexisting-sidebar-id"]],
+ });
+
+ const res = await openInspectorForURL("about:blank");
+ inspector = res.inspector;
+ toolbox = res.toolbox;
+
+ const onceSidebarCreated = toolbox.once(
+ `extension-sidebar-created-${SIDEBAR_ID}`
+ );
+ toolbox.registerInspectorExtensionSidebar(SIDEBAR_ID, {
+ title: SIDEBAR_TITLE,
+ });
+
+ // Wait the extension sidebar to be created and then unregister it to force the tabbar
+ // to select a new one.
+ await onceSidebarCreated;
+ toolbox.unregisterInspectorExtensionSidebar(SIDEBAR_ID);
+
+ is(
+ inspector.sidebar.getCurrentTabID(),
+ "layoutview",
+ "Got the expected inspector sidebar tab selected"
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/devtools/client/inspector/extensions/test/head.js b/devtools/client/inspector/extensions/test/head.js
new file mode 100644
index 0000000000..17d7538904
--- /dev/null
+++ b/devtools/client/inspector/extensions/test/head.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/. */
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+"use strict";
+
+// Import the inspector's head.js first (which itself imports shared-head.js).
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
+ this
+);
+
+// Import the inspector extensions test helpers (shared between the tests that live
+// in the current devtools test directory and the devtools sidebar tests that live
+// in browser/components/extensions/test/browser).
+Services.scriptloader.loadSubScript(
+ new URL("head_devtools_inspector_sidebar.js", gTestPath).href,
+ this
+);
diff --git a/devtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js b/devtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js
new file mode 100644
index 0000000000..73467c3e31
--- /dev/null
+++ b/devtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js
@@ -0,0 +1,224 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* exported getExtensionSidebarActors, expectNoSuchActorIDs,
+ waitForObjectInspector, testSetExpressionSidebarPanel, assertTreeView,
+ assertObjectInspector, moveMouseOnObjectInspectorDOMNode,
+ moveMouseOnPanelCenter, clickOpenInspectorIcon */
+
+"use strict";
+
+const ACCORDION_LABEL_SELECTOR = ".accordion-header-label";
+const ACCORDION_CONTENT_SELECTOR = ".accordion-content";
+
+// Retrieve the array of all the objectValueGrip actors from the
+// inspector extension sidebars state
+// (used in browser_ext_devtools_panels_elements_sidebar.js).
+function getExtensionSidebarActors(inspector) {
+ const state = inspector.store.getState();
+
+ const actors = [];
+
+ for (const sidebarId of Object.keys(state.extensionsSidebar)) {
+ const sidebarState = state.extensionsSidebar[sidebarId];
+
+ if (
+ sidebarState.viewMode === "object-value-grip-view" &&
+ sidebarState.objectValueGrip &&
+ sidebarState.objectValueGrip.actor
+ ) {
+ actors.push(sidebarState.objectValueGrip.actor);
+ }
+ }
+
+ return actors;
+}
+
+// Test that the specified objectValueGrip actors have been released
+// on the remote debugging server
+// (used in browser_ext_devtools_panels_elements_sidebar.js).
+async function expectNoSuchActorIDs(client, actors) {
+ info(`Test that all the objectValueGrip actors have been released`);
+ for (const actor of actors) {
+ await Assert.rejects(
+ client.request({ to: actor, type: "requestTypes" }),
+ err => err.message == `No such actor for ID: ${actor}`
+ );
+ }
+}
+
+function waitForObjectInspector(panelDoc, waitForNodeWithType = "object") {
+ const selector = `.object-inspector .objectBox-${waitForNodeWithType}`;
+ return TestUtils.waitForCondition(() => {
+ return !!panelDoc.querySelectorAll(selector).length;
+ }, `Wait for objectInspector's node type "${waitForNodeWithType}" to be loaded`);
+}
+
+// Helper function used inside the sidebar.setExtensionPage test case.
+async function testSetExtensionPageSidebarPanel(panelDoc, expectedURL) {
+ const selector = "iframe.inspector-extension-sidebar-page";
+ const iframesCount = await TestUtils.waitForCondition(() => {
+ return panelDoc.querySelectorAll(selector).length;
+ }, "Wait for the extension page iframe");
+
+ is(
+ iframesCount,
+ 1,
+ "Got the expected number of iframes in the extension panel"
+ );
+
+ const iframeWindow = panelDoc.querySelector(selector).contentWindow;
+ await TestUtils.waitForCondition(() => {
+ return iframeWindow.document.readyState === "complete";
+ }, "Wait for the extension page iframe to complete to load");
+
+ is(
+ iframeWindow.location.href,
+ expectedURL,
+ "Got the expected url in the extension panel iframe"
+ );
+}
+
+// Helper function used inside the sidebar.setObjectValueGrip test case.
+async function testSetExpressionSidebarPanel(panel, expected) {
+ const { nodesLength, propertiesNames, rootTitle } = expected;
+
+ await waitForObjectInspector(panel);
+
+ const objectInspectors = [...panel.querySelectorAll(".tree")];
+ is(
+ objectInspectors.length,
+ 1,
+ "There is the expected number of object inspectors"
+ );
+ const [objectInspector] = objectInspectors;
+
+ await TestUtils.waitForCondition(() => {
+ return objectInspector.querySelectorAll(".node").length >= nodesLength;
+ }, "Wait the objectInspector to have been fully rendered");
+
+ const oiNodes = objectInspector.querySelectorAll(".node");
+
+ is(
+ oiNodes.length,
+ nodesLength,
+ "Got the expected number of nodes in the tree"
+ );
+ const propertiesNodes = [
+ ...objectInspector.querySelectorAll(".object-label"),
+ ].map(el => el.textContent);
+ is(
+ JSON.stringify(propertiesNodes),
+ JSON.stringify(propertiesNames),
+ "Got the expected property names"
+ );
+
+ if (rootTitle) {
+ // Also check that the ObjectInspector is rendered inside
+ // an Accordion component with the expected title.
+ const accordion = panel.querySelector(".accordion");
+
+ ok(accordion, "Got an Accordion component as expected");
+
+ is(
+ accordion.querySelector(ACCORDION_CONTENT_SELECTOR).firstChild,
+ objectInspector,
+ "The ObjectInspector should be inside the Accordion content"
+ );
+
+ is(
+ accordion.querySelector(ACCORDION_LABEL_SELECTOR).textContent,
+ rootTitle,
+ "The Accordion has the expected label"
+ );
+ } else {
+ // Also check that there is no Accordion component rendered
+ // inside the sidebar panel.
+ ok(
+ !panel.querySelector(".accordion"),
+ "Got no Accordion component as expected"
+ );
+ }
+}
+
+function assertTreeView(panelDoc, expectedContent) {
+ const { expectedTreeTables, expectedStringCells, expectedNumberCells } =
+ expectedContent;
+
+ if (expectedTreeTables) {
+ is(
+ panelDoc.querySelectorAll("table.treeTable").length,
+ expectedTreeTables,
+ "The panel document contains the expected number of TreeView components"
+ );
+ }
+
+ if (expectedStringCells) {
+ is(
+ panelDoc.querySelectorAll("table.treeTable .stringCell").length,
+ expectedStringCells,
+ "The panel document contains the expected number of string cells."
+ );
+ }
+
+ if (expectedNumberCells) {
+ is(
+ panelDoc.querySelectorAll("table.treeTable .numberCell").length,
+ expectedNumberCells,
+ "The panel document contains the expected number of number cells."
+ );
+ }
+}
+
+async function assertObjectInspector(panelDoc, expectedContent) {
+ const { expectedDOMNodes, expectedOpenInspectors } = expectedContent;
+
+ // Get and verify the DOMNode and the "open inspector"" icon
+ // rendered inside the ObjectInspector.
+ const nodes = panelDoc.querySelectorAll(".objectBox-node");
+ const nodeOpenInspectors = panelDoc.querySelectorAll(
+ ".objectBox-node .open-inspector"
+ );
+
+ is(
+ nodes.length,
+ expectedDOMNodes,
+ "Found the expected number of ObjectInspector DOMNodes"
+ );
+ is(
+ nodeOpenInspectors.length,
+ expectedOpenInspectors,
+ "Found the expected nuber of open-inspector icons inside the ObjectInspector"
+ );
+}
+
+function moveMouseOnObjectInspectorDOMNode(panelDoc, nodeIndex = 0) {
+ const nodes = panelDoc.querySelectorAll(".objectBox-node");
+ const node = nodes[nodeIndex];
+
+ ok(node, "Found the ObjectInspector DOMNode");
+
+ EventUtils.synthesizeMouseAtCenter(
+ node,
+ { type: "mousemove" },
+ node.ownerDocument.defaultView
+ );
+}
+
+function moveMouseOnPanelCenter(panelDoc) {
+ EventUtils.synthesizeMouseAtCenter(
+ panelDoc,
+ { type: "mousemove" },
+ panelDoc.window
+ );
+}
+
+function clickOpenInspectorIcon(panelDoc, nodeIndex = 0) {
+ const nodes = panelDoc.querySelectorAll(".objectBox-node .open-inspector");
+ const node = nodes[nodeIndex];
+
+ ok(node, "Found the ObjectInspector open-inspector icon");
+
+ node.click();
+}