diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/client/inspector/markup/test/helper_events_test_runner.js | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/devtools/client/inspector/markup/test/helper_events_test_runner.js b/devtools/client/inspector/markup/test/helper_events_test_runner.js new file mode 100644 index 0000000000..1b100d39bc --- /dev/null +++ b/devtools/client/inspector/markup/test/helper_events_test_runner.js @@ -0,0 +1,297 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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"}] */ +/* import-globals-from head.js */ +/* import-globals-from helper_diff.js */ +"use strict"; + +const beautify = require("resource://devtools/shared/jsbeautify/beautify.js"); + +loadHelperScript("helper_diff.js"); + +/** + * Generator function that runs checkEventsForNode() for each object in the + * TEST_DATA array. + */ +async function runEventPopupTests(url, tests) { + const { inspector } = await openInspectorForURL(url); + + await inspector.markup.expandAll(); + + for (const test of tests) { + await checkEventsForNode(test, inspector); + } + + // Wait for promises to avoid leaks when running this as a single test. + // We need to do this because we have opened a bunch of popups and don't them + // to affect other test runs when they are GCd. + await promiseNextTick(); +} + +/** + * Generator function that takes a selector and expected results and returns + * the event info. + * + * @param {Object} test + * A test object should contain the following properties: + * - selector {String} a css selector targeting the node to edit + * - expected {Array} array of expected event objects + * - type {String} event type + * - filename {String} filename:line where the evt handler is defined + * - attributes {Array} array of event attributes ({String}) + * - handler {String} string representation of the handler + * - beforeTest {Function} (optional) a function to execute on the page + * before running the test + * - isSourceMapped {Boolean} (optional) true if the location + * is source-mapped, requiring some extra delay before the checks + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * opened + */ +async function checkEventsForNode(test, inspector) { + const { selector, expected, beforeTest, isSourceMapped } = test; + const container = await getContainerForSelector(selector, inspector); + + if (typeof beforeTest === "function") { + await beforeTest(inspector); + } + + const evHolder = container.elt.querySelector( + ".inspector-badge.interactive[data-event]" + ); + + if (expected.length === 0) { + // If no event is expected, check that event bubble is hidden. + ok(!evHolder, "event bubble should be hidden"); + return; + } + + const tooltip = inspector.markup.eventDetailsTooltip; + + await selectNode(selector, inspector); + + let sourceMapPromise = null; + if (isSourceMapped) { + sourceMapPromise = tooltip.once("event-tooltip-source-map-ready"); + } + + // Click button to show tooltip + info("Clicking evHolder"); + evHolder.scrollIntoView({ + block: "center", + inline: "end", + behavior: "instant", + }); + EventUtils.synthesizeMouseAtCenter( + evHolder, + {}, + inspector.markup.doc.defaultView + ); + await tooltip.once("shown"); + info("tooltip shown"); + + if (isSourceMapped) { + info("Waiting for source map to be applied"); + await sourceMapPromise; + } + + // Check values + const headers = tooltip.panel.querySelectorAll(".event-header"); + const nodeFront = container.node; + const cssSelector = nodeFront.nodeName + "#" + nodeFront.id; + + for (let i = 0; i < headers.length; i++) { + const label = `${cssSelector}.${expected[i].type} (index ${i})`; + info(`${label} START`); + + const header = headers[i]; + const type = header.querySelector(".event-tooltip-event-type"); + const filename = header.querySelector(".event-tooltip-filename"); + const attributes = Array.from( + header.querySelectorAll(".event-tooltip-attributes") + ); + const contentBox = header.nextElementSibling; + + info("Looking for " + type.textContent); + + is(type.textContent, expected[i].type, "type matches for " + cssSelector); + is( + filename.textContent, + expected[i].filename, + "filename matches for " + cssSelector + ); + + Assert.deepEqual( + attributes.map(el => el.textContent), + expected[i].attributes, + `we have the expected attributes for "${cssSelector}"` + ); + + is( + header.classList.contains("content-expanded"), + false, + "We are not in expanded state" + ); + + // Make sure the header is not hidden by scrollbars before clicking. + header.scrollIntoView(); + + // Avoid clicking the header's center (could hit the debugger button) + EventUtils.synthesizeMouse(header, 2, 2, {}, type.ownerGlobal); + await tooltip.once("event-tooltip-ready"); + + is( + header.classList.contains("content-expanded") && + contentBox.hasAttribute("open"), + true, + "We are in expanded state and icon changed" + ); + + is( + tooltip.panel.querySelectorAll(".event-header.content-expanded") + .length === 1 && + tooltip.panel.querySelectorAll(".event-tooltip-content-box[open]") + .length === 1, + true, + "Only one event box is expanded at a time" + ); + + const editor = tooltip.eventTooltip._eventEditors.get(contentBox).editor; + const tidiedHandler = beautify.js(expected[i].handler, { + indent_size: 2, + }); + testDiff( + editor.getText(), + tidiedHandler, + "handler matches for " + cssSelector, + ok + ); + + const checkbox = header.querySelector("input[type=checkbox]"); + ok(checkbox, "The event toggling checkbox is displayed"); + const disabled = checkbox.hasAttribute("disabled"); + // We can't disable React/jQuery events at the moment, so ensure that for those, + // the checkbox is disabled. + const shouldBeDisabled = + expected[i].attributes?.includes("React") || + expected[i].attributes?.includes("jQuery"); + Assert.strictEqual( + disabled, + shouldBeDisabled, + `The checkbox is ${shouldBeDisabled ? "disabled" : "enabled"}\n` + ); + + info(`${label} END`); + } + + const tooltipHidden = tooltip.once("hidden"); + tooltip.hide(); + await tooltipHidden; +} + +/** + * This should be kept in sync with the content of the window load event listener callback + * content in doc_markup_events_jquery.html. + */ +function getDocMarkupEventsJQueryLoadHandlerText() { + return ` + () => { + const handler1 = function liveDivDblClick() { + alert(1); + }; + const handler2 = function liveDivDragStart() { + alert(2); + }; + const handler3 = function liveDivDragLeave() { + alert(3); + }; + const handler4 = function liveDivDragEnd() { + alert(4); + }; + const handler5 = function liveDivDrop() { + alert(5); + }; + const handler6 = function liveDivDragOver() { + alert(6); + }; + const handler7 = function divClick1() { + alert(7); + }; + const handler8 = function divClick2() { + alert(8); + }; + const handler9 = function divKeyDown() { + alert(9); + }; + const handler10 = function divDragOut() { + alert(10); + }; + + if ($("#livediv").live) { + $("#livediv").live("dblclick", handler1); + $("#livediv").live("dragstart", handler2); + } + + if ($("#livediv").delegate) { + $(document).delegate("#livediv", "dragleave", handler3); + $(document).delegate("#livediv", "dragend", handler4); + } + + if ($("#livediv").on) { + $(document).on("drop", "#livediv", handler5); + $(document).on("dragover", "#livediv", handler6); + $(document).on("dragout", "#livediv:xxxxx", handler10); + } + + const div = $("div")[0]; + $(div).click(handler7); + $(div).click(handler8); + $(div).keydown(handler9); + + class MyClass { + constructor() { + $(document).on("click", '#inclassboundeventdiv', this.onClick.bind(this)); + } + onClick() { alert(11); } + } + new MyClass(); + }`; +} + +/** + * Create diff of two strings. + * + * @param {String} text1 + * String to compare with text2. + * @param {String} text2 [description] + * String to compare with text1. + * @param {String} msg + * Message to display on failure. A diff will be displayed after this + * message. + */ +function testDiff(text1, text2, msg) { + let out = ""; + + if (text1 === text2) { + ok(true, msg); + return; + } + + const result = textDiff(text1, text2); + + for (const { atom, operation } of result) { + switch (operation) { + case "add": + out += "+ " + atom + "\n"; + break; + case "delete": + out += "- " + atom + "\n"; + break; + case "none": + out += " " + atom + "\n"; + break; + } + } + + ok(false, msg + "\nDIFF:\n==========\n" + out + "==========\n"); +} |