summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/test/browser_inspector_breadcrumbs_mutations.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/inspector/test/browser_inspector_breadcrumbs_mutations.js282
1 files changed, 282 insertions, 0 deletions
diff --git a/devtools/client/inspector/test/browser_inspector_breadcrumbs_mutations.js b/devtools/client/inspector/test/browser_inspector_breadcrumbs_mutations.js
new file mode 100644
index 0000000000..91e3976907
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_mutations.js
@@ -0,0 +1,282 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the breadcrumbs widget refreshes correctly when there are markup
+// mutations (and that it doesn't refresh when those mutations don't change its
+// output).
+
+const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs.html";
+
+// Each item in the TEST_DATA array is a test case that should contain the
+// following properties:
+// - desc {String} A description of this test case (will be logged).
+// - setup {Function*} A generator function (can yield promises) that sets up
+// the test case. Useful for selecting a node before starting the test.
+// - run {Function*} A generator function (can yield promises) that runs the
+// actual test case, i.e, mutates the content DOM to cause the breadcrumbs
+// to refresh, or not.
+// - shouldRefresh {Boolean} Once the `run` function has completed, and the test
+// has detected that the page has changed, this boolean instructs the test to
+// verify if the breadcrumbs has refreshed or not.
+// - output {Array} A list of strings for the text that should be found in each
+// button after the test has run.
+const TEST_DATA = [
+ {
+ desc: "Adding a child at the end of the chain shouldn't change anything",
+ async setup(inspector) {
+ await selectNode("#i1111", inspector);
+ },
+ async run({ walker, selection }) {
+ await walker.setInnerHTML(selection.nodeFront, "<b>test</b>");
+ },
+ shouldRefresh: false,
+ output: ["html", "body", "article#i1", "div#i11", "div#i111", "div#i1111"],
+ },
+ {
+ desc: "Updating an ID to an displayed element should refresh",
+ setup() {},
+ async run({ walker }) {
+ const node = await walker.querySelector(walker.rootNode, "#i1");
+ await node.modifyAttributes([
+ {
+ attributeName: "id",
+ newValue: "i1-changed",
+ },
+ ]);
+ },
+ shouldRefresh: true,
+ output: [
+ "html",
+ "body",
+ "article#i1-changed",
+ "div#i11",
+ "div#i111",
+ "div#i1111",
+ ],
+ },
+ {
+ desc: "Updating an class to a displayed element should refresh",
+ setup() {},
+ async run({ walker }) {
+ const node = await walker.querySelector(walker.rootNode, "body");
+ await node.modifyAttributes([
+ {
+ attributeName: "class",
+ newValue: "test-class",
+ },
+ ]);
+ },
+ shouldRefresh: true,
+ output: [
+ "html",
+ "body.test-class",
+ "article#i1-changed",
+ "div#i11",
+ "div#i111",
+ "div#i1111",
+ ],
+ },
+ {
+ desc:
+ "Updating a non id/class attribute to a displayed element should not " +
+ "refresh",
+ setup() {},
+ async run({ walker }) {
+ const node = await walker.querySelector(walker.rootNode, "#i11");
+ await node.modifyAttributes([
+ {
+ attributeName: "name",
+ newValue: "value",
+ },
+ ]);
+ },
+ shouldRefresh: false,
+ output: [
+ "html",
+ "body.test-class",
+ "article#i1-changed",
+ "div#i11",
+ "div#i111",
+ "div#i1111",
+ ],
+ },
+ {
+ desc: "Moving a child in an element that's not displayed should not refresh",
+ setup() {},
+ async run({ walker }) {
+ // Re-append #i1211 as a last child of #i2.
+ const parent = await walker.querySelector(walker.rootNode, "#i2");
+ const child = await walker.querySelector(walker.rootNode, "#i211");
+ await walker.insertBefore(child, parent);
+ },
+ shouldRefresh: false,
+ output: [
+ "html",
+ "body.test-class",
+ "article#i1-changed",
+ "div#i11",
+ "div#i111",
+ "div#i1111",
+ ],
+ },
+ {
+ desc: "Moving an undisplayed child in a displayed element should not refresh",
+ setup() {},
+ async run({ walker }) {
+ // Re-append #i2 in body (move it to the end).
+ const parent = await walker.querySelector(walker.rootNode, "body");
+ const child = await walker.querySelector(walker.rootNode, "#i2");
+ await walker.insertBefore(child, parent);
+ },
+ shouldRefresh: false,
+ output: [
+ "html",
+ "body.test-class",
+ "article#i1-changed",
+ "div#i11",
+ "div#i111",
+ "div#i1111",
+ ],
+ },
+ {
+ desc:
+ "Updating attributes on an element that's not displayed should not " +
+ "refresh",
+ setup() {},
+ async run({ walker }) {
+ const node = await walker.querySelector(walker.rootNode, "#i2");
+ await node.modifyAttributes([
+ {
+ attributeName: "id",
+ newValue: "i2-changed",
+ },
+ {
+ attributeName: "class",
+ newValue: "test-class",
+ },
+ ]);
+ },
+ shouldRefresh: false,
+ output: [
+ "html",
+ "body.test-class",
+ "article#i1-changed",
+ "div#i11",
+ "div#i111",
+ "div#i1111",
+ ],
+ },
+ {
+ desc: "Removing the currently selected node should refresh",
+ async setup(inspector) {
+ await selectNode("#i2-changed", inspector);
+ },
+ async run({ walker, selection }) {
+ await walker.removeNode(selection.nodeFront);
+ },
+ shouldRefresh: true,
+ output: ["html", "body.test-class"],
+ },
+ {
+ desc: "Changing the class of the currently selected node should refresh",
+ setup() {},
+ async run({ selection }) {
+ await selection.nodeFront.modifyAttributes([
+ {
+ attributeName: "class",
+ newValue: "test-class-changed",
+ },
+ ]);
+ },
+ shouldRefresh: true,
+ output: ["html", "body.test-class-changed"],
+ },
+ {
+ desc: "Changing the id of the currently selected node should refresh",
+ setup() {},
+ async run({ selection }) {
+ await selection.nodeFront.modifyAttributes([
+ {
+ attributeName: "id",
+ newValue: "new-id",
+ },
+ ]);
+ },
+ shouldRefresh: true,
+ output: ["html", "body#new-id.test-class-changed"],
+ },
+];
+
+add_task(async function () {
+ const { inspector } = await openInspectorForURL(TEST_URI);
+ const breadcrumbs = inspector.panelDoc.getElementById(
+ "inspector-breadcrumbs"
+ );
+ const container = breadcrumbs.querySelector(".html-arrowscrollbox-inner");
+ const win = container.ownerDocument.defaultView;
+
+ for (const { desc, setup, run, shouldRefresh, output } of TEST_DATA) {
+ info("Running test case: " + desc);
+
+ info(
+ "Listen to markupmutation events from the inspector to know when a " +
+ "test case has completed"
+ );
+ const onContentMutation = inspector.once("markupmutation");
+
+ info("Running setup");
+ await setup(inspector);
+
+ info("Listen to mutations on the breadcrumbs container");
+ let hasBreadcrumbsMutated = false;
+ const observer = new win.MutationObserver(mutations => {
+ // Only consider childList changes or tooltiptext/checked attributes
+ // changes. The rest may be mutations caused by the overflowing arrowbox.
+ for (const { type, attributeName } of mutations) {
+ const isChildList = type === "childList";
+ const isAttributes =
+ type === "attributes" &&
+ (attributeName === "checked" || attributeName === "tooltiptext");
+ if (isChildList || isAttributes) {
+ hasBreadcrumbsMutated = true;
+ break;
+ }
+ }
+ });
+ observer.observe(container, {
+ attributes: true,
+ childList: true,
+ subtree: true,
+ });
+
+ info("Running the test case");
+ await run(inspector);
+
+ info("Wait until the page has mutated");
+ await onContentMutation;
+
+ if (shouldRefresh) {
+ info("The breadcrumbs is expected to refresh, so wait for it");
+ await inspector.once("inspector-updated");
+ } else {
+ ok(
+ !inspector._updateProgress,
+ "The breadcrumbs widget is not currently updating"
+ );
+ }
+
+ is(shouldRefresh, hasBreadcrumbsMutated, "Has the breadcrumbs refreshed?");
+ observer.disconnect();
+
+ info("Check the output of the breadcrumbs widget");
+ is(container.childNodes.length, output.length, "Correct number of buttons");
+ for (let i = 0; i < container.childNodes.length; i++) {
+ is(
+ output[i],
+ container.childNodes[i].textContent,
+ "Text content for button " + i + " is correct"
+ );
+ }
+ }
+});