import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,9 @@
"use strict";
module.exports = {
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../.eslintrc.mochitests.js",
"globals": {
"DeveloperToolbar": true
}
};

View file

@ -0,0 +1,188 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
browser_layoutHelpers.html
browser_layoutHelpers-getBoxQuads.html
browser_templater_basic.html
browser_toolbar_basic.html
browser_toolbar_webconsole_errors_count.html
browser_devices.json
doc_options-view.xul
head.js
helper_color_data.js
helper_html_tooltip.js
helper_inplace_editor.js
html-mdn-css-basic-testing.html
html-mdn-css-no-summary.html
html-mdn-css-no-summary-or-syntax.html
html-mdn-css-no-syntax.html
html-mdn-css-syntax-old-style.html
leakhunt.js
test-actor.js
test-actor-registry.js
!/devtools/client/framework/test/shared-head.js
[browser_css_angle.js]
[browser_css_color.js]
[browser_cubic-bezier-01.js]
[browser_cubic-bezier-02.js]
[browser_cubic-bezier-03.js]
[browser_cubic-bezier-04.js]
[browser_cubic-bezier-05.js]
[browser_cubic-bezier-06.js]
[browser_filter-editor-01.js]
[browser_filter-editor-02.js]
[browser_filter-editor-03.js]
[browser_filter-editor-04.js]
[browser_filter-editor-05.js]
[browser_filter-editor-06.js]
[browser_filter-editor-07.js]
[browser_filter-editor-08.js]
[browser_filter-editor-09.js]
[browser_filter-editor-10.js]
[browser_filter-presets-01.js]
[browser_filter-presets-02.js]
[browser_filter-presets-03.js]
[browser_flame-graph-01.js]
[browser_flame-graph-02.js]
[browser_flame-graph-03a.js]
[browser_flame-graph-03b.js]
[browser_flame-graph-03c.js]
[browser_flame-graph-04.js]
[browser_flame-graph-05.js]
[browser_flame-graph-utils-01.js]
[browser_flame-graph-utils-02.js]
[browser_flame-graph-utils-03.js]
[browser_flame-graph-utils-04.js]
[browser_flame-graph-utils-05.js]
[browser_flame-graph-utils-06.js]
[browser_flame-graph-utils-hash.js]
[browser_graphs-01.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-02.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-03.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-04.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-05.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-06.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-07a.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-07b.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-07c.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-07d.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-07e.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-08.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-09a.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-09b.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-09c.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-09d.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-09e.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-09f.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-10a.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-10b.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-10c.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-11a.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-11b.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-12.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-13.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-14.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-15.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_graphs-16.js]
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_html_tooltip-01.js]
[browser_html_tooltip-02.js]
[browser_html_tooltip-03.js]
[browser_html_tooltip-04.js]
[browser_html_tooltip-05.js]
[browser_html_tooltip_arrow-01.js]
[browser_html_tooltip_arrow-02.js]
[browser_html_tooltip_consecutive-show.js]
[browser_html_tooltip_hover.js]
[browser_html_tooltip_offset.js]
[browser_html_tooltip_rtl.js]
[browser_html_tooltip_variable-height.js]
[browser_html_tooltip_width-auto.js]
[browser_html_tooltip_xul-wrapper.js]
[browser_inplace-editor-01.js]
[browser_inplace-editor-02.js]
[browser_inplace-editor_autocomplete_01.js]
[browser_inplace-editor_autocomplete_02.js]
[browser_inplace-editor_autocomplete_offset.js]
[browser_inplace-editor_maxwidth.js]
[browser_keycodes.js]
[browser_key_shortcuts.js]
[browser_layoutHelpers.js]
skip-if = e10s # Layouthelpers test should not run in a content page.
[browser_layoutHelpers-getBoxQuads.js]
skip-if = e10s # Layouthelpers test should not run in a content page.
[browser_mdn-docs-01.js]
[browser_mdn-docs-02.js]
[browser_mdn-docs-03.js]
[browser_num-l10n.js]
[browser_options-view-01.js]
[browser_outputparser.js]
skip-if = e10s # Test intermittently fails with e10s. Bug 1124162.
[browser_poller.js]
[browser_prefs-01.js]
[browser_prefs-02.js]
[browser_require_raw.js]
[browser_spectrum.js]
[browser_theme.js]
[browser_tableWidget_basic.js]
[browser_tableWidget_keyboard_interaction.js]
[browser_tableWidget_mouse_interaction.js]
[browser_telemetry_button_eyedropper.js]
[browser_telemetry_button_paintflashing.js]
skip-if = e10s # Bug 937167 - e10s paintflashing
[browser_telemetry_button_responsive.js]
skip-if = e10s # Bug 1067145 - e10s responsiveview
[browser_telemetry_button_scratchpad.js]
[browser_telemetry_sidebar.js]
[browser_telemetry_toolbox.js]
[browser_telemetry_toolboxtabs_canvasdebugger.js]
[browser_telemetry_toolboxtabs_inspector.js]
[browser_telemetry_toolboxtabs_jsdebugger.js]
[browser_telemetry_toolboxtabs_jsprofiler.js]
[browser_telemetry_toolboxtabs_netmonitor.js]
[browser_telemetry_toolboxtabs_options.js]
[browser_telemetry_toolboxtabs_shadereditor.js]
[browser_telemetry_toolboxtabs_storage.js]
[browser_telemetry_toolboxtabs_styleeditor.js]
[browser_telemetry_toolboxtabs_webaudioeditor.js]
[browser_telemetry_toolboxtabs_webconsole.js]
[browser_templater_basic.js]
[browser_toolbar_basic.js]
skip-if = (e10s && debug) # Bug 1253035
[browser_toolbar_tooltip.js]
[browser_toolbar_webconsole_errors_count.js]
skip-if = e10s # The developertoolbar error count isn't correct with e10s
[browser_treeWidget_basic.js]
[browser_treeWidget_keyboard_interaction.js]
[browser_treeWidget_mouse_interaction.js]
[browser_devices.js]
[browser_theme_switching.js]

View file

@ -0,0 +1,176 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from head.js */
"use strict";
const TEST_URI = "data:text/html;charset=utf-8,browser_css_angle.js";
var {angleUtils} = require("devtools/client/shared/css-angle");
add_task(function* () {
yield addTab("about:blank");
let [host] = yield createHost("bottom", TEST_URI);
info("Starting the test");
testAngleUtils();
testAngleValidity();
host.destroy();
gBrowser.removeCurrentTab();
});
function testAngleUtils() {
let data = getTestData();
for (let {authored, deg, rad, grad, turn} of data) {
let angle = new angleUtils.CssAngle(authored);
// Check all values.
info("Checking values for " + authored);
is(angle.deg, deg, "color.deg === deg");
is(angle.rad, rad, "color.rad === rad");
is(angle.grad, grad, "color.grad === grad");
is(angle.turn, turn, "color.turn === turn");
testToString(angle, deg, rad, grad, turn);
}
}
function testAngleValidity() {
let data = getAngleValidityData();
for (let {angle, result} of data) {
let testAngle = new angleUtils.CssAngle(angle);
let validString = testAngle.valid ? " a valid" : "an invalid";
is(testAngle.valid, result,
`Testing that "${angle}" is ${validString} angle`);
}
}
function testToString(angle, deg, rad, grad, turn) {
angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.deg;
is(angle.toString(), deg, "toString() with deg type");
angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.rad;
is(angle.toString(), rad, "toString() with rad type");
angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.grad;
is(angle.toString(), grad, "toString() with grad type");
angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.turn;
is(angle.toString(), turn, "toString() with turn type");
}
function getAngleValidityData() {
return [{
angle: "0.2turn",
result: true
}, {
angle: "-0.2turn",
result: true
}, {
angle: "-.2turn",
result: true
}, {
angle: "1e02turn",
result: true
}, {
angle: "-2e2turn",
result: true
}, {
angle: ".2turn",
result: true
}, {
angle: "0.2aaturn",
result: false
}, {
angle: "2dega",
result: false
}, {
angle: "0.deg",
result: false
}, {
angle: ".deg",
result: false
}, {
angle: "..2turn",
result: false
}];
}
function getTestData() {
return [{
authored: "0deg",
deg: "0deg",
rad: "0rad",
grad: "0grad",
turn: "0turn"
}, {
authored: "180deg",
deg: "180deg",
rad: `${Math.round(Math.PI * 10000) / 10000}rad`,
grad: "200grad",
turn: "0.5turn"
}, {
authored: "180DEG",
deg: "180DEG",
rad: `${Math.round(Math.PI * 10000) / 10000}RAD`,
grad: "200GRAD",
turn: "0.5TURN"
}, {
authored: `-${Math.PI}rad`,
deg: "-180deg",
rad: `-${Math.PI}rad`,
grad: "-200grad",
turn: "-0.5turn"
}, {
authored: `-${Math.PI}RAD`,
deg: "-180DEG",
rad: `-${Math.PI}RAD`,
grad: "-200GRAD",
turn: "-0.5TURN"
}, {
authored: "100grad",
deg: "90deg",
rad: `${Math.round(Math.PI / 2 * 10000) / 10000}rad`,
grad: "100grad",
turn: "0.25turn"
}, {
authored: "100GRAD",
deg: "90DEG",
rad: `${Math.round(Math.PI / 2 * 10000) / 10000}RAD`,
grad: "100GRAD",
turn: "0.25TURN"
}, {
authored: "-1turn",
deg: "-360deg",
rad: `${-1 * Math.round(Math.PI * 2 * 10000) / 10000}rad`,
grad: "-400grad",
turn: "-1turn"
}, {
authored: "-10TURN",
deg: "-3600DEG",
rad: `${-1 * Math.round(Math.PI * 2 * 10 * 10000) / 10000}RAD`,
grad: "-4000GRAD",
turn: "-10TURN"
}, {
authored: "inherit",
deg: "inherit",
rad: "inherit",
grad: "inherit",
turn: "inherit"
}, {
authored: "initial",
deg: "initial",
rad: "initial",
grad: "initial",
turn: "initial"
}, {
authored: "unset",
deg: "unset",
rad: "unset",
grad: "unset",
turn: "unset"
}];
}

View file

@ -0,0 +1,137 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_URI = "data:text/html;charset=utf-8,browser_css_color.js";
var {colorUtils} = require("devtools/shared/css/color");
/* global getFixtureColorData */
loadHelperScript("helper_color_data.js");
add_task(function* () {
yield addTab("about:blank");
let [host,, doc] = yield createHost("bottom", TEST_URI);
info("Creating a test canvas element to test colors");
let canvas = createTestCanvas(doc);
info("Starting the test");
testColorUtils(canvas);
host.destroy();
gBrowser.removeCurrentTab();
});
function createTestCanvas(doc) {
let canvas = doc.createElement("canvas");
canvas.width = canvas.height = 10;
doc.body.appendChild(canvas);
return canvas;
}
function testColorUtils(canvas) {
let data = getFixtureColorData();
for (let {authored, name, hex, hsl, rgb} of data) {
let color = new colorUtils.CssColor(authored);
// Check all values.
info("Checking values for " + authored);
is(color.name, name, "color.name === name");
is(color.hex, hex, "color.hex === hex");
is(color.hsl, hsl, "color.hsl === hsl");
is(color.rgb, rgb, "color.rgb === rgb");
testToString(color, name, hex, hsl, rgb);
testColorMatch(name, hex, hsl, rgb, color.rgba, canvas);
}
testSetAlpha();
}
function testToString(color, name, hex, hsl, rgb) {
color.colorUnit = colorUtils.CssColor.COLORUNIT.name;
is(color.toString(), name, "toString() with authored type");
color.colorUnit = colorUtils.CssColor.COLORUNIT.hex;
is(color.toString(), hex, "toString() with hex type");
color.colorUnit = colorUtils.CssColor.COLORUNIT.hsl;
is(color.toString(), hsl, "toString() with hsl type");
color.colorUnit = colorUtils.CssColor.COLORUNIT.rgb;
is(color.toString(), rgb, "toString() with rgb type");
}
function testColorMatch(name, hex, hsl, rgb, rgba, canvas) {
let target;
let ctx = canvas.getContext("2d");
let clearCanvas = function () {
canvas.width = 1;
};
let setColor = function (color) {
ctx.fillStyle = color;
ctx.fillRect(0, 0, 1, 1);
};
let setTargetColor = function () {
clearCanvas();
// All colors have rgba so we can use this to compare against.
setColor(rgba);
let [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
target = {r: r, g: g, b: b, a: a};
};
let test = function (color, type) {
// hsla -> rgba -> hsla produces inaccurate results so we
// need some tolerence here.
let tolerance = 3;
clearCanvas();
setColor(color);
let [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
let rgbFail = Math.abs(r - target.r) > tolerance ||
Math.abs(g - target.g) > tolerance ||
Math.abs(b - target.b) > tolerance;
ok(!rgbFail, "color " + rgba + " matches target. Type: " + type);
if (rgbFail) {
info(`target: ${target.toSource()}, color: [r: ${r}, g: ${g}, b: ${b}, a: ${a}]`);
}
let alphaFail = a !== target.a;
ok(!alphaFail, "color " + rgba + " alpha value matches target.");
};
setTargetColor();
test(name, "name");
test(hex, "hex");
test(hsl, "hsl");
test(rgb, "rgb");
}
function testSetAlpha() {
let values = [
["longhex", "#ff0000", 0.5, "rgba(255, 0, 0, 0.5)"],
["hex", "#f0f", 0.2, "rgba(255, 0, 255, 0.2)"],
["rgba", "rgba(120, 34, 23, 1)", 0.25, "rgba(120, 34, 23, 0.25)"],
["rgb", "rgb(120, 34, 23)", 0.25, "rgba(120, 34, 23, 0.25)"],
["hsl", "hsl(208, 100%, 97%)", 0.75, "rgba(240, 248, 255, 0.75)"],
["hsla", "hsla(208, 100%, 97%, 1)", 0.75, "rgba(240, 248, 255, 0.75)"],
["alphahex", "#f08f", 0.6, "rgba(255, 0, 136, 0.6)"],
["longalphahex", "#00ff80ff", 0.2, "rgba(0, 255, 128, 0.2)"]
];
values.forEach(([type, value, alpha, expected]) => {
is(colorUtils.setAlpha(value, alpha), expected,
"correctly sets alpha value for " + type);
});
try {
colorUtils.setAlpha("rgb(24, 25%, 45, 1)", 1);
ok(false, "Should fail when passing in an invalid color.");
} catch (e) {
ok(true, "Fails when setAlpha receives an invalid color.");
}
is(colorUtils.setAlpha("#fff"), "rgba(255, 255, 255, 1)",
"sets alpha to 1 if invalid.");
}

View file

@ -0,0 +1,38 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the CubicBezierWidget generates content in a given parent node
const {CubicBezierWidget} =
require("devtools/client/shared/widgets/CubicBezierWidget");
const TEST_URI = `data:text/html,<div id="cubic-bezier-container" />`;
add_task(function* () {
let [host,, doc] = yield createHost("bottom", TEST_URI);
info("Checking that the graph markup is created in the parent");
let container = doc.querySelector("#cubic-bezier-container");
let w = new CubicBezierWidget(container);
ok(container.querySelector(".display-wrap"),
"The display has been added");
ok(container.querySelector(".coordinate-plane"),
"The coordinate plane has been added");
let buttons = container.querySelectorAll("button");
is(buttons.length, 2,
"The 2 control points have been added");
is(buttons[0].className, "control-point");
is(buttons[1].className, "control-point");
ok(container.querySelector("canvas"), "The curve canvas has been added");
info("Destroying the widget");
w.destroy();
is(container.children.length, 0, "All nodes have been removed");
host.destroy();
});

View file

@ -0,0 +1,200 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the CubicBezierWidget events
const {CubicBezierWidget} =
require("devtools/client/shared/widgets/CubicBezierWidget");
const {PREDEFINED} = require("devtools/client/shared/widgets/CubicBezierPresets");
// In this test we have to use a slightly more complete HTML tree, with <body>
// in order to remove its margin and prevent shifted positions
const TEST_URI = `data:text/html,
<html><body>
<div id="cubic-bezier-container"/>
</body></html>`;
add_task(function* () {
let [host, win, doc] = yield createHost("bottom", TEST_URI);
// Required or widget will be clipped inside of 'bottom'
// host by -14. Setting `fixed` zeroes this which is needed for
// calculating offsets. Occurs in test env only.
doc.body.setAttribute("style", "position: fixed; margin: 0;");
let container = doc.querySelector("#cubic-bezier-container");
let w = new CubicBezierWidget(container, PREDEFINED.linear);
let rect = w.curve.getBoundingClientRect();
rect.graphTop = rect.height * w.bezierCanvas.padding[0];
rect.graphBottom = rect.height - rect.graphTop;
rect.graphHeight = rect.graphBottom - rect.graphTop;
yield pointsCanBeDragged(w, win, doc, rect);
yield curveCanBeClicked(w, win, doc, rect);
yield pointsCanBeMovedWithKeyboard(w, win, doc, rect);
w.destroy();
host.destroy();
});
function* pointsCanBeDragged(widget, win, doc, offsets) {
info("Checking that the control points can be dragged with the mouse");
info("Listening for the update event");
let onUpdated = widget.once("updated");
info("Generating a mousedown/move/up on P1");
widget._onPointMouseDown({target: widget.p1});
doc.onmousemove({pageX: offsets.left, pageY: offsets.graphTop});
doc.onmouseup();
let bezier = yield onUpdated;
ok(true, "The widget fired the updated event");
ok(bezier, "The updated event contains a bezier argument");
is(bezier.P1[0], 0, "The new P1 time coordinate is correct");
is(bezier.P1[1], 1, "The new P1 progress coordinate is correct");
info("Listening for the update event");
onUpdated = widget.once("updated");
info("Generating a mousedown/move/up on P2");
widget._onPointMouseDown({target: widget.p2});
doc.onmousemove({pageX: offsets.right, pageY: offsets.graphBottom});
doc.onmouseup();
bezier = yield onUpdated;
is(bezier.P2[0], 1, "The new P2 time coordinate is correct");
is(bezier.P2[1], 0, "The new P2 progress coordinate is correct");
}
function* curveCanBeClicked(widget, win, doc, offsets) {
info("Checking that clicking on the curve moves the closest control point");
info("Listening for the update event");
let onUpdated = widget.once("updated");
info("Click close to P1");
let x = offsets.left + (offsets.width / 4.0);
let y = offsets.graphTop + (offsets.graphHeight / 4.0);
widget._onCurveClick({pageX: x, pageY: y});
let bezier = yield onUpdated;
ok(true, "The widget fired the updated event");
is(bezier.P1[0], 0.25, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
is(bezier.P2[0], 1, "P2 time coordinate remained unchanged");
is(bezier.P2[1], 0, "P2 progress coordinate remained unchanged");
info("Listening for the update event");
onUpdated = widget.once("updated");
info("Click close to P2");
x = offsets.right - (offsets.width / 4);
y = offsets.graphBottom - (offsets.graphHeight / 4);
widget._onCurveClick({pageX: x, pageY: y});
bezier = yield onUpdated;
is(bezier.P2[0], 0.75, "The new P2 time coordinate is correct");
is(bezier.P2[1], 0.25, "The new P2 progress coordinate is correct");
is(bezier.P1[0], 0.25, "P1 time coordinate remained unchanged");
is(bezier.P1[1], 0.75, "P1 progress coordinate remained unchanged");
}
function* pointsCanBeMovedWithKeyboard(widget, win, doc, offsets) {
info("Checking that points respond to keyboard events");
let singleStep = 3;
let shiftStep = 30;
info("Moving P1 to the left");
let newOffset = parseInt(widget.p1.style.left, 10) - singleStep;
let x = widget.bezierCanvas
.offsetsToCoordinates({style: {left: newOffset}})[0];
let onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 37));
let bezier = yield onUpdated;
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
info("Moving P1 to the left, fast");
newOffset = parseInt(widget.p1.style.left, 10) - shiftStep;
x = widget.bezierCanvas
.offsetsToCoordinates({style: {left: newOffset}})[0];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 37, true));
bezier = yield onUpdated;
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
info("Moving P1 to the right, fast");
newOffset = parseInt(widget.p1.style.left, 10) + shiftStep;
x = widget.bezierCanvas
.offsetsToCoordinates({style: {left: newOffset}})[0];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 39, true));
bezier = yield onUpdated;
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
info("Moving P1 to the bottom");
newOffset = parseInt(widget.p1.style.top, 10) + singleStep;
let y = widget.bezierCanvas
.offsetsToCoordinates({style: {top: newOffset}})[1];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 40));
bezier = yield onUpdated;
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], y, "The new P1 progress coordinate is correct");
info("Moving P1 to the bottom, fast");
newOffset = parseInt(widget.p1.style.top, 10) + shiftStep;
y = widget.bezierCanvas
.offsetsToCoordinates({style: {top: newOffset}})[1];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 40, true));
bezier = yield onUpdated;
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], y, "The new P1 progress coordinate is correct");
info("Moving P1 to the top, fast");
newOffset = parseInt(widget.p1.style.top, 10) - shiftStep;
y = widget.bezierCanvas
.offsetsToCoordinates({style: {top: newOffset}})[1];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p1, 38, true));
bezier = yield onUpdated;
is(bezier.P1[0], x, "The new P1 time coordinate is correct");
is(bezier.P1[1], y, "The new P1 progress coordinate is correct");
info("Checking that keyboard events also work with P2");
info("Moving P2 to the left");
newOffset = parseInt(widget.p2.style.left, 10) - singleStep;
x = widget.bezierCanvas
.offsetsToCoordinates({style: {left: newOffset}})[0];
onUpdated = widget.once("updated");
widget._onPointKeyDown(getKeyEvent(widget.p2, 37));
bezier = yield onUpdated;
is(bezier.P2[0], x, "The new P2 time coordinate is correct");
is(bezier.P2[1], 0.25, "The new P2 progress coordinate is correct");
}
function getKeyEvent(target, keyCode, shift = false) {
return {
target: target,
keyCode: keyCode,
shiftKey: shift,
preventDefault: () => {}
};
}

View file

@ -0,0 +1,68 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that coordinates can be changed programatically in the CubicBezierWidget
const {CubicBezierWidget} =
require("devtools/client/shared/widgets/CubicBezierWidget");
const {PREDEFINED} = require("devtools/client/shared/widgets/CubicBezierPresets");
const TEST_URI = `data:text/html,<div id="cubic-bezier-container" />`;
add_task(function* () {
let [host,, doc] = yield createHost("bottom", TEST_URI);
let container = doc.querySelector("#cubic-bezier-container");
let w = new CubicBezierWidget(container, PREDEFINED.linear);
yield coordinatesCanBeChangedByProvidingAnArray(w);
yield coordinatesCanBeChangedByProvidingAValue(w);
w.destroy();
host.destroy();
});
function* coordinatesCanBeChangedByProvidingAnArray(widget) {
info("Listening for the update event");
let onUpdated = widget.once("updated");
info("Setting new coordinates");
widget.coordinates = [0, 1, 1, 0];
let bezier = yield onUpdated;
ok(true, "The updated event was fired as a result of setting coordinates");
is(bezier.P1[0], 0, "The new P1 time coordinate is correct");
is(bezier.P1[1], 1, "The new P1 progress coordinate is correct");
is(bezier.P2[0], 1, "The new P2 time coordinate is correct");
is(bezier.P2[1], 0, "The new P2 progress coordinate is correct");
}
function* coordinatesCanBeChangedByProvidingAValue(widget) {
info("Listening for the update event");
let onUpdated = widget.once("updated");
info("Setting linear css value");
widget.cssCubicBezierValue = "linear";
let bezier = yield onUpdated;
ok(true, "The updated event was fired as a result of setting cssValue");
is(bezier.P1[0], 0, "The new P1 time coordinate is correct");
is(bezier.P1[1], 0, "The new P1 progress coordinate is correct");
is(bezier.P2[0], 1, "The new P2 time coordinate is correct");
is(bezier.P2[1], 1, "The new P2 progress coordinate is correct");
info("Setting a custom cubic-bezier css value");
onUpdated = widget.once("updated");
widget.cssCubicBezierValue = "cubic-bezier(.25,-0.5, 1, 1.25)";
bezier = yield onUpdated;
ok(true, "The updated event was fired as a result of setting cssValue");
is(bezier.P1[0], .25, "The new P1 time coordinate is correct");
is(bezier.P1[1], -.5, "The new P1 progress coordinate is correct");
is(bezier.P2[0], 1, "The new P2 time coordinate is correct");
is(bezier.P2[1], 1.25, "The new P2 progress coordinate is correct");
}

View file

@ -0,0 +1,50 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the CubicBezierPresetWidget generates markup.
const {CubicBezierPresetWidget} =
require("devtools/client/shared/widgets/CubicBezierWidget");
const {PRESETS} = require("devtools/client/shared/widgets/CubicBezierPresets");
const TEST_URI = `data:text/html,<div id="cubic-bezier-container" />`;
add_task(function* () {
let [host,, doc] = yield createHost("bottom", TEST_URI);
let container = doc.querySelector("#cubic-bezier-container");
let w = new CubicBezierPresetWidget(container);
info("Checking that the presets are created in the parent");
ok(container.querySelector(".preset-pane"),
"The preset pane has been added");
ok(container.querySelector("#preset-categories"),
"The preset categories have been added");
let categories = container.querySelectorAll(".category");
is(categories.length, Object.keys(PRESETS).length,
"The preset categories have been added");
Object.keys(PRESETS).forEach(category => {
ok(container.querySelector("#" + category), `${category} has been added`);
ok(container.querySelector("#preset-category-" + category),
`The preset list for ${category} has been added.`);
});
info("Checking that each of the presets and its preview have been added");
Object.keys(PRESETS).forEach(category => {
Object.keys(PRESETS[category]).forEach(presetLabel => {
let preset = container.querySelector("#" + presetLabel);
ok(preset, `${presetLabel} has been added`);
ok(preset.querySelector("canvas"),
`${presetLabel}'s canvas preview has been added`);
ok(preset.querySelector("p"),
`${presetLabel}'s label has been added`);
});
});
w.destroy();
host.destroy();
});

View file

@ -0,0 +1,48 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the CubicBezierPresetWidget cycles menus
const {CubicBezierPresetWidget} =
require("devtools/client/shared/widgets/CubicBezierWidget");
const {PREDEFINED, PRESETS, DEFAULT_PRESET_CATEGORY} =
require("devtools/client/shared/widgets/CubicBezierPresets");
const TEST_URI = `data:text/html,<div id="cubic-bezier-container" />`;
add_task(function* () {
let [host,, doc] = yield createHost("bottom", TEST_URI);
let container = doc.querySelector("#cubic-bezier-container");
let w = new CubicBezierPresetWidget(container);
info("Checking that preset is selected if coordinates are known");
w.refreshMenu([0, 0, 0, 0]);
is(w.activeCategory, container.querySelector(`#${DEFAULT_PRESET_CATEGORY}`),
"The default category is selected");
is(w._activePreset, null, "There is no selected category");
w.refreshMenu(PREDEFINED.linear);
is(w.activeCategory, container.querySelector("#ease-in-out"),
"The ease-in-out category is active");
is(w._activePreset, container.querySelector("#ease-in-out-linear"),
"The ease-in-out-linear preset is active");
w.refreshMenu(PRESETS["ease-out"]["ease-out-sine"]);
is(w.activeCategory, container.querySelector("#ease-out"),
"The ease-out category is active");
is(w._activePreset, container.querySelector("#ease-out-sine"),
"The ease-out-sine preset is active");
w.refreshMenu([0, 0, 0, 0]);
is(w.activeCategory, container.querySelector("#ease-out"),
"The ease-out category is still active");
is(w._activePreset, null, "No preset is active");
w.destroy();
host.destroy();
});

View file

@ -0,0 +1,79 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the integration between CubicBezierWidget and CubicBezierPresets
const {CubicBezierWidget} =
require("devtools/client/shared/widgets/CubicBezierWidget");
const {PRESETS} = require("devtools/client/shared/widgets/CubicBezierPresets");
const TEST_URI = `data:text/html,<div id="cubic-bezier-container" />`;
add_task(function* () {
let [host, win, doc] = yield createHost("bottom", TEST_URI);
let container = doc.querySelector("#cubic-bezier-container");
let w = new CubicBezierWidget(container,
PRESETS["ease-in"]["ease-in-sine"]);
w.presets.refreshMenu(PRESETS["ease-in"]["ease-in-sine"]);
let rect = w.curve.getBoundingClientRect();
rect.graphTop = rect.height * w.bezierCanvas.padding[0];
yield adjustingBezierUpdatesPreset(w, win, doc, rect);
yield selectingPresetUpdatesBezier(w, win, doc, rect);
w.destroy();
host.destroy();
});
function* adjustingBezierUpdatesPreset(widget, win, doc, rect) {
info("Checking that changing the bezier refreshes the preset menu");
is(widget.presets.activeCategory,
doc.querySelector("#ease-in"),
"The selected category is ease-in");
is(widget.presets._activePreset,
doc.querySelector("#ease-in-sine"),
"The selected preset is ease-in-sine");
info("Generating custom bezier curve by dragging");
widget._onPointMouseDown({target: widget.p1});
doc.onmousemove({pageX: rect.left, pageY: rect.graphTop});
doc.onmouseup();
is(widget.presets.activeCategory,
doc.querySelector("#ease-in"),
"The selected category is still ease-in");
is(widget.presets._activePreset, null,
"There is no active preset");
}
function* selectingPresetUpdatesBezier(widget, win, doc, rect) {
info("Checking that selecting a preset updates bezier curve");
info("Listening for the new coordinates event");
let onNewCoordinates = widget.presets.once("new-coordinates");
let onUpdated = widget.once("updated");
info("Click a preset");
let preset = doc.querySelector("#ease-in-sine");
widget.presets._onPresetClick({currentTarget: preset});
yield onNewCoordinates;
ok(true, "The preset widget fired the new-coordinates event");
let bezier = yield onUpdated;
ok(true, "The bezier canvas fired the updated event");
is(bezier.P1[0], preset.coordinates[0], "The new P1 time coordinate is correct");
is(bezier.P1[1], preset.coordinates[1], "The new P1 progress coordinate is correct");
is(bezier.P2[0], preset.coordinates[2], "P2 time coordinate is correct ");
is(bezier.P2[1], preset.coordinates[3], "P2 progress coordinate is correct");
}

View file

@ -0,0 +1,57 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {
getDevices,
getDeviceString,
addDevice
} = require("devtools/client/shared/devices");
add_task(function* () {
Services.prefs.setCharPref("devtools.devices.url",
TEST_URI_ROOT + "browser_devices.json");
let devices = yield getDevices();
is(devices.TYPES.length, 1, "Found 1 device type.");
let type1 = devices.TYPES[0];
is(devices[type1].length, 2, "Found 2 devices of type #1.");
let string = getDeviceString(type1);
ok(typeof string === "string" && string.length > 0, "Able to localize type #1.");
let device1 = {
name: "SquarePhone",
width: 320,
height: 320,
pixelRatio: 2,
userAgent: "Mozilla/5.0 (Mobile; rv:42.0)",
touch: true,
firefoxOS: true
};
addDevice(device1, type1);
devices = yield getDevices();
is(devices[type1].length, 3, "Added new device of type #1.");
ok(devices[type1].filter(d => d.name === device1.name), "Found the new device.");
let type2 = "appliances";
let device2 = {
name: "Mr Freezer",
width: 800,
height: 600,
pixelRatio: 5,
userAgent: "Mozilla/5.0 (Appliance; rv:42.0)",
touch: true,
firefoxOS: true
};
addDevice(device2, type2);
devices = yield getDevices();
is(devices.TYPES.length, 2, "Added device type #2.");
is(devices[type2].length, 1, "Added new device of type #2.");
});

View file

@ -0,0 +1,23 @@
{
"TYPES": [ "phones" ],
"phones": [
{
"name": "Small Phone",
"width": 320,
"height": 480,
"pixelRatio": 1,
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
"touch": true,
"firefoxOS": true
},
{
"name": "Big Phone",
"width": 360,
"height": 640,
"pixelRatio": 3,
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
"touch": true,
"firefoxOS": true
}
]
}

View file

@ -0,0 +1,114 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the Filter Editor Widget parses filter values correctly (setCssValue)
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const DOMUtils =
Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
const TEST_URI = `data:text/html,<div id="filter-container" />`;
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
// Verify that the given string consists of a valid CSS URL token.
// Return true on success, false on error.
function verifyURL(string) {
let lexer = DOMUtils.getCSSLexer(string);
let token = lexer.nextToken();
if (!token || token.tokenType !== "url") {
return false;
}
return lexer.nextToken() === null;
}
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
let widget = new CSSFilterEditorWidget(container, "none", cssIsValid);
info("Test parsing of a valid CSS Filter value");
widget.setCssValue("blur(2px) contrast(200%)");
is(widget.getCssValue(),
"blur(2px) contrast(200%)",
"setCssValue should work for computed values");
info("Test parsing of space-filled value");
widget.setCssValue("blur( 2px ) contrast( 2 )");
is(widget.getCssValue(),
"blur(2px) contrast(200%)",
"setCssValue should work for spaced values");
info("Test parsing of string-typed values");
widget.setCssValue("drop-shadow( 2px 1px 5px black) url( example.svg#filter )");
is(widget.getCssValue(),
"drop-shadow(2px 1px 5px black) url(example.svg#filter)",
"setCssValue should work for string-typed values");
info("Test parsing of mixed-case function names");
widget.setCssValue("BLUR(2px) Contrast(200%) Drop-Shadow(2px 1px 5px Black)");
is(widget.getCssValue(),
"BLUR(2px) Contrast(200%) Drop-Shadow(2px 1px 5px Black)",
"setCssValue should work for mixed-case function names");
info("Test parsing of invalid filter value");
widget.setCssValue("totallyinvalid");
is(widget.getCssValue(), "none",
"setCssValue should turn completely invalid value to 'none'");
info("Test parsing of invalid function argument");
widget.setCssValue("blur('hello')");
is(widget.getCssValue(), "blur(0px)",
"setCssValue should replace invalid function argument with default");
info("Test parsing of invalid function argument #2");
widget.setCssValue("drop-shadow(whatever)");
is(widget.getCssValue(), "drop-shadow()",
"setCssValue should replace invalid drop-shadow argument with empty string");
info("Test parsing of mixed invalid argument");
widget.setCssValue("contrast(5%) whatever invert('xxx')");
is(widget.getCssValue(), "contrast(5%) invert(0%)",
"setCssValue should handle multiple errors");
info("Test parsing of 'unset'");
widget.setCssValue("unset");
is(widget.getCssValue(), "unset", "setCssValue should handle 'unset'");
info("Test parsing of 'initial'");
widget.setCssValue("initial");
is(widget.getCssValue(), "initial", "setCssValue should handle 'initial'");
info("Test parsing of 'inherit'");
widget.setCssValue("inherit");
is(widget.getCssValue(), "inherit", "setCssValue should handle 'inherit'");
info("Test parsing of quoted URL");
widget.setCssValue("url('invalid ) when ) unquoted')");
is(widget.getCssValue(), "url('invalid ) when ) unquoted')",
"setCssValue should re-quote single-quoted URL contents");
widget.setCssValue("url(\"invalid ) when ) unquoted\")");
is(widget.getCssValue(), "url(\"invalid ) when ) unquoted\")",
"setCssValue should re-quote double-quoted URL contents");
widget.setCssValue("url(ordinary)");
is(widget.getCssValue(), "url(ordinary)",
"setCssValue should not quote ordinary unquoted URL contents");
let quotedurl =
"url(invalid\\ \\)\\ {\\\twhen\\ }\\ ;\\ \\\\unquoted\\'\\\")";
ok(verifyURL(quotedurl), "weird URL is valid");
widget.setCssValue(quotedurl);
is(widget.getCssValue(), quotedurl,
"setCssValue should re-quote weird unquoted URL contents");
let dataurl = "url(data:image/svg+xml;utf8,<svg\\ " +
"xmlns=\\\"http://www.w3.org/2000/svg\\\"><filter\\ id=\\\"blur\\\">" +
"<feGaussianBlur\\ stdDeviation=\\\"3\\\"/></filter></svg>#blur)";
ok(verifyURL(dataurl), "data URL is valid");
widget.setCssValue(dataurl);
is(widget.getCssValue(), dataurl, "setCssValue should not mangle data urls");
});

View file

@ -0,0 +1,107 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the Filter Editor Widget renders filters correctly
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const { LocalizationHelper } = require("devtools/shared/l10n");
const STRINGS_URI = "devtools/client/locales/filterwidget.properties";
const L10N = new LocalizationHelper(STRINGS_URI);
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const TEST_DATA = [
{
cssValue: "blur(2px) contrast(200%) hue-rotate(20.2deg) drop-shadow(5px 5px black)",
expected: [
{
label: "blur",
value: "2",
unit: "px"
},
{
label: "contrast",
value: "200",
unit: "%"
},
{
label: "hue-rotate",
value: "20.2",
unit: "deg"
},
{
label: "drop-shadow",
value: "5px 5px black",
unit: null
}
]
},
{
cssValue: "hue-rotate(420.2deg)",
expected: [
{
label: "hue-rotate",
value: "420.2",
unit: "deg"
}
]
},
{
cssValue: "url(example.svg)",
expected: [
{
label: "url",
value: "example.svg",
unit: null
}
]
},
{
cssValue: "none",
expected: []
}
];
const container = doc.querySelector("#filter-container");
let widget = new CSSFilterEditorWidget(container, "none", cssIsValid);
info("Test rendering of different types");
for (let {cssValue, expected} of TEST_DATA) {
widget.setCssValue(cssValue);
if (cssValue === "none") {
const text = container.querySelector("#filters").textContent;
ok(text.indexOf(L10N.getStr("emptyFilterList")) > -1,
"Contains |emptyFilterList| string when given value 'none'");
ok(text.indexOf(L10N.getStr("addUsingList")) > -1,
"Contains |addUsingList| string when given value 'none'");
continue;
}
const filters = container.querySelectorAll(".filter");
testRenderedFilters(filters, expected);
}
});
function testRenderedFilters(filters, expected) {
for (let [index, filter] of [...filters].entries()) {
let [name, value] = filter.children,
label = name.children[1],
[input, unit] = value.children;
const eq = expected[index];
is(label.textContent, eq.label, "Label should match");
is(input.value, eq.value, "Values should match");
if (eq.unit) {
is(unit.textContent, eq.unit, "Unit should match");
}
}
}

View file

@ -0,0 +1,65 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Filter Editor Widget add, removeAt, updateAt, getValueAt methods
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const GRAYSCALE_MAX = 100;
const INVERT_MIN = 0;
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
let widget = new CSSFilterEditorWidget(container, "none", cssIsValid);
info("Test add method");
const blur = widget.add("blur", "10.2px");
is(widget.getCssValue(), "blur(10.2px)",
"Should add filters");
const url = widget.add("url", "test.svg");
is(widget.getCssValue(), "blur(10.2px) url(test.svg)",
"Should add filters in order");
info("Test updateValueAt method");
widget.updateValueAt(url, "test2.svg");
widget.updateValueAt(blur, 5);
is(widget.getCssValue(), "blur(5px) url(test2.svg)",
"Should update values correctly");
info("Test getValueAt method");
is(widget.getValueAt(blur), "5px",
"Should return value + unit");
is(widget.getValueAt(url), "test2.svg",
"Should return value for string-type filters");
info("Test removeAt method");
widget.removeAt(url);
is(widget.getCssValue(), "blur(5px)",
"Should remove the specified filter");
info("Test add method applying filter range to value");
const grayscale = widget.add("grayscale", GRAYSCALE_MAX + 1);
is(widget.getValueAt(grayscale), `${GRAYSCALE_MAX}%`,
"Shouldn't allow values higher than max");
const invert = widget.add("invert", INVERT_MIN - 1);
is(widget.getValueAt(invert), `${INVERT_MIN}%`,
"Shouldn't allow values less than INVERT_MIN");
info("Test updateValueAt method applying filter range to value");
widget.updateValueAt(grayscale, GRAYSCALE_MAX + 1);
is(widget.getValueAt(grayscale), `${GRAYSCALE_MAX}%`,
"Shouldn't allow values higher than max");
widget.updateValueAt(invert, INVERT_MIN - 1);
is(widget.getValueAt(invert), `${INVERT_MIN}%`,
"Shouldn't allow values less than INVERT_MIN");
});

View file

@ -0,0 +1,87 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Filter Editor Widget's drag-drop re-ordering
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const LIST_ITEM_HEIGHT = 32;
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
const initialValue = "blur(2px) contrast(200%) brightness(200%)";
let widget = new CSSFilterEditorWidget(container, initialValue, cssIsValid);
const filters = widget.el.querySelector("#filters");
function first() {
return filters.children[0];
}
function mid() {
return filters.children[1];
}
function last() {
return filters.children[2];
}
info("Test re-ordering neighbour filters");
widget._mouseDown({
target: first().querySelector("i"),
pageY: 0
});
widget._mouseMove({ pageY: LIST_ITEM_HEIGHT });
// Element re-ordering should be instant
is(mid().querySelector("label").textContent, "blur",
"Should reorder elements correctly");
widget._mouseUp();
is(widget.getCssValue(), "contrast(200%) blur(2px) brightness(200%)",
"Should reorder filters objects correctly");
info("Test re-ordering first and last filters");
widget._mouseDown({
target: first().querySelector("i"),
pageY: 0
});
widget._mouseMove({ pageY: LIST_ITEM_HEIGHT * 2 });
// Element re-ordering should be instant
is(last().querySelector("label").textContent, "contrast",
"Should reorder elements correctly");
widget._mouseUp();
is(widget.getCssValue(), "brightness(200%) blur(2px) contrast(200%)",
"Should reorder filters objects correctly");
info("Test dragging first element out of list");
const boundaries = filters.getBoundingClientRect();
widget._mouseDown({
target: first().querySelector("i"),
pageY: 0
});
widget._mouseMove({ pageY: -LIST_ITEM_HEIGHT * 5 });
ok(first().getBoundingClientRect().top >= boundaries.top,
"First filter should not move outside filter list");
widget._mouseUp();
info("Test dragging last element out of list");
widget._mouseDown({
target: last().querySelector("i"),
pageY: 0
});
widget._mouseMove({ pageY: -LIST_ITEM_HEIGHT * 5 });
ok(last().getBoundingClientRect().bottom <= boundaries.bottom,
"Last filter should not move outside filter list");
widget._mouseUp();
});

View file

@ -0,0 +1,148 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
requestLongerTimeout(2);
// Tests the Filter Editor Widget's label-dragging
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const FAST_VALUE_MULTIPLIER = 10;
const SLOW_VALUE_MULTIPLIER = 0.1;
const DEFAULT_VALUE_MULTIPLIER = 1;
const GRAYSCALE_MAX = 100,
GRAYSCALE_MIN = 0;
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
let widget = new CSSFilterEditorWidget(
container, "grayscale(0%) url(test.svg)", cssIsValid
);
const filters = widget.el.querySelector("#filters");
const grayscale = filters.children[0];
const url = filters.children[1];
info("Test label-dragging on number-type filters without modifiers");
widget._mouseDown({
target: grayscale.querySelector("label"),
pageX: 0,
altKey: false,
shiftKey: false
});
widget._mouseMove({
pageX: 12,
altKey: false,
shiftKey: false
});
let expected = DEFAULT_VALUE_MULTIPLIER * 12;
is(widget.getValueAt(0),
`${expected}%`,
"Should update value correctly without modifiers");
info("Test label-dragging on number-type filters with alt");
widget._mouseMove({
// 20 - 12 = 8
pageX: 20,
altKey: true,
shiftKey: false
});
expected = expected + SLOW_VALUE_MULTIPLIER * 8;
is(widget.getValueAt(0),
`${expected}%`,
"Should update value correctly with alt key");
info("Test label-dragging on number-type filters with shift");
widget._mouseMove({
// 25 - 20 = 5
pageX: 25,
altKey: false,
shiftKey: true
});
expected = expected + FAST_VALUE_MULTIPLIER * 5;
is(widget.getValueAt(0),
`${expected}%`,
"Should update value correctly with shift key");
info("Test releasing mouse and dragging again");
widget._mouseUp();
widget._mouseDown({
target: grayscale.querySelector("label"),
pageX: 0,
altKey: false,
shiftKey: false
});
widget._mouseMove({
pageX: 5,
altKey: false,
shiftKey: false
});
expected = expected + DEFAULT_VALUE_MULTIPLIER * 5;
is(widget.getValueAt(0),
`${expected}%`,
"Should reset multiplier to default");
info("Test value ranges");
widget._mouseMove({
// 30 - 25 = 5
pageX: 30,
altKey: false,
shiftKey: true
});
expected = GRAYSCALE_MAX;
is(widget.getValueAt(0),
`${expected}%`,
"Shouldn't allow values higher than max");
widget._mouseMove({
pageX: -11,
altKey: false,
shiftKey: true
});
expected = GRAYSCALE_MIN;
is(widget.getValueAt(0),
`${expected}%`,
"Shouldn't allow values less than min");
widget._mouseUp();
info("Test label-dragging on string-type filters");
widget._mouseDown({
target: url.querySelector("label"),
pageX: 0,
altKey: false,
shiftKey: false
});
ok(!widget.isDraggingLabel,
"Label-dragging should not work for string-type filters");
widget._mouseMove({
pageX: -11,
altKey: false,
shiftKey: true
});
is(widget.getValueAt(1),
"test.svg",
"Label-dragging on string-type filters shouldn't affect their value");
});

View file

@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Filter Editor Widget's add button
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const { LocalizationHelper } = require("devtools/shared/l10n");
const STRINGS_URI = "devtools/client/locales/filterwidget.properties";
const L10N = new LocalizationHelper(STRINGS_URI);
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
let widget = new CSSFilterEditorWidget(container, "none", cssIsValid);
const select = widget.el.querySelector("select"),
add = widget.el.querySelector("#add-filter");
const TEST_DATA = [
{
name: "blur",
unit: "px",
type: "length"
},
{
name: "contrast",
unit: "%",
type: "percentage"
},
{
name: "hue-rotate",
unit: "deg",
type: "angle"
},
{
name: "drop-shadow",
placeholder: L10N.getStr("dropShadowPlaceholder"),
type: "string"
},
{
name: "url",
placeholder: "example.svg#c1",
type: "string"
}
];
info("Test adding new filters with different units");
for (let [index, filter] of TEST_DATA.entries()) {
select.value = filter.name;
add.click();
if (filter.unit) {
is(widget.getValueAt(index), `0${filter.unit}`,
`Should add ${filter.unit} to ${filter.type} filters`);
} else if (filter.placeholder) {
let i = index + 1;
const input = widget.el.querySelector(`.filter:nth-child(${i}) input`);
is(input.placeholder, filter.placeholder,
"Should set the appropriate placeholder for string-type filters");
}
}
});

View file

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Filter Editor Widget's remove button
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
let widget = new CSSFilterEditorWidget(
container, "blur(2px) contrast(200%)", cssIsValid
);
info("Test removing filters with remove button");
widget.el.querySelector(".filter button").click();
is(widget.getCssValue(), "contrast(200%)",
"Should remove the clicked filter");
});

View file

@ -0,0 +1,84 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Filter Editor Widget inputs increase/decrease value using
// arrow keys, applying multiplier using alt/shift on number-type filters
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const FAST_VALUE_MULTIPLIER = 10;
const SLOW_VALUE_MULTIPLIER = 0.1;
const DEFAULT_VALUE_MULTIPLIER = 1;
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
const initialValue = "blur(2px)";
let widget = new CSSFilterEditorWidget(container, initialValue, cssIsValid);
let value = 2;
triggerKey = triggerKey.bind(widget);
info("Test simple arrow keys");
triggerKey(40);
value -= DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should decrease value using down arrow");
triggerKey(38);
value += DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should decrease value using down arrow");
info("Test shift key multiplier");
triggerKey(38, "shiftKey");
value += FAST_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should increase value by fast multiplier using up arrow");
triggerKey(40, "shiftKey");
value -= FAST_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should decrease value by fast multiplier using down arrow");
info("Test alt key multiplier");
triggerKey(38, "altKey");
value += SLOW_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should increase value by slow multiplier using up arrow");
triggerKey(40, "altKey");
value -= SLOW_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should decrease value by slow multiplier using down arrow");
triggerKey = null;
});
// Triggers the specified keyCode and modifier key on
// first filter's input
function triggerKey(key, modifier) {
const filter = this.el.querySelector("#filters").children[0];
const input = filter.querySelector("input");
this._keyDown({
target: input,
keyCode: key,
[modifier]: true,
preventDefault: function () {}
});
}

View file

@ -0,0 +1,125 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Filter Editor Widget inputs increase/decrease value when cursor is
// on a number using arrow keys, applying multiplier using alt/shift on strings
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const FAST_VALUE_MULTIPLIER = 10;
const SLOW_VALUE_MULTIPLIER = 0.1;
const DEFAULT_VALUE_MULTIPLIER = 1;
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
const initialValue = "drop-shadow(rgb(0, 0, 0) 1px 1px 0px)";
let widget = new CSSFilterEditorWidget(container, initialValue, cssIsValid);
widget.el.querySelector("#filters input").setSelectionRange(13, 13);
let value = 1;
triggerKey = triggerKey.bind(widget);
info("Test simple arrow keys");
triggerKey(40);
value -= DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease value using down arrow");
triggerKey(38);
value += DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease value using down arrow");
info("Test shift key multiplier");
triggerKey(38, "shiftKey");
value += FAST_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should increase value by fast multiplier using up arrow");
triggerKey(40, "shiftKey");
value -= FAST_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease value by fast multiplier using down arrow");
info("Test alt key multiplier");
triggerKey(38, "altKey");
value += SLOW_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should increase value by slow multiplier using up arrow");
triggerKey(40, "altKey");
value -= SLOW_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease value by slow multiplier using down arrow");
triggerKey(40, "shiftKey");
value -= FAST_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease to negative");
triggerKey(40);
value -= DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease negative numbers correctly");
triggerKey(38);
value += DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should increase negative values correctly");
triggerKey(40, "altKey");
triggerKey(40, "altKey");
value -= SLOW_VALUE_MULTIPLIER * 2;
is(widget.getValueAt(0), val(value),
"Should decrease float numbers correctly");
triggerKey(38, "altKey");
value += SLOW_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should increase float numbers correctly");
triggerKey = null;
});
// Triggers the specified keyCode and modifier key on
// first filter's input
function triggerKey(key, modifier) {
const filter = this.el.querySelector("#filters").children[0];
const input = filter.querySelector("input");
this._keyDown({
target: input,
keyCode: key,
[modifier]: true,
preventDefault: function () {}
});
}
function val(value) {
let v = value.toFixed(1);
if (v.indexOf(".0") > -1) {
v = v.slice(0, -2);
}
return `rgb(0, 0, 0) ${v}px 1px 0px`;
}

View file

@ -0,0 +1,87 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Filter Editor Widget inputs increase/decrease value when cursor is
// on a number using arrow keys if cursor is behind/mid/after the number strings
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const DEFAULT_VALUE_MULTIPLIER = 1;
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
const initialValue = "drop-shadow(rgb(0, 0, 0) 10px 1px 0px)";
let widget = new CSSFilterEditorWidget(container, initialValue, cssIsValid);
const input = widget.el.querySelector("#filters input");
let value = 10;
triggerKey = triggerKey.bind(widget);
info("Test increment/decrement of string-type numbers without selection");
input.setSelectionRange(14, 14);
triggerKey(40);
value -= DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should work with cursor in the middle of number");
input.setSelectionRange(13, 13);
triggerKey(38);
value += DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should work with cursor before the number");
input.setSelectionRange(15, 15);
triggerKey(40);
value -= DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should work with cursor after the number");
info("Test increment/decrement of string-type numbers with a selection");
input.setSelectionRange(13, 15);
triggerKey(38);
input.setSelectionRange(13, 18);
triggerKey(38);
value += DEFAULT_VALUE_MULTIPLIER * 2;
is(widget.getValueAt(0), val(value),
"Should work if a there is a selection, starting with the number");
triggerKey = null;
});
// Triggers the specified keyCode and modifier key on
// first filter's input
function triggerKey(key, modifier) {
const filter = this.el.querySelector("#filters").children[0];
const input = filter.querySelector("input");
this._keyDown({
target: input,
keyCode: key,
[modifier]: true,
preventDefault: function () {}
});
}
function val(value) {
let v = value.toFixed(1);
if (v.indexOf(".0") > -1) {
v = v.slice(0, -2);
}
return `rgb(0, 0, 0) ${v}px 1px 0px`;
}

View file

@ -0,0 +1,99 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests saving presets
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
let widget = new CSSFilterEditorWidget(container, "none", cssIsValid);
// First render
yield widget.once("render");
const VALUE = "blur(2px) contrast(150%)";
const NAME = "Test";
yield showFilterPopupPresetsAndCreatePreset(widget, NAME, VALUE);
let preset = widget.el.querySelector(".preset");
is(preset.querySelector("label").textContent, NAME,
"Should show preset name correctly");
is(preset.querySelector("span").textContent, VALUE,
"Should show preset value preview correctly");
let list = yield widget.getPresets();
let input = widget.el.querySelector(".presets-list .footer input");
let data = list[0];
is(data.name, NAME,
"Should add the preset to asyncStorage - name property");
is(data.value, VALUE,
"Should add the preset to asyncStorage - name property");
info("Test overriding preset by using the same name");
const VALUE_2 = "saturate(50%) brightness(10%)";
widget.setCssValue(VALUE_2);
yield savePreset(widget);
is(widget.el.querySelectorAll(".preset").length, 1,
"Should override the preset with the same name - render");
list = yield widget.getPresets();
data = list[0];
is(list.length, 1,
"Should override the preset with the same name - asyncStorage");
is(data.name, NAME,
"Should override the preset with the same name - prop name");
is(data.value, VALUE_2,
"Should override the preset with the same name - prop value");
yield widget.setPresets([]);
info("Test saving a preset without name");
input.value = "";
yield savePreset(widget, "preset-save-error");
list = yield widget.getPresets();
is(list.length, 0,
"Should not add a preset without name");
info("Test saving a preset without filters");
input.value = NAME;
widget.setCssValue("none");
yield savePreset(widget, "preset-save-error");
list = yield widget.getPresets();
is(list.length, 0,
"Should not add a preset without filters (value: none)");
});
/**
* Call savePreset on widget and wait for the specified event to emit
* @param {CSSFilterWidget} widget
* @param {string} expectEvent="render" The event to listen on
* @return {Promise}
*/
function savePreset(widget, expectEvent = "render") {
let onEvent = widget.once(expectEvent);
widget._savePreset({
preventDefault: () => {},
});
return onEvent;
}

View file

@ -0,0 +1,45 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests loading presets
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
let widget = new CSSFilterEditorWidget(container, "none", cssIsValid);
// First render
yield widget.once("render");
const VALUE = "blur(2px) contrast(150%)";
const NAME = "Test";
yield showFilterPopupPresetsAndCreatePreset(widget, NAME, VALUE);
let onRender = widget.once("render");
// reset value
widget.setCssValue("saturate(100%) brightness(150%)");
yield onRender;
let preset = widget.el.querySelector(".preset");
onRender = widget.once("render");
widget._presetClick({
target: preset
});
yield onRender;
is(widget.getCssValue(), VALUE,
"Should set widget's value correctly");
is(widget.el.querySelector(".presets-list .footer input").value, NAME,
"Should set input's value to name");
});

View file

@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests deleting presets
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {getClientCssProperties} = require("devtools/shared/fronts/css-properties");
const TEST_URI = `data:text/html,<div id="filter-container" />`;
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
const cssIsValid = getClientCssProperties().getValidityChecker(doc);
const container = doc.querySelector("#filter-container");
let widget = new CSSFilterEditorWidget(container, "none", cssIsValid);
// First render
yield widget.once("render");
const NAME = "Test";
const VALUE = "blur(2px) contrast(150%)";
yield showFilterPopupPresetsAndCreatePreset(widget, NAME, VALUE);
let removeButton = widget.el.querySelector(".preset .remove-button");
let onRender = widget.once("render");
widget._presetClick({
target: removeButton
});
yield onRender;
is(widget.el.querySelector(".preset"), null,
"Should re-render after removing preset");
let list = yield widget.getPresets();
is(list.length, 0,
"Should remove presets from asyncStorage");
});

View file

@ -0,0 +1,61 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that flame graph widget works properly.
const {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new FlameGraph(doc.body);
let readyEventEmitted;
graph.once("ready", () => {
readyEventEmitted = true;
});
yield graph.ready();
ok(readyEventEmitted, "The 'ready' event should have been emitted");
testGraph(host, graph);
yield graph.destroy();
host.destroy();
}
function testGraph(host, graph) {
ok(graph._container.classList.contains("flame-graph-widget-container"),
"The correct graph container was created.");
ok(graph._canvas.classList.contains("flame-graph-widget-canvas"),
"The correct graph container was created.");
let bounds = host.frame.getBoundingClientRect();
is(graph.width, bounds.width * window.devicePixelRatio,
"The graph has the correct width.");
is(graph.height, bounds.height * window.devicePixelRatio,
"The graph has the correct height.");
ok(graph._selection.start === null,
"The graph's selection start value is initially null.");
ok(graph._selection.end === null,
"The graph's selection end value is initially null.");
ok(graph._selectionDragger.origin === null,
"The graph's dragger origin value is initially null.");
ok(graph._selectionDragger.anchor.start === null,
"The graph's dragger anchor start value is initially null.");
ok(graph._selectionDragger.anchor.end === null,
"The graph's dragger anchor end value is initially null.");
}

View file

@ -0,0 +1,44 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that flame graph widgets may have a fixed width or height.
const {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new FlameGraph(doc.body);
graph.fixedWidth = 200;
graph.fixedHeight = 100;
yield graph.ready();
testGraph(host, graph);
yield graph.destroy();
host.destroy();
}
function testGraph(host, graph) {
let bounds = host.frame.getBoundingClientRect();
isnot(graph.width, bounds.width * window.devicePixelRatio,
"The graph should not span all the parent node's width.");
isnot(graph.height, bounds.height * window.devicePixelRatio,
"The graph should not span all the parent node's height.");
is(graph.width, graph.fixedWidth * window.devicePixelRatio,
"The graph has the correct width.");
is(graph.height, graph.fixedHeight * window.devicePixelRatio,
"The graph has the correct height.");
}

View file

@ -0,0 +1,138 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that selections in the flame graph widget work properly.
const TEST_DATA = [
{
color: "#f00",
blocks: [
{ x: 0, y: 0, width: 50, height: 20, text: "FOO" },
{ x: 50, y: 0, width: 100, height: 20, text: "BAR" }
]
},
{
color: "#00f",
blocks: [
{ x: 0, y: 30, width: 30, height: 20, text: "BAZ" }
]
}
];
const TEST_BOUNDS = { startTime: 0, endTime: 150 };
const TEST_WIDTH = 200;
const TEST_HEIGHT = 100;
const {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new FlameGraph(doc.body, 1);
graph.fixedWidth = TEST_WIDTH;
graph.fixedHeight = TEST_HEIGHT;
graph.horizontalPanThreshold = 0;
graph.verticalPanThreshold = 0;
yield graph.ready();
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.setData({ data: TEST_DATA, bounds: TEST_BOUNDS });
is(graph.getViewRange().startTime, 0,
"The selection start boundary is correct (1).");
is(graph.getViewRange().endTime, 150,
"The selection end boundary is correct (1).");
scroll(graph, 200, HORIZONTAL_AXIS, 10);
is(graph.getViewRange().startTime | 0, 75,
"The selection start boundary is correct (2).");
is(graph.getViewRange().endTime | 0, 150,
"The selection end boundary is correct (2).");
scroll(graph, -200, HORIZONTAL_AXIS, 10);
is(graph.getViewRange().startTime | 0, 37,
"The selection start boundary is correct (3).");
is(graph.getViewRange().endTime | 0, 112,
"The selection end boundary is correct (3).");
scroll(graph, 200, VERTICAL_AXIS, TEST_WIDTH / 2);
is(graph.getViewRange().startTime | 0, 34,
"The selection start boundary is correct (4).");
is(graph.getViewRange().endTime | 0, 115,
"The selection end boundary is correct (4).");
scroll(graph, -200, VERTICAL_AXIS, TEST_WIDTH / 2);
is(graph.getViewRange().startTime | 0, 37,
"The selection start boundary is correct (5).");
is(graph.getViewRange().endTime | 0, 112,
"The selection end boundary is correct (5).");
dragStart(graph, TEST_WIDTH / 2);
is(graph.getViewRange().startTime | 0, 37,
"The selection start boundary is correct (6).");
is(graph.getViewRange().endTime | 0, 112,
"The selection end boundary is correct (6).");
hover(graph, TEST_WIDTH / 2 - 10);
is(graph.getViewRange().startTime | 0, 41,
"The selection start boundary is correct (7).");
is(graph.getViewRange().endTime | 0, 116,
"The selection end boundary is correct (7).");
dragStop(graph, 10);
is(graph.getViewRange().startTime | 0, 71,
"The selection start boundary is correct (8).");
is(graph.getViewRange().endTime | 0, 145,
"The selection end boundary is correct (8).");
}
// EventUtils just doesn't work!
function hover(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
}
function dragStart(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
}
function dragStop(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}
var HORIZONTAL_AXIS = 1;
var VERTICAL_AXIS = 2;
function scroll(graph, wheel, axis, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseWheel({ testX: x, testY: y, axis, detail: wheel,
HORIZONTAL_AXIS,
VERTICAL_AXIS
});
}

View file

@ -0,0 +1,92 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that selections in the flame graph widget work properly on HiDPI.
const TEST_DATA = [
{
color: "#f00",
blocks: [
{ x: 0, y: 0, width: 50, height: 20, text: "FOO" },
{ x: 50, y: 0, width: 100, height: 20, text: "BAR" }
]
},
{
color: "#00f",
blocks: [
{ x: 0, y: 30, width: 30, height: 20, text: "BAZ" }
]
}
];
const TEST_BOUNDS = { startTime: 0, endTime: 150 };
const TEST_WIDTH = 200;
const TEST_HEIGHT = 100;
const TEST_DPI_DENSITIY = 2;
var {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new FlameGraph(doc.body, TEST_DPI_DENSITIY);
graph.fixedWidth = TEST_WIDTH;
graph.fixedHeight = TEST_HEIGHT;
yield graph.ready();
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.setData({ data: TEST_DATA, bounds: TEST_BOUNDS });
is(graph.getViewRange().startTime, 0,
"The selection start boundary is correct on HiDPI (1).");
is(graph.getViewRange().endTime, 150,
"The selection end boundary is correct on HiDPI (1).");
is(graph.getOuterBounds().startTime, 0,
"The bounds start boundary is correct on HiDPI (1).");
is(graph.getOuterBounds().endTime, 150,
"The bounds end boundary is correct on HiDPI (1).");
scroll(graph, 10000, HORIZONTAL_AXIS, 1);
is(Math.round(graph.getViewRange().startTime), 150,
"The selection start boundary is correct on HiDPI (2).");
is(Math.round(graph.getViewRange().endTime), 150,
"The selection end boundary is correct on HiDPI (2).");
is(graph.getOuterBounds().startTime, 0,
"The bounds start boundary is correct on HiDPI (2).");
is(graph.getOuterBounds().endTime, 150,
"The bounds end boundary is correct on HiDPI (2).");
}
// EventUtils just doesn't work!
var HORIZONTAL_AXIS = 1;
var VERTICAL_AXIS = 2;
function scroll(graph, wheel, axis, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseWheel({ testX: x, testY: y, axis, detail: wheel,
HORIZONTAL_AXIS,
VERTICAL_AXIS
});
}

View file

@ -0,0 +1,155 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that vertical panning in the flame graph widget works properly.
const TEST_DATA = [
{
color: "#f00",
blocks: [
{ x: 0, y: 0, width: 50, height: 20, text: "FOO" },
{ x: 50, y: 0, width: 100, height: 20, text: "BAR" }
]
},
{
color: "#00f",
blocks: [
{ x: 0, y: 30, width: 30, height: 20, text: "BAZ" }
]
}
];
const TEST_BOUNDS = { startTime: 0, endTime: 150 };
const TEST_WIDTH = 200;
const TEST_HEIGHT = 100;
const TEST_DPI_DENSITIY = 2;
const {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new FlameGraph(doc.body, TEST_DPI_DENSITIY);
graph.fixedWidth = TEST_WIDTH;
graph.fixedHeight = TEST_HEIGHT;
yield graph.ready();
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.setData({ data: TEST_DATA, bounds: TEST_BOUNDS });
// Drag up vertically only.
dragStart(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2);
is(graph.getViewRange().startTime | 0, 0,
"The selection start boundary is correct (1).");
is(graph.getViewRange().endTime | 0, 150,
"The selection end boundary is correct (1).");
is(graph.getViewRange().verticalOffset | 0, 0,
"The vertical offset is correct (1).");
hover(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2 - 50);
is(graph.getViewRange().startTime | 0, 0,
"The selection start boundary is correct (2).");
is(graph.getViewRange().endTime | 0, 150,
"The selection end boundary is correct (2).");
is(graph.getViewRange().verticalOffset | 0, 17,
"The vertical offset is correct (2).");
dragStop(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2 - 100);
is(graph.getViewRange().startTime | 0, 0,
"The selection start boundary is correct (3).");
is(graph.getViewRange().endTime | 0, 150,
"The selection end boundary is correct (3).");
is(graph.getViewRange().verticalOffset | 0, 42,
"The vertical offset is correct (3).");
// Drag down strongly vertically and slightly horizontally.
dragStart(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2);
is(graph.getViewRange().startTime | 0, 0,
"The selection start boundary is correct (4).");
is(graph.getViewRange().endTime | 0, 150,
"The selection end boundary is correct (4).");
is(graph.getViewRange().verticalOffset | 0, 42,
"The vertical offset is correct (4).");
hover(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2 + 50);
is(graph.getViewRange().startTime | 0, 0,
"The selection start boundary is correct (5).");
is(graph.getViewRange().endTime | 0, 150,
"The selection end boundary is correct (5).");
is(graph.getViewRange().verticalOffset | 0, 25,
"The vertical offset is correct (5).");
dragStop(graph, TEST_WIDTH / 2 + 100, TEST_HEIGHT / 2 + 500);
is(graph.getViewRange().startTime | 0, 0,
"The selection start boundary is correct (6).");
is(graph.getViewRange().endTime | 0, 150,
"The selection end boundary is correct (6).");
is(graph.getViewRange().verticalOffset | 0, 0,
"The vertical offset is correct (6).");
// Drag up slightly vertically and strongly horizontally.
dragStart(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2);
is(graph.getViewRange().startTime | 0, 0,
"The selection start boundary is correct (7).");
is(graph.getViewRange().endTime | 0, 150,
"The selection end boundary is correct (7).");
is(graph.getViewRange().verticalOffset | 0, 0,
"The vertical offset is correct (7).");
hover(graph, TEST_WIDTH / 2 + 50, TEST_HEIGHT / 2);
is(graph.getViewRange().startTime | 0, 0,
"The selection start boundary is correct (8).");
is(graph.getViewRange().endTime | 0, 116,
"The selection end boundary is correct (8).");
is(graph.getViewRange().verticalOffset | 0, 0,
"The vertical offset is correct (8).");
dragStop(graph, TEST_WIDTH / 2 + 500, TEST_HEIGHT / 2 + 100);
is(graph.getViewRange().startTime | 0, 0,
"The selection start boundary is correct (9).");
is(graph.getViewRange().endTime | 0, 0,
"The selection end boundary is correct (9).");
is(graph.getViewRange().verticalOffset | 0, 0,
"The vertical offset is correct (9).");
}
// EventUtils just doesn't work!
function hover(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
}
function dragStart(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
}
function dragStop(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}

View file

@ -0,0 +1,90 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that text metrics in the flame graph widget work properly.
const HTML_NS = "http://www.w3.org/1999/xhtml";
const {ELLIPSIS} = require("devtools/shared/l10n");
const {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph");
const {FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE} = require("devtools/client/shared/widgets/FlameGraph");
const {FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new FlameGraph(doc.body, 1);
yield graph.ready();
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
is(graph._averageCharWidth, getAverageCharWidth(),
"The average char width was calculated correctly.");
is(graph._overflowCharWidth, getCharWidth(ELLIPSIS),
"The ellipsis char width was calculated correctly.");
let text = "This text is maybe overflowing";
let text1000px = graph._getFittedText(text, 1000);
let text50px = graph._getFittedText(text, 50);
let text10px = graph._getFittedText(text, 10);
let text1px = graph._getFittedText(text, 1);
is(graph._getTextWidthApprox(text), getAverageCharWidth() * text.length,
"The approximate width was calculated correctly.");
info("Text at 1000px width: " + text1000px);
info("Text at 50px width : " + text50px);
info("Text at 10px width : " + text10px);
info("Text at 1px width : " + text1px);
is(text1000px, text,
"The fitted text for 1000px width is correct.");
isnot(text50px, text,
"The fitted text for 50px width is correct (1).");
ok(text50px.includes(ELLIPSIS),
"The fitted text for 50px width is correct (2).");
is(graph._getFittedText(text, FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE + 1), ELLIPSIS,
"The fitted text for text font size width is correct.");
is(graph._getFittedText(text, 1), "",
"The fitted text for 1px width is correct.");
}
function getAverageCharWidth() {
let letterWidthsSum = 0;
let start = " ".charCodeAt(0);
let end = "z".charCodeAt(0) + 1;
for (let i = start; i < end; i++) {
let char = String.fromCharCode(i);
letterWidthsSum += getCharWidth(char);
}
return letterWidthsSum / (end - start);
}
function getCharWidth(char) {
let canvas = document.createElementNS(HTML_NS, "canvas");
let ctx = canvas.getContext("2d");
let fontSize = FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE;
let fontFamily = FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY;
ctx.font = fontSize + "px " + fontFamily;
return ctx.measureText(char).width;
}

View file

@ -0,0 +1,113 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that flame graph widget has proper keyboard support.
const TEST_DATA = [
{
color: "#f00",
blocks: [
{ x: 0, y: 0, width: 50, height: 20, text: "FOO" },
{ x: 50, y: 0, width: 100, height: 20, text: "BAR" }
]
},
{
color: "#00f",
blocks: [
{ x: 0, y: 30, width: 30, height: 20, text: "BAZ" }
]
}
];
const TEST_BOUNDS = { startTime: 0, endTime: 150 };
const TEST_DPI_DENSITIY = 2;
const KEY_CODE_UP = 38;
const KEY_CODE_LEFT = 37;
const KEY_CODE_RIGHT = 39;
var {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new FlameGraph(doc.body, TEST_DPI_DENSITIY);
yield graph.ready();
yield testGraph(host, graph);
yield graph.destroy();
host.destroy();
}
function* testGraph(host, graph) {
graph.setData({ data: TEST_DATA, bounds: TEST_BOUNDS });
is(graph._selection.start, 0,
"The graph's selection start value is initially correct.");
is(graph._selection.end, TEST_BOUNDS.endTime * TEST_DPI_DENSITIY,
"The graph's selection end value is initially correct.");
yield pressKeyForTime(graph, KEY_CODE_LEFT, 1000);
is(graph._selection.start, 0,
"The graph's selection start value is correct after pressing LEFT.");
ok(graph._selection.end < TEST_BOUNDS.endTime * TEST_DPI_DENSITIY,
"The graph's selection end value is correct after pressing LEFT.");
graph._selection.start = 0;
graph._selection.end = TEST_BOUNDS.endTime * TEST_DPI_DENSITIY;
info("Graph selection was reset (1).");
yield pressKeyForTime(graph, KEY_CODE_RIGHT, 1000);
ok(graph._selection.start > 0,
"The graph's selection start value is correct after pressing RIGHT.");
is(graph._selection.end, TEST_BOUNDS.endTime * TEST_DPI_DENSITIY,
"The graph's selection end value is correct after pressing RIGHT.");
graph._selection.start = 0;
graph._selection.end = TEST_BOUNDS.endTime * TEST_DPI_DENSITIY;
info("Graph selection was reset (2).");
yield pressKeyForTime(graph, KEY_CODE_UP, 1000);
ok(graph._selection.start > 0,
"The graph's selection start value is correct after pressing UP.");
ok(graph._selection.end < TEST_BOUNDS.endTime * TEST_DPI_DENSITIY,
"The graph's selection end value is correct after pressing UP.");
let distanceLeft = graph._selection.start;
let distanceRight = TEST_BOUNDS.endTime * TEST_DPI_DENSITIY - graph._selection.end;
ok(Math.abs(distanceRight - distanceLeft) < 0.1,
"The graph zoomed correctly towards the center point.");
}
function pressKeyForTime(graph, keyCode, ms) {
graph._onKeyDown({
keyCode,
preventDefault: () => {},
stopPropagation: () => {},
});
return new Promise(resolve => {
setTimeout(() => {
graph._onKeyUp({
keyCode,
preventDefault: () => {},
stopPropagation: () => {},
});
resolve();
}, ms);
});
}

View file

@ -0,0 +1,256 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that text metrics and data conversion from profiler samples
// widget work properly in the flame graph.
const {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph");
const {PALLETTE_SIZE} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA);
ok(out, "Some data was outputted properly");
is(out.length, PALLETTE_SIZE, "The outputted length is correct.");
info("Got flame graph data:\n" + out.toSource() + "\n");
for (let i = 0; i < out.length; i++) {
let found = out[i];
let expected = EXPECTED_OUTPUT[i];
is(found.blocks.length, expected.blocks.length,
"The correct number of blocks were found in this bucket.");
for (let j = 0; j < found.blocks.length; j++) {
is(found.blocks[j].x, expected.blocks[j].x,
"The expected block X position is correct for this frame.");
is(found.blocks[j].y, expected.blocks[j].y,
"The expected block Y position is correct for this frame.");
is(found.blocks[j].width, expected.blocks[j].width,
"The expected block width is correct for this frame.");
is(found.blocks[j].height, expected.blocks[j].height,
"The expected block height is correct for this frame.");
is(found.blocks[j].text, expected.blocks[j].text,
"The expected block text is correct for this frame.");
}
}
}
var TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "M"
}, {
location: "N",
}, {
location: "P"
}],
time: 50,
}, {
frames: [{
location: "A"
}, {
location: "B",
}, {
location: "C"
}],
time: 100,
}, {
frames: [{
location: "A"
}, {
location: "B",
}, {
location: "D"
}],
time: 210,
}, {
frames: [{
location: "A"
}, {
location: "E",
}, {
location: "F"
}],
time: 330,
}, {
frames: [{
location: "A"
}, {
location: "B",
}, {
location: "C"
}],
time: 460,
}, {
frames: [{
location: "X"
}, {
location: "Y",
}, {
location: "Z"
}],
time: 500
}]);
var EXPECTED_OUTPUT = [{
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 50,
frameKey: "A",
x: 50,
y: 0,
width: 410,
height: 15,
text: "A"
}]
}, {
blocks: [{
startTime: 50,
frameKey: "B",
x: 50,
y: 15,
width: 160,
height: 15,
text: "B"
}, {
startTime: 330,
frameKey: "B",
x: 330,
y: 15,
width: 130,
height: 15,
text: "B"
}]
}, {
blocks: [{
startTime: 50,
frameKey: "C",
x: 50,
y: 30,
width: 50,
height: 15,
text: "C"
}, {
startTime: 330,
frameKey: "C",
x: 330,
y: 30,
width: 130,
height: 15,
text: "C"
}]
}, {
blocks: [{
startTime: 100,
frameKey: "D",
x: 100,
y: 30,
width: 110,
height: 15,
text: "D"
}, {
startTime: 460,
frameKey: "X",
x: 460,
y: 0,
width: 40,
height: 15,
text: "X"
}]
}, {
blocks: [{
startTime: 210,
frameKey: "E",
x: 210,
y: 15,
width: 120,
height: 15,
text: "E"
}, {
startTime: 460,
frameKey: "Y",
x: 460,
y: 15,
width: 40,
height: 15,
text: "Y"
}]
}, {
blocks: [{
startTime: 210,
frameKey: "F",
x: 210,
y: 30,
width: 120,
height: 15,
text: "F"
}, {
startTime: 460,
frameKey: "Z",
x: 460,
y: 30,
width: 40,
height: 15,
text: "Z"
}]
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "M",
x: 0,
y: 0,
width: 50,
height: 15,
text: "M"
}]
}, {
blocks: [{
startTime: 0,
frameKey: "N",
x: 0,
y: 15,
width: 50,
height: 15,
text: "N"
}]
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "P",
x: 0,
y: 30,
width: 50,
height: 15,
text: "P"
}]
}, {
blocks: []
}, {
blocks: []
}];

View file

@ -0,0 +1,130 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests consecutive duplicate frames are removed from the flame graph data.
const {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph");
const {PALLETTE_SIZE} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
flattenRecursion: true
});
ok(out, "Some data was outputted properly");
is(out.length, PALLETTE_SIZE, "The outputted length is correct.");
info("Got flame graph data:\n" + out.toSource() + "\n");
for (let i = 0; i < out.length; i++) {
let found = out[i];
let expected = EXPECTED_OUTPUT[i];
is(found.blocks.length, expected.blocks.length,
"The correct number of blocks were found in this bucket.");
for (let j = 0; j < found.blocks.length; j++) {
is(found.blocks[j].x, expected.blocks[j].x,
"The expected block X position is correct for this frame.");
is(found.blocks[j].y, expected.blocks[j].y,
"The expected block Y position is correct for this frame.");
is(found.blocks[j].width, expected.blocks[j].width,
"The expected block width is correct for this frame.");
is(found.blocks[j].height, expected.blocks[j].height,
"The expected block height is correct for this frame.");
is(found.blocks[j].text, expected.blocks[j].text,
"The expected block text is correct for this frame.");
}
}
}
var TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "A"
}, {
location: "A"
}, {
location: "A"
}, {
location: "B",
}, {
location: "B",
}, {
location: "C"
}],
time: 50,
}]);
var EXPECTED_OUTPUT = [{
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "A",
x: 0,
y: 0,
width: 50,
height: 15,
text: "A"
}]
}, {
blocks: [{
startTime: 0,
frameKey: "B",
x: 0,
y: 15,
width: 50,
height: 15,
text: "B"
}]
}, {
blocks: [{
startTime: 0,
frameKey: "C",
x: 0,
y: 30,
width: 50,
height: 15,
text: "C"
}]
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}];

View file

@ -0,0 +1,136 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests if platform frames are removed from the flame graph data.
const {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph");
const {PALLETTE_SIZE} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
contentOnly: true
});
ok(out, "Some data was outputted properly");
is(out.length, PALLETTE_SIZE, "The outputted length is correct.");
info("Got flame graph data:\n" + out.toSource() + "\n");
for (let i = 0; i < out.length; i++) {
let found = out[i];
let expected = EXPECTED_OUTPUT[i];
is(found.blocks.length, expected.blocks.length,
"The correct number of blocks were found in this bucket.");
for (let j = 0; j < found.blocks.length; j++) {
is(found.blocks[j].x, expected.blocks[j].x,
"The expected block X position is correct for this frame.");
is(found.blocks[j].y, expected.blocks[j].y,
"The expected block Y position is correct for this frame.");
is(found.blocks[j].width, expected.blocks[j].width,
"The expected block width is correct for this frame.");
is(found.blocks[j].height, expected.blocks[j].height,
"The expected block height is correct for this frame.");
is(found.blocks[j].text, expected.blocks[j].text,
"The expected block text is correct for this frame.");
}
}
}
var TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "http://A"
}, {
location: "https://B"
}, {
location: "file://C",
}, {
location: "chrome://D"
}, {
location: "resource://E"
}],
time: 50,
}]);
var EXPECTED_OUTPUT = [{
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "http://A",
x: 0,
y: 0,
width: 50,
height: 15,
text: "http://A"
}]
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "Gecko",
x: 0,
y: 45,
width: 50,
height: 15,
text: "Gecko"
}]
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "https://B",
x: 0,
y: 15,
width: 50,
height: 15,
text: "https://B"
}]
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "file://C",
x: 0,
y: 30,
width: 50,
height: 15,
text: "file://C"
}]
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}];

View file

@ -0,0 +1,188 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests if (idle) nodes are added when necessary in the flame graph data.
const {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph");
const {PALLETTE_SIZE} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
flattenRecursion: true,
contentOnly: true,
showIdleBlocks: "\m/"
});
ok(out, "Some data was outputted properly");
is(out.length, PALLETTE_SIZE, "The outputted length is correct.");
info("Got flame graph data:\n" + out.toSource() + "\n");
for (let i = 0; i < out.length; i++) {
let found = out[i];
let expected = EXPECTED_OUTPUT[i];
is(found.blocks.length, expected.blocks.length,
"The correct number of blocks were found in this bucket.");
for (let j = 0; j < found.blocks.length; j++) {
is(found.blocks[j].x, expected.blocks[j].x,
"The expected block X position is correct for this frame.");
is(found.blocks[j].y, expected.blocks[j].y,
"The expected block Y position is correct for this frame.");
is(found.blocks[j].width, expected.blocks[j].width,
"The expected block width is correct for this frame.");
is(found.blocks[j].height, expected.blocks[j].height,
"The expected block height is correct for this frame.");
is(found.blocks[j].text, expected.blocks[j].text,
"The expected block text is correct for this frame.");
}
}
}
var TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "http://A"
}, {
location: "http://A"
}, {
location: "http://A"
}, {
location: "https://B"
}, {
location: "https://B"
}, {
location: "file://C",
}, {
location: "chrome://D"
}, {
location: "resource://E"
}],
time: 50
}, {
frames: [],
time: 100
}, {
frames: [{
location: "http://A"
}, {
location: "https://B"
}, {
location: "file://C",
}],
time: 150
}]);
var EXPECTED_OUTPUT = [{
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "http://A",
x: 0,
y: 0,
width: 50,
height: 15,
text: "http://A"
}, {
startTime: 100,
frameKey: "http://A",
x: 100,
y: 0,
width: 50,
height: 15,
text: "http://A"
}]
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "Gecko",
x: 0,
y: 45,
width: 50,
height: 15,
text: "Gecko"
}]
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "https://B",
x: 0,
y: 15,
width: 50,
height: 15,
text: "https://B"
}, {
startTime: 100,
frameKey: "https://B",
x: 100,
y: 15,
width: 50,
height: 15,
text: "https://B"
}]
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "file://C",
x: 0,
y: 30,
width: 50,
height: 15,
text: "file://C"
}, {
startTime: 100,
frameKey: "file://C",
x: 100,
y: 30,
width: 50,
height: 15,
text: "file://C"
}]
}, {
blocks: [{
startTime: 50,
frameKey: "m/",
x: 50,
y: 0,
width: 50,
height: 15,
text: "m/"
}]
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}];

View file

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that flame graph data is cached, and that the cache may be cleared.
const {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let out1 = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA);
let out2 = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA);
is(out1, out2, "The outputted data is identical.");
let out3 = FlameGraphUtils.createFlameGraphDataFromThread(
TEST_DATA, { flattenRecursion: true }
);
is(out2, out3, "The outputted data is still identical.");
FlameGraphUtils.removeFromCache(TEST_DATA);
let out4 = FlameGraphUtils.createFlameGraphDataFromThread(
TEST_DATA, { flattenRecursion: true }
);
isnot(out3, out4, "The outputted data is not identical anymore.");
}
var TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "A"
}, {
location: "A"
}, {
location: "A"
}, {
location: "B",
}, {
location: "B",
}, {
location: "C"
}],
time: 50,
}]);

View file

@ -0,0 +1,117 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the text displayed is the function name, file name and line number
// if applicable and demangling.
const {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph");
const {PALLETTE_SIZE} = require("devtools/client/shared/widgets/FlameGraph");
const MANGLED_FN = "__Z3FooIiEvv";
const UNMANGLED_FN = "void Foo<int>()";
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
flattenRecursion: true
});
ok(out, "Some data was outputted properly");
is(out.length, PALLETTE_SIZE, "The outputted length is correct.");
info("Got flame graph data:\n" + out.toSource() + "\n");
for (let i = 0; i < out.length; i++) {
let found = out[i];
let expected = EXPECTED_OUTPUT[i];
is(found.blocks.length, expected.blocks.length,
"The correct number of blocks were found in this bucket.");
for (let j = 0; j < found.blocks.length; j++) {
is(found.blocks[j].x, expected.blocks[j].x,
"The expected block X position is correct for this frame.");
is(found.blocks[j].y, expected.blocks[j].y,
"The expected block Y position is correct for this frame.");
is(found.blocks[j].width, expected.blocks[j].width,
"The expected block width is correct for this frame.");
is(found.blocks[j].height, expected.blocks[j].height,
"The expected block height is correct for this frame.");
is(found.blocks[j].text, expected.blocks[j].text,
"The expected block text is correct for this frame.");
}
}
}
var TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "A (http://path/to/file.js:10:5)"
}, {
location: `${MANGLED_FN} (http://path/to/file.js:100:5)`
}],
time: 50,
}]);
var EXPECTED_OUTPUT = [{
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: "A (http://path/to/file.js:10:5)",
x: 0,
y: 0,
width: 50,
height: 15,
text: "A (file.js:10)"
}]
}, {
blocks: []
}, {
blocks: []
}, {
blocks: [{
startTime: 0,
frameKey: `${MANGLED_FN} (http://path/to/file.js:100:5)`,
x: 0,
y: 15,
width: 50,
height: 15,
text: `${UNMANGLED_FN} (file.js:100)`
}]
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}, {
blocks: []
}];

View file

@ -0,0 +1,24 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests if (idle) nodes are added when necessary in the flame graph data.
const {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph");
add_task(function* () {
let hash1 = FlameGraphUtils._getStringHash("abc");
let hash2 = FlameGraphUtils._getStringHash("acb");
let hash3 = FlameGraphUtils._getStringHash(Array.from(Array(100000)).join("a"));
let hash4 = FlameGraphUtils._getStringHash(Array.from(Array(100000)).join("b"));
isnot(hash1, hash2, "The hashes should not be equal (1).");
isnot(hash2, hash3, "The hashes should not be equal (2).");
isnot(hash3, hash4, "The hashes should not be equal (3).");
ok(Number.isInteger(hash1), "The hashes should be integers, not Infinity or NaN (1).");
ok(Number.isInteger(hash2), "The hashes should be integers, not Infinity or NaN (2).");
ok(Number.isInteger(hash3), "The hashes should be integers, not Infinity or NaN (3).");
ok(Number.isInteger(hash4), "The hashes should be integers, not Infinity or NaN (4).");
});

View file

@ -0,0 +1,70 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that graph widgets works properly.
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
finish();
});
function* performTest() {
let [host,, doc] = yield createHost();
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new LineGraphWidget(doc.body, "fps");
let readyEventEmitted;
graph.once("ready", () => {
readyEventEmitted = true;
});
yield graph.ready();
ok(readyEventEmitted, "The 'ready' event should have been emitted");
testGraph(host, graph);
yield graph.destroy();
host.destroy();
}
function testGraph(host, graph) {
ok(graph._container.classList.contains("line-graph-widget-container"),
"The correct graph container was created.");
ok(graph._canvas.classList.contains("line-graph-widget-canvas"),
"The correct graph container was created.");
let bounds = host.frame.getBoundingClientRect();
is(graph.width, bounds.width * window.devicePixelRatio,
"The graph has the correct width.");
is(graph.height, bounds.height * window.devicePixelRatio,
"The graph has the correct height.");
ok(graph._cursor.x === null,
"The graph's cursor X coordinate is initially null.");
ok(graph._cursor.y === null,
"The graph's cursor Y coordinate is initially null.");
ok(graph._selection.start === null,
"The graph's selection start value is initially null.");
ok(graph._selection.end === null,
"The graph's selection end value is initially null.");
ok(graph._selectionDragger.origin === null,
"The graph's dragger origin value is initially null.");
ok(graph._selectionDragger.anchor.start === null,
"The graph's dragger anchor start value is initially null.");
ok(graph._selectionDragger.anchor.end === null,
"The graph's dragger anchor end value is initially null.");
ok(graph._selectionResizer.margin === null,
"The graph's resizer margin value is initially null.");
}

View file

@ -0,0 +1,107 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that graph widgets can properly add data, regions and highlights.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
testDataAndRegions(graph);
testHighlights(graph);
yield graph.destroy();
host.destroy();
}
function testDataAndRegions(graph) {
let thrown1;
try {
graph.setRegions(TEST_REGIONS);
} catch (e) {
thrown1 = true;
}
ok(thrown1, "Setting regions before setting data shouldn't work.");
graph.setData(TEST_DATA);
graph.setRegions(TEST_REGIONS);
let thrown2;
try {
graph.setRegions(TEST_REGIONS);
} catch (e) {
thrown2 = true;
}
ok(thrown2, "Setting regions twice shouldn't work.");
ok(graph.hasData(), "The graph should now have the data source set.");
ok(graph.hasRegions(), "The graph should now have the regions set.");
is(graph.dataScaleX,
// last & first tick in TEST_DATA
graph.width / 4180,
"The data scale on the X axis is correct.");
is(graph.dataScaleY,
// max value in TEST_DATA * GRAPH_DAMPEN_VALUES
graph.height / 60 * 0.85,
"The data scale on the Y axis is correct.");
for (let i = 0; i < TEST_REGIONS.length; i++) {
let original = TEST_REGIONS[i];
let normalized = graph._regions[i];
is(original.start * graph.dataScaleX, normalized.start,
"The region's start value was properly normalized.");
is(original.end * graph.dataScaleX, normalized.end,
"The region's end value was properly normalized.");
}
}
function testHighlights(graph) {
graph.setMask(TEST_REGIONS);
ok(graph.hasMask(),
"The graph should now have the highlights set.");
graph.setMask([]);
ok(graph.hasMask(),
"The graph shouldn't have anything highlighted.");
graph.setMask(null);
ok(!graph.hasMask(),
"The graph should have everything highlighted.");
}

View file

@ -0,0 +1,111 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that graph widgets can handle clients getting/setting the
// selection or cursor.
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
yield testSelection(graph);
yield testCursor(graph);
yield graph.destroy();
host.destroy();
}
function* testSelection(graph) {
ok(graph.getSelection().start === null,
"The graph's selection should initially have a null start value.");
ok(graph.getSelection().end === null,
"The graph's selection should initially have a null end value.");
ok(!graph.hasSelection(),
"There shouldn't initially be any selection.");
let selected = graph.once("selecting");
graph.setSelection({ start: 100, end: 200 });
yield selected;
ok(true, "A 'selecting' event has been fired.");
ok(graph.hasSelection(),
"There should now be a selection.");
is(graph.getSelection().start, 100,
"The graph's selection now has an updated start value.");
is(graph.getSelection().end, 200,
"The graph's selection now has an updated end value.");
let thrown;
try {
graph.setSelection({ start: null, end: null });
} catch (e) {
thrown = true;
}
ok(thrown, "Setting a null selection shouldn't work.");
ok(graph.hasSelection(),
"There should still be a selection.");
let deselected = graph.once("deselecting");
graph.dropSelection();
yield deselected;
ok(true, "A 'deselecting' event has been fired.");
ok(!graph.hasSelection(),
"There shouldn't be any selection anymore.");
ok(graph.getSelection().start === null,
"The graph's selection now has a null start value.");
ok(graph.getSelection().end === null,
"The graph's selection now has a null end value.");
}
function* testCursor(graph) {
ok(graph.getCursor().x === null,
"The graph's cursor should initially have a null X value.");
ok(graph.getCursor().y === null,
"The graph's cursor should initially have a null Y value.");
ok(!graph.hasCursor(),
"There shouldn't initially be any cursor.");
graph.setCursor({ x: 100, y: 50 });
ok(graph.hasCursor(),
"There should now be a cursor.");
is(graph.getCursor().x, 100,
"The graph's cursor now has an updated start value.");
is(graph.getCursor().y, 50,
"The graph's cursor now has an updated end value.");
let thrown;
try {
graph.setCursor({ x: null, y: null });
} catch (e) {
thrown = true;
}
ok(thrown, "Setting a null cursor shouldn't work.");
ok(graph.hasCursor(),
"There should still be a cursor.");
graph.dropCursor();
ok(!graph.hasCursor(),
"There shouldn't be any cursor anymore.");
ok(graph.getCursor().x === null,
"The graph's cursor now has a null start value.");
ok(graph.getCursor().y === null,
"The graph's cursor now has a null end value.");
}

View file

@ -0,0 +1,69 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that graph widgets can correctly compare selections and cursors.
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
ok(!graph.hasSelection(),
"There shouldn't initially be any selection.");
is(graph.getSelectionWidth(), 0,
"The selection width should be 0 when there's no selection.");
graph.setSelection({ start: 100, end: 200 });
ok(graph.hasSelection(),
"There should now be a selection.");
is(graph.getSelectionWidth(), 100,
"The selection width should now be 100.");
ok(graph.isSelectionDifferent({ start: 100, end: 201 }),
"The selection was correctly reported to be different (1).");
ok(graph.isSelectionDifferent({ start: 101, end: 200 }),
"The selection was correctly reported to be different (2).");
ok(graph.isSelectionDifferent({ start: null, end: null }),
"The selection was correctly reported to be different (3).");
ok(graph.isSelectionDifferent(null),
"The selection was correctly reported to be different (4).");
ok(!graph.isSelectionDifferent({ start: 100, end: 200 }),
"The selection was incorrectly reported to be different (1).");
ok(!graph.isSelectionDifferent(graph.getSelection()),
"The selection was incorrectly reported to be different (2).");
graph.setCursor({ x: 100, y: 50 });
ok(graph.isCursorDifferent({ x: 100, y: 51 }),
"The cursor was correctly reported to be different (1).");
ok(graph.isCursorDifferent({ x: 101, y: 50 }),
"The cursor was correctly reported to be different (2).");
ok(graph.isCursorDifferent({ x: null, y: null }),
"The cursor was correctly reported to be different (3).");
ok(graph.isCursorDifferent(null),
"The cursor was correctly reported to be different (4).");
ok(!graph.isCursorDifferent({ x: 100, y: 50 }),
"The cursor was incorrectly reported to be different (1).");
ok(!graph.isCursorDifferent(graph.getCursor()),
"The cursor was incorrectly reported to be different (2).");
}

View file

@ -0,0 +1,154 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that graph widgets can correctly determine which regions are hovered.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
ok(!graph.getHoveredRegion(),
"There should be no hovered region yet because there's no regions.");
ok(!graph._isHoveringStartBoundary(),
"The graph start boundary should not be hovered.");
ok(!graph._isHoveringEndBoundary(),
"The graph end boundary should not be hovered.");
ok(!graph._isHoveringSelectionContents(),
"The graph contents should not be hovered.");
ok(!graph._isHoveringSelectionContentsOrBoundaries(),
"The graph contents or boundaries should not be hovered.");
graph.setData(TEST_DATA);
graph.setRegions(TEST_REGIONS);
ok(!graph.getHoveredRegion(),
"There should be no hovered region yet because there's no cursor.");
graph.setCursor({ x: TEST_REGIONS[0].start * graph.dataScaleX - 1, y: 0 });
ok(!graph.getHoveredRegion(),
"There shouldn't be any hovered region yet.");
graph.setCursor({ x: TEST_REGIONS[0].start * graph.dataScaleX + 1, y: 0 });
ok(graph.getHoveredRegion(),
"There should be a hovered region now.");
is(graph.getHoveredRegion().start, 320 * graph.dataScaleX,
"The reported hovered region is correct (1).");
is(graph.getHoveredRegion().end, 460 * graph.dataScaleX,
"The reported hovered region is correct (2).");
graph.setSelection({ start: 100, end: 200 });
info("Setting cursor over the left boundary.");
graph.setCursor({ x: 100, y: 0 });
ok(graph._isHoveringStartBoundary(),
"The graph start boundary should be hovered.");
ok(!graph._isHoveringEndBoundary(),
"The graph end boundary should not be hovered.");
ok(!graph._isHoveringSelectionContents(),
"The graph contents should not be hovered.");
ok(graph._isHoveringSelectionContentsOrBoundaries(),
"The graph contents or boundaries should be hovered.");
info("Setting cursor near the left boundary.");
graph.setCursor({ x: 105, y: 0 });
ok(graph._isHoveringStartBoundary(),
"The graph start boundary should be hovered.");
ok(!graph._isHoveringEndBoundary(),
"The graph end boundary should not be hovered.");
ok(graph._isHoveringSelectionContents(),
"The graph contents should be hovered.");
ok(graph._isHoveringSelectionContentsOrBoundaries(),
"The graph contents or boundaries should be hovered.");
info("Setting cursor over the selection.");
graph.setCursor({ x: 150, y: 0 });
ok(!graph._isHoveringStartBoundary(),
"The graph start boundary should not be hovered.");
ok(!graph._isHoveringEndBoundary(),
"The graph end boundary should not be hovered.");
ok(graph._isHoveringSelectionContents(),
"The graph contents should be hovered.");
ok(graph._isHoveringSelectionContentsOrBoundaries(),
"The graph contents or boundaries should be hovered.");
info("Setting cursor near the right boundary.");
graph.setCursor({ x: 195, y: 0 });
ok(!graph._isHoveringStartBoundary(),
"The graph start boundary should not be hovered.");
ok(graph._isHoveringEndBoundary(),
"The graph end boundary should be hovered.");
ok(graph._isHoveringSelectionContents(),
"The graph contents should be hovered.");
ok(graph._isHoveringSelectionContentsOrBoundaries(),
"The graph contents or boundaries should be hovered.");
info("Setting cursor over the right boundary.");
graph.setCursor({ x: 200, y: 0 });
ok(!graph._isHoveringStartBoundary(),
"The graph start boundary should not be hovered.");
ok(graph._isHoveringEndBoundary(),
"The graph end boundary should be hovered.");
ok(!graph._isHoveringSelectionContents(),
"The graph contents should not be hovered.");
ok(graph._isHoveringSelectionContentsOrBoundaries(),
"The graph contents or boundaries should be hovered.");
info("Setting away from the selection.");
graph.setCursor({ x: 300, y: 0 });
ok(!graph._isHoveringStartBoundary(),
"The graph start boundary should not be hovered.");
ok(!graph._isHoveringEndBoundary(),
"The graph end boundary should not be hovered.");
ok(!graph._isHoveringSelectionContents(),
"The graph contents should not be hovered.");
ok(!graph._isHoveringSelectionContentsOrBoundaries(),
"The graph contents or boundaries should not be hovered.");
}

View file

@ -0,0 +1,112 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests if clicking on regions adds a selection spanning that region.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.setData(TEST_DATA);
graph.setRegions(TEST_REGIONS);
click(graph, (graph._regions[0].start + graph._regions[0].end) / 2);
is(graph.getSelection().start, graph._regions[0].start,
"The first region is now selected (1).");
is(graph.getSelection().end, graph._regions[0].end,
"The first region is now selected (2).");
let min = map(graph.getSelection().start, 0, graph.width, 112, 4180);
let max = map(graph.getSelection().end, 0, graph.width, 112, 4180);
is(graph.getMappedSelection().min, min,
"The mapped selection's min value is correct (1).");
is(graph.getMappedSelection().max, max,
"The mapped selection's max value is correct (2).");
click(graph, (graph._regions[1].start + graph._regions[1].end) / 2);
is(graph.getSelection().start, graph._regions[1].start,
"The second region is now selected (1).");
is(graph.getSelection().end, graph._regions[1].end,
"The second region is now selected (2).");
min = map(graph.getSelection().start, 0, graph.width, 112, 4180);
max = map(graph.getSelection().end, 0, graph.width, 112, 4180);
is(graph.getMappedSelection().min, min,
"The mapped selection's min value is correct (3).");
is(graph.getMappedSelection().max, max,
"The mapped selection's max value is correct (4).");
graph.setSelection({ start: graph.width, end: 0 });
min = map(0, 0, graph.width, 112, 4180);
max = map(graph.width, 0, graph.width, 112, 4180);
is(graph.getMappedSelection().min, min,
"The mapped selection's min value is correct (5).");
is(graph.getMappedSelection().max, max,
"The mapped selection's max value is correct (6).");
graph.setSelection({ start: graph.width + 100, end: -100 });
min = map(0, 0, graph.width, 112, 4180);
max = map(graph.width, 0, graph.width, 112, 4180);
is(graph.getMappedSelection().min, min,
"The mapped selection's min value is correct (7).");
is(graph.getMappedSelection().max, max,
"The mapped selection's max value is correct (8).");
}
/**
* Maps a value from one range to another.
*/
function map(value, istart, istop, ostart, ostop) {
return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
}
// EventUtils just doesn't work!
function click(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}

View file

@ -0,0 +1,232 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests if selecting, resizing, moving selections and zooming in/out works.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
testGraph(graph, normalDragStop);
yield graph.destroy();
let graph2 = new LineGraphWidget(doc.body, "fps");
yield graph2.once("ready");
testGraph(graph2, buggyDragStop);
yield graph2.destroy();
host.destroy();
}
function testGraph(graph, dragStop) {
graph.setData(TEST_DATA);
info("Making a selection.");
dragStart(graph, 300);
ok(graph.hasSelectionInProgress(),
"The selection should start (1).");
is(graph.getSelection().start, 300,
"The current selection start value is correct (1).");
is(graph.getSelection().end, 300,
"The current selection end value is correct (1).");
hover(graph, 400);
ok(graph.hasSelectionInProgress(),
"The selection should still be in progress (2).");
is(graph.getSelection().start, 300,
"The current selection start value is correct (2).");
is(graph.getSelection().end, 400,
"The current selection end value is correct (2).");
dragStop(graph, 500);
ok(!graph.hasSelectionInProgress(),
"The selection should have stopped (3).");
is(graph.getSelection().start, 300,
"The current selection start value is correct (3).");
is(graph.getSelection().end, 500,
"The current selection end value is correct (3).");
info("Making a new selection.");
dragStart(graph, 200);
ok(graph.hasSelectionInProgress(),
"The selection should start (4).");
is(graph.getSelection().start, 200,
"The current selection start value is correct (4).");
is(graph.getSelection().end, 200,
"The current selection end value is correct (4).");
hover(graph, 300);
ok(graph.hasSelectionInProgress(),
"The selection should still be in progress (5).");
is(graph.getSelection().start, 200,
"The current selection start value is correct (5).");
is(graph.getSelection().end, 300,
"The current selection end value is correct (5).");
dragStop(graph, 400);
ok(!graph.hasSelectionInProgress(),
"The selection should have stopped (6).");
is(graph.getSelection().start, 200,
"The current selection start value is correct (6).");
is(graph.getSelection().end, 400,
"The current selection end value is correct (6).");
info("Resizing by dragging the end handlebar.");
dragStart(graph, 400);
is(graph.getSelection().start, 200,
"The current selection start value is correct (7).");
is(graph.getSelection().end, 400,
"The current selection end value is correct (7).");
dragStop(graph, 600);
is(graph.getSelection().start, 200,
"The current selection start value is correct (8).");
is(graph.getSelection().end, 600,
"The current selection end value is correct (8).");
info("Resizing by dragging the start handlebar.");
dragStart(graph, 200);
is(graph.getSelection().start, 200,
"The current selection start value is correct (9).");
is(graph.getSelection().end, 600,
"The current selection end value is correct (9).");
dragStop(graph, 100);
is(graph.getSelection().start, 100,
"The current selection start value is correct (10).");
is(graph.getSelection().end, 600,
"The current selection end value is correct (10).");
info("Moving by dragging the selection.");
dragStart(graph, 300);
hover(graph, 400);
is(graph.getSelection().start, 200,
"The current selection start value is correct (11).");
is(graph.getSelection().end, 700,
"The current selection end value is correct (11).");
dragStop(graph, 500);
is(graph.getSelection().start, 300,
"The current selection start value is correct (12).");
is(graph.getSelection().end, 800,
"The current selection end value is correct (12).");
info("Zooming in by scrolling inside the selection.");
scroll(graph, -1000, 600);
is(graph.getSelection().start, 525,
"The current selection start value is correct (13).");
is(graph.getSelection().end, 650,
"The current selection end value is correct (13).");
info("Zooming out by scrolling inside the selection.");
scroll(graph, 1000, 600);
is(graph.getSelection().start, 468.75,
"The current selection start value is correct (14).");
is(graph.getSelection().end, 687.5,
"The current selection end value is correct (14).");
info("Sliding left by scrolling outside the selection.");
scroll(graph, 100, 900);
is(graph.getSelection().start, 458.75,
"The current selection start value is correct (15).");
is(graph.getSelection().end, 677.5,
"The current selection end value is correct (15).");
info("Sliding right by scrolling outside the selection.");
scroll(graph, -100, 900);
is(graph.getSelection().start, 468.75,
"The current selection start value is correct (16).");
is(graph.getSelection().end, 687.5,
"The current selection end value is correct (16).");
info("Zooming out a lot.");
scroll(graph, Number.MAX_SAFE_INTEGER, 500);
is(graph.getSelection().start, 1,
"The current selection start value is correct (17).");
is(graph.getSelection().end, graph.width - 1,
"The current selection end value is correct (17).");
}
// EventUtils just doesn't work!
function hover(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
}
function dragStart(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
}
function normalDragStop(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}
function buggyDragStop(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
// Only fire a mousemove with no buttons instead of a mouseup.
// This happens when the mouseup happens outside of the window.
// Send different coordinates to make sure the selection is preserved,
// see Bugs 1066504 and 1144779.
graph._onMouseMove({ testX: x + 1, testY: y + 1, buttons: 0 });
}
function scroll(graph, wheel, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseWheel({ testX: x, testY: y, detail: wheel });
}

View file

@ -0,0 +1,88 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests if selections can't be added via clicking, while not allowed.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.setData(TEST_DATA);
graph.selectionEnabled = false;
info("Attempting to make a selection.");
dragStart(graph, 300);
is(graph.hasSelection() || graph.hasSelectionInProgress(), false,
"The graph shouldn't have a selection (1).");
hover(graph, 400);
is(graph.hasSelection() || graph.hasSelectionInProgress(), false,
"The graph shouldn't have a selection (2).");
dragStop(graph, 500);
is(graph.hasSelection() || graph.hasSelectionInProgress(), false,
"The graph shouldn't have a selection (3).");
}
// EventUtils just doesn't work!
function hover(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
}
function dragStart(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
}
function dragStop(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}

View file

@ -0,0 +1,139 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests if movement via event dispatching using screenX / screenY
// works. All of the other tests directly use the graph's mouse event
// callbacks with textX / testY for convenience.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.setData(TEST_DATA);
info("Making a selection.");
dragStart(graph, 300);
ok(graph.hasSelectionInProgress(),
"The selection should start (1).");
is(graph.getSelection().start, 300,
"The current selection start value is correct (1).");
is(graph.getSelection().end, 300,
"The current selection end value is correct (1).");
hover(graph, 400);
ok(graph.hasSelectionInProgress(),
"The selection should still be in progress (2).");
is(graph.getSelection().start, 300,
"The current selection start value is correct (2).");
is(graph.getSelection().end, 400,
"The current selection end value is correct (2).");
dragStop(graph, 500);
ok(!graph.hasSelectionInProgress(),
"The selection should have stopped (3).");
is(graph.getSelection().start, 300,
"The current selection start value is correct (3).");
is(graph.getSelection().end, 500,
"The current selection end value is correct (3).");
info("Making a new selection.");
dragStart(graph, 200);
ok(graph.hasSelectionInProgress(),
"The selection should start (4).");
is(graph.getSelection().start, 200,
"The current selection start value is correct (4).");
is(graph.getSelection().end, 200,
"The current selection end value is correct (4).");
hover(graph, 300);
ok(graph.hasSelectionInProgress(),
"The selection should still be in progress (5).");
is(graph.getSelection().start, 200,
"The current selection start value is correct (5).");
is(graph.getSelection().end, 300,
"The current selection end value is correct (5).");
dragStop(graph, 400);
ok(!graph.hasSelectionInProgress(),
"The selection should have stopped (6).");
is(graph.getSelection().start, 200,
"The current selection start value is correct (6).");
is(graph.getSelection().end, 400,
"The current selection end value is correct (6).");
}
// EventUtils just doesn't work!
function dispatchEvent(graph, x, y, type) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
let quad = graph._canvas.getBoxQuads({
relativeTo: window.document
})[0];
let screenX = window.screenX + quad.p1.x + x;
let screenY = window.screenY + quad.p1.y + y;
graph._canvas.dispatchEvent(new MouseEvent(type, {
bubbles: true,
cancelable: true,
buttons: 1,
view: window,
screenX: screenX,
screenY: screenY,
}));
}
function hover(graph, x, y = 1) {
dispatchEvent(graph, x, y, "mousemove");
}
function dragStart(graph, x, y = 1) {
dispatchEvent(graph, x, y, "mousemove");
dispatchEvent(graph, x, y, "mousedown");
}
function dragStop(graph, x, y = 1) {
dispatchEvent(graph, x, y, "mousemove");
dispatchEvent(graph, x, y, "mouseup");
}

View file

@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that selections are drawn onto the canvas.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.setData(TEST_DATA);
graph.setRegions(TEST_REGIONS);
// Measure the color of the first pixel before any selection is made.
graph._onAnimationFrame();
let pixelNoSelection = graph._ctx.getImageData(1, 1, 1, 1).data;
graph.setSelection({ start: 0, end: 10 });
graph._onAnimationFrame();
let pixelNormalSelection = graph._ctx.getImageData(1, 1, 1, 1).data;
Assert.notDeepEqual(pixelNormalSelection, pixelNoSelection,
"The first pixel is part of the drawn selection.");
graph.setSelection({ start: graph.width + 100, end: -100 });
graph._onAnimationFrame();
let pixelFullSelection = graph._ctx.getImageData(1, 1, 1, 1).data;
Assert.deepEqual(pixelFullSelection, pixelNormalSelection,
"The first pixel is still part of the drawn selection.");
}

View file

@ -0,0 +1,127 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that selections are drawn onto the canvas.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
let CURRENT_ZOOM = 1;
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
graph.setData(TEST_DATA);
info("Testing with normal zoom.");
testGraph(graph);
info("Testing while zoomed out.");
setZoom(host.frame, .5);
testGraph(graph);
info("Testing while zoomed in.");
setZoom(host.frame, 2);
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.dropSelection();
info("Making a selection.");
dragStart(graph, 100);
ok(graph.hasSelectionInProgress(),
"The selection should start (1).");
is(graph.getSelection().start, 100,
"The current selection start value is correct (1).");
is(graph.getSelection().end, 100,
"The current selection end value is correct (1).");
hover(graph, 200);
ok(graph.hasSelectionInProgress(),
"The selection should still be in progress (2).");
is(graph.getSelection().start, 100,
"The current selection start value is correct (2).");
is(graph.getSelection().end, 200,
"The current selection end value is correct (2).");
dragStop(graph, 300);
ok(!graph.hasSelectionInProgress(),
"The selection should have stopped (3).");
is(graph.getSelection().start, 100,
"The current selection start value is correct (3).");
is(graph.getSelection().end, 300,
"The current selection end value is correct (3).");
}
function setZoom(frame, zoomValue) {
let contViewer = frame.docShell.contentViewer;
CURRENT_ZOOM = contViewer.fullZoom = zoomValue;
}
// EventUtils just doesn't work!
function dispatchEvent(graph, x, y, fn) {
x *= CURRENT_ZOOM;
y *= CURRENT_ZOOM;
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
let quad = graph._canvas.getBoxQuads({
relativeTo: window.document
})[0];
let screenX = (window.screenX + quad.p1.x + x);
let screenY = (window.screenY + quad.p1.y + y);
fn({
screenX: screenX,
screenY: screenY,
});
}
function hover(graph, x, y = 1) {
dispatchEvent(graph, x, y, graph._onMouseMove);
}
function dragStart(graph, x, y = 1) {
dispatchEvent(graph, x, y, graph._onMouseMove);
dispatchEvent(graph, x, y, graph._onMouseDown);
}
function dragStop(graph, x, y = 1) {
dispatchEvent(graph, x, y, graph._onMouseMove);
dispatchEvent(graph, x, y, graph._onMouseUp);
}

View file

@ -0,0 +1,88 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests if a selection is dropped when clicking outside of it.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.setData(TEST_DATA);
dragStart(graph, 300);
dragStop(graph, 500);
ok(graph.hasSelection(),
"A selection should be available.");
is(graph.getSelection().start, 300,
"The current selection start value is correct.");
is(graph.getSelection().end, 500,
"The current selection end value is correct.");
click(graph, 600);
ok(!graph.hasSelection(),
"The selection should be dropped.");
}
// EventUtils just doesn't work!
function click(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}
function dragStart(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
}
function dragStop(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}

View file

@ -0,0 +1,104 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that line graphs properly create the gutter and tooltips.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, { metric: "fps" });
yield testGraph(graph);
yield graph.destroy();
host.destroy();
}
function* testGraph(graph) {
info("Should be able to set the graph data before waiting for the ready event.");
yield graph.setDataWhenReady(TEST_DATA);
ok(graph.hasData(), "Data was set successfully.");
is(graph._gutter.hidden, false,
"The gutter should not be hidden because the tooltips have arrows.");
is(graph._maxTooltip.hidden, false,
"The max tooltip should not be hidden.");
is(graph._avgTooltip.hidden, false,
"The avg tooltip should not be hidden.");
is(graph._minTooltip.hidden, false,
"The min tooltip should not be hidden.");
is(graph._maxTooltip.getAttribute("with-arrows"), "true",
"The maximum tooltip has the correct 'with-arrows' attribute.");
is(graph._avgTooltip.getAttribute("with-arrows"), "true",
"The average tooltip has the correct 'with-arrows' attribute.");
is(graph._minTooltip.getAttribute("with-arrows"), "true",
"The minimum tooltip has the correct 'with-arrows' attribute.");
is(graph._maxTooltip.querySelector("[text=info]").textContent, "max",
"The maximum tooltip displays the correct info.");
is(graph._avgTooltip.querySelector("[text=info]").textContent, "avg",
"The average tooltip displays the correct info.");
is(graph._minTooltip.querySelector("[text=info]").textContent, "min",
"The minimum tooltip displays the correct info.");
is(graph._maxTooltip.querySelector("[text=value]").textContent, "60",
"The maximum tooltip displays the correct value.");
is(graph._avgTooltip.querySelector("[text=value]").textContent, "41.72",
"The average tooltip displays the correct value.");
is(graph._minTooltip.querySelector("[text=value]").textContent, "10",
"The minimum tooltip displays the correct value.");
is(graph._maxTooltip.querySelector("[text=metric]").textContent, "fps",
"The maximum tooltip displays the correct metric.");
is(graph._avgTooltip.querySelector("[text=metric]").textContent, "fps",
"The average tooltip displays the correct metric.");
is(graph._minTooltip.querySelector("[text=metric]").textContent, "fps",
"The minimum tooltip displays the correct metric.");
is(parseInt(graph._maxTooltip.style.top, 10), 22,
"The maximum tooltip is positioned correctly.");
is(parseInt(graph._avgTooltip.style.top, 10), 61,
"The average tooltip is positioned correctly.");
is(parseInt(graph._minTooltip.style.top, 10), 128,
"The minimum tooltip is positioned correctly.");
is(parseInt(graph._maxGutterLine.style.top, 10), 22,
"The maximum gutter line is positioned correctly.");
is(parseInt(graph._avgGutterLine.style.top, 10), 61,
"The average gutter line is positioned correctly.");
is(parseInt(graph._minGutterLine.style.top, 10), 128,
"The minimum gutter line is positioned correctly.");
}

View file

@ -0,0 +1,82 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that line graphs properly use the tooltips configuration properties.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
graph.withTooltipArrows = false;
graph.withFixedTooltipPositions = true;
yield testGraph(graph);
yield graph.destroy();
host.destroy();
}
function* testGraph(graph) {
yield graph.setDataWhenReady(TEST_DATA);
is(graph._gutter.hidden, false,
"The gutter should be visible even if the tooltips don't have arrows.");
is(graph._maxTooltip.hidden, false,
"The max tooltip should not be hidden.");
is(graph._avgTooltip.hidden, false,
"The avg tooltip should not be hidden.");
is(graph._minTooltip.hidden, false,
"The min tooltip should not be hidden.");
is(graph._maxTooltip.getAttribute("with-arrows"), "false",
"The maximum tooltip has the correct 'with-arrows' attribute.");
is(graph._avgTooltip.getAttribute("with-arrows"), "false",
"The average tooltip has the correct 'with-arrows' attribute.");
is(graph._minTooltip.getAttribute("with-arrows"), "false",
"The minimum tooltip has the correct 'with-arrows' attribute.");
is(parseInt(graph._maxTooltip.style.top, 10), 8,
"The maximum tooltip is positioned correctly.");
is(parseInt(graph._avgTooltip.style.top, 10), 8,
"The average tooltip is positioned correctly.");
is(parseInt(graph._minTooltip.style.top, 10), 142,
"The minimum tooltip is positioned correctly.");
is(parseInt(graph._maxGutterLine.style.top, 10), 22,
"The maximum gutter line is positioned correctly.");
is(parseInt(graph._avgGutterLine.style.top, 10), 61,
"The average gutter line is positioned correctly.");
is(parseInt(graph._minGutterLine.style.top, 10), 128,
"The minimum gutter line is positioned correctly.");
}

View file

@ -0,0 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that line graphs hide the tooltips when there's no data available.
const TEST_DATA = [];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield testGraph(graph);
yield graph.destroy();
host.destroy();
}
function* testGraph(graph) {
yield graph.setDataWhenReady(TEST_DATA);
is(graph._gutter.hidden, true,
"The gutter should be hidden, since there's no data available.");
is(graph._maxTooltip.hidden, true,
"The max tooltip should be hidden.");
is(graph._avgTooltip.hidden, true,
"The avg tooltip should be hidden.");
is(graph._minTooltip.hidden, true,
"The min tooltip should be hidden.");
}

View file

@ -0,0 +1,39 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that line graphs hide the 'max' tooltip when the distance between
// the 'min' and 'max' tooltip is too small.
const TEST_DATA = [{ delta: 100, value: 60 }, { delta: 200, value: 59.9 }];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield testGraph(graph);
yield graph.destroy();
host.destroy();
}
function* testGraph(graph) {
yield graph.setDataWhenReady(TEST_DATA);
is(graph._gutter.hidden, false,
"The gutter should not be hidden.");
is(graph._maxTooltip.hidden, true,
"The max tooltip should be hidden.");
is(graph._avgTooltip.hidden, false,
"The avg tooltip should not be hidden.");
is(graph._minTooltip.hidden, false,
"The min tooltip should not be hidden.");
}

View file

@ -0,0 +1,84 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that line graphs hide the gutter and tooltips when there's no data,
// but show them when there is.
const NO_DATA = [];
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield testGraph(graph);
yield graph.destroy();
host.destroy();
}
function* testGraph(graph) {
yield graph.setDataWhenReady(NO_DATA);
is(graph._gutter.hidden, true,
"The gutter should be hidden when there's no data available.");
is(graph._maxTooltip.hidden, true,
"The max tooltip should be hidden when there's no data available.");
is(graph._avgTooltip.hidden, true,
"The avg tooltip should be hidden when there's no data available.");
is(graph._minTooltip.hidden, true,
"The min tooltip should be hidden when there's no data available.");
yield graph.setDataWhenReady(TEST_DATA);
is(graph._gutter.hidden, false,
"The gutter should be visible now.");
is(graph._maxTooltip.hidden, false,
"The max tooltip should be visible now.");
is(graph._avgTooltip.hidden, false,
"The avg tooltip should be visible now.");
is(graph._minTooltip.hidden, false,
"The min tooltip should be visible now.");
yield graph.setDataWhenReady(NO_DATA);
is(graph._gutter.hidden, true,
"The gutter should be hidden again.");
is(graph._maxTooltip.hidden, true,
"The max tooltip should be hidden again.");
is(graph._avgTooltip.hidden, true,
"The avg tooltip should be hidden again.");
is(graph._minTooltip.hidden, true,
"The min tooltip should be hidden again.");
}

View file

@ -0,0 +1,53 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the constructor options for `min`, `max` and `avg` on displaying the
// gutter/tooltips and lines.
const TEST_DATA = [{ delta: 100, value: 60 }, { delta: 200, value: 1 }];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
yield testGraph(doc.body, { avg: false });
yield testGraph(doc.body, { min: false });
yield testGraph(doc.body, { max: false });
yield testGraph(doc.body, { min: false, max: false, avg: false });
yield testGraph(doc.body, {});
host.destroy();
}
function* testGraph(parent, options) {
options.metric = "fps";
let graph = new LineGraphWidget(parent, options);
yield graph.setDataWhenReady(TEST_DATA);
let shouldGutterShow = options.min === false && options.max === false;
is(graph._gutter.hidden, shouldGutterShow,
`The gutter should ${shouldGutterShow ? "" : "not "}be shown`);
is(graph._maxTooltip.hidden, options.max === false,
`The max tooltip should ${options.max === false ? "not " : ""}be shown`);
is(graph._maxGutterLine.hidden, options.max === false,
`The max gutter should ${options.max === false ? "not " : ""}be shown`);
is(graph._minTooltip.hidden, options.min === false,
`The min tooltip should ${options.min === false ? "not " : ""}be shown`);
is(graph._minGutterLine.hidden, options.min === false,
`The min gutter should ${options.min === false ? "not " : ""}be shown`);
is(graph._avgTooltip.hidden, options.avg === false,
`The avg tooltip should ${options.avg === false ? "not " : ""}be shown`);
is(graph._avgGutterLine.hidden, options.avg === false,
`The avg gutter should ${options.avg === false ? "not " : ""}be shown`);
yield graph.destroy();
}

View file

@ -0,0 +1,162 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that graphs properly handle resizing.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost("window");
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
let refreshCount = 0;
graph.on("refresh", () => refreshCount++);
yield testGraph(host, graph);
is(refreshCount, 2, "The graph should've been refreshed 2 times.");
yield graph.destroy();
host.destroy();
}
function* testGraph(host, graph) {
graph.setData(TEST_DATA);
let initialBounds = host.frame.getBoundingClientRect();
host._window.resizeBy(-100, -100);
yield graph.once("refresh");
let newBounds = host.frame.getBoundingClientRect();
is(initialBounds.width - newBounds.width, 100,
"The window was properly resized (1).");
is(initialBounds.height - newBounds.height, 100,
"The window was properly resized (2).");
is(graph.width, newBounds.width * window.devicePixelRatio,
"The graph has the correct width (1).");
is(graph.height, newBounds.height * window.devicePixelRatio,
"The graph has the correct height (1).");
info("Making a selection.");
dragStart(graph, 300);
ok(graph.hasSelectionInProgress(),
"The selection should start (1).");
is(graph.getSelection().start, 300,
"The current selection start value is correct (1).");
is(graph.getSelection().end, 300,
"The current selection end value is correct (1).");
hover(graph, 400);
ok(graph.hasSelectionInProgress(),
"The selection should still be in progress (2).");
is(graph.getSelection().start, 300,
"The current selection start value is correct (2).");
is(graph.getSelection().end, 400,
"The current selection end value is correct (2).");
dragStop(graph, 500);
ok(!graph.hasSelectionInProgress(),
"The selection should have stopped (3).");
is(graph.getSelection().start, 300,
"The current selection start value is correct (3).");
is(graph.getSelection().end, 500,
"The current selection end value is correct (3).");
host._window.resizeBy(100, 100);
yield graph.once("refresh");
let newerBounds = host.frame.getBoundingClientRect();
is(initialBounds.width - newerBounds.width, 0,
"The window was properly resized (3).");
is(initialBounds.height - newerBounds.height, 0,
"The window was properly resized (4).");
is(graph.width, newerBounds.width * window.devicePixelRatio,
"The graph has the correct width (2).");
is(graph.height, newerBounds.height * window.devicePixelRatio,
"The graph has the correct height (2).");
info("Making a new selection.");
dragStart(graph, 200);
ok(graph.hasSelectionInProgress(),
"The selection should start (4).");
is(graph.getSelection().start, 200,
"The current selection start value is correct (4).");
is(graph.getSelection().end, 200,
"The current selection end value is correct (4).");
hover(graph, 300);
ok(graph.hasSelectionInProgress(),
"The selection should still be in progress (5).");
is(graph.getSelection().start, 200,
"The current selection start value is correct (5).");
is(graph.getSelection().end, 300,
"The current selection end value is correct (5).");
dragStop(graph, 400);
ok(!graph.hasSelectionInProgress(),
"The selection should have stopped (6).");
is(graph.getSelection().start, 200,
"The current selection start value is correct (6).");
is(graph.getSelection().end, 400,
"The current selection end value is correct (6).");
}
// EventUtils just doesn't work!
function hover(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
}
function dragStart(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
}
function dragStop(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}

View file

@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that graphs aren't refreshed when the owner window resizes but
// the graph dimensions stay the same.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost("window");
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new LineGraphWidget(doc.body, "fps");
graph.fixedWidth = 200;
graph.fixedHeight = 100;
yield graph.once("ready");
let refreshCount = 0;
let refreshCancelledCount = 0;
graph.on("refresh", () => refreshCount++);
graph.on("refresh-cancelled", () => refreshCancelledCount++);
yield testGraph(host, graph);
is(refreshCount, 0, "The graph shouldn't have been refreshed at all.");
is(refreshCancelledCount, 2, "The graph should've had 2 refresh attempts.");
yield graph.destroy();
host.destroy();
}
function* testGraph(host, graph) {
graph.setData(TEST_DATA);
host._window.resizeBy(-100, -100);
yield graph.once("refresh-cancelled");
host._window.resizeBy(100, 100);
yield graph.once("refresh-cancelled");
}

View file

@ -0,0 +1,109 @@
"use strict";
// Tests that graphs properly handle resizing.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost("window");
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
let refreshCount = 0;
graph.on("refresh", () => refreshCount++);
yield testGraph(host, graph);
is(refreshCount, 2, "The graph should've been refreshed 2 times.");
yield graph.destroy();
host.destroy();
}
function* testGraph(host, graph) {
graph.setData(TEST_DATA);
host._window.resizeTo(500, 500);
yield graph.once("refresh");
let oldBounds = host.frame.getBoundingClientRect();
is(graph._width, oldBounds.width * window.devicePixelRatio,
"The window was properly resized (1).");
is(graph._height, oldBounds.height * window.devicePixelRatio,
"The window was properly resized (1).");
dragStart(graph, 100);
dragStop(graph, 400);
is(graph.getSelection().start, 100,
"The current selection start value is correct (1).");
is(graph.getSelection().end, 400,
"The current selection end value is correct (1).");
info("Making sure the selection updates when the window is resized");
host._window.resizeTo(250, 250);
yield graph.once("refresh");
let newBounds = host.frame.getBoundingClientRect();
is(graph._width, newBounds.width * window.devicePixelRatio,
"The window was properly resized (2).");
is(graph._height, newBounds.height * window.devicePixelRatio,
"The window was properly resized (2).");
let ratio = oldBounds.width / newBounds.width;
info("The window resize ratio is: " + ratio);
is(graph.getSelection().start, Math.round(100 / ratio),
"The current selection start value is correct (2).");
is(graph.getSelection().end, Math.round(400 / ratio),
"The current selection end value is correct (2).");
}
// EventUtils just doesn't work!
function dragStart(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
}
function dragStop(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}

View file

@ -0,0 +1,60 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that bar graph create a legend as expected.
const BarGraphWidget = require("devtools/client/shared/widgets/BarGraphWidget");
const CATEGORIES = [
{ color: "#46afe3", label: "Foo" },
{ color: "#eb5368", label: "Bar" },
{ color: "#70bf53", label: "Baz" }
];
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new BarGraphWidget(doc.body);
yield graph.once("ready");
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.format = CATEGORIES;
graph.setData([{ delta: 0, values: [] }]);
let legendContainer = graph._document.querySelector(".bar-graph-widget-legend");
ok(legendContainer,
"A legend container should be available.");
is(legendContainer.childNodes.length, 3,
"Three legend items should have been created.");
let legendItems = graph._document.querySelectorAll(".bar-graph-widget-legend-item");
is(legendItems.length, 3,
"Three legend items should exist in the entire graph.");
is(legendItems[0].querySelector("[view=color]").style.backgroundColor,
"rgb(70, 175, 227)", "The first legend item has the correct color.");
is(legendItems[1].querySelector("[view=color]").style.backgroundColor,
"rgb(235, 83, 104)", "The second legend item has the correct color.");
is(legendItems[2].querySelector("[view=color]").style.backgroundColor,
"rgb(112, 191, 83)", "The third legend item has the correct color.");
is(legendItems[0].querySelector("[view=label]").textContent, "Foo",
"The first legend item has the correct label.");
is(legendItems[1].querySelector("[view=label]").textContent, "Bar",
"The second legend item has the correct label.");
is(legendItems[2].querySelector("[view=label]").textContent, "Baz",
"The third legend item has the correct label.");
}

View file

@ -0,0 +1,133 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that bar graph's legend items handle mouseover/mouseout.
const BarGraphWidget = require("devtools/client/shared/widgets/BarGraphWidget");
const CATEGORIES = [
{ color: "#46afe3", label: "Foo" },
{ color: "#eb5368", label: "Bar" },
{ color: "#70bf53", label: "Baz" }
];
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new BarGraphWidget(doc.body, 1);
graph.fixedWidth = 200;
graph.fixedHeight = 100;
yield graph.once("ready");
yield testGraph(graph);
yield graph.destroy();
host.destroy();
}
function* testGraph(graph) {
graph.format = CATEGORIES;
graph.dataOffsetX = 1000;
graph.setData([{
delta: 1100, values: [0, 2, 3]
}, {
delta: 1200, values: [1, 0, 2]
}, {
delta: 1300, values: [2, 1, 0]
}, {
delta: 1400, values: [0, 3, 1]
}, {
delta: 1500, values: [3, 0, 2]
}, {
delta: 1600, values: [3, 2, 0]
}]);
/* eslint-disable max-len */
is(graph._blocksBoundingRects.toSource(), "[{type:1, start:0, end:33.33333333333333, top:70, bottom:100}, {type:2, start:0, end:33.33333333333333, top:24, bottom:69}, {type:0, start:34.33333333333333, end:66.66666666666666, top:85, bottom:100}, {type:2, start:34.33333333333333, end:66.66666666666666, top:54, bottom:84}, {type:0, start:67.66666666666666, end:100, top:70, bottom:100}, {type:1, start:67.66666666666666, end:100, top:54, bottom:69}, {type:1, start:101, end:133.33333333333331, top:55, bottom:100}, {type:2, start:101, end:133.33333333333331, top:39, bottom:54}, {type:0, start:134.33333333333331, end:166.66666666666666, top:55, bottom:100}, {type:2, start:134.33333333333331, end:166.66666666666666, top:24, bottom:54}, {type:0, start:167.66666666666666, end:200, top:55, bottom:100}, {type:1, start:167.66666666666666, end:200, top:24, bottom:54}]",
"The correct blocks bounding rects were calculated for the bar graph.");
let legendItems = graph._document.querySelectorAll(".bar-graph-widget-legend-item");
is(legendItems.length, 3,
"Three legend items should exist in the entire graph.");
yield testLegend(graph, 0, {
highlights: "[{type:0, start:34.33333333333333, end:66.66666666666666, top:85, bottom:100}, {type:0, start:67.66666666666666, end:100, top:70, bottom:100}, {type:0, start:134.33333333333331, end:166.66666666666666, top:55, bottom:100}, {type:0, start:167.66666666666666, end:200, top:55, bottom:100}]",
selection: "({start:34.33333333333333, end:200})",
leftmost: "({type:0, start:34.33333333333333, end:66.66666666666666, top:85, bottom:100})",
rightmost: "({type:0, start:167.66666666666666, end:200, top:55, bottom:100})"
});
yield testLegend(graph, 1, {
highlights: "[{type:1, start:0, end:33.33333333333333, top:70, bottom:100}, {type:1, start:67.66666666666666, end:100, top:54, bottom:69}, {type:1, start:101, end:133.33333333333331, top:55, bottom:100}, {type:1, start:167.66666666666666, end:200, top:24, bottom:54}]",
selection: "({start:0, end:200})",
leftmost: "({type:1, start:0, end:33.33333333333333, top:70, bottom:100})",
rightmost: "({type:1, start:167.66666666666666, end:200, top:24, bottom:54})"
});
yield testLegend(graph, 2, {
highlights: "[{type:2, start:0, end:33.33333333333333, top:24, bottom:69}, {type:2, start:34.33333333333333, end:66.66666666666666, top:54, bottom:84}, {type:2, start:101, end:133.33333333333331, top:39, bottom:54}, {type:2, start:134.33333333333331, end:166.66666666666666, top:24, bottom:54}]",
selection: "({start:0, end:166.66666666666666})",
leftmost: "({type:2, start:0, end:33.33333333333333, top:24, bottom:69})",
rightmost: "({type:2, start:134.33333333333331, end:166.66666666666666, top:24, bottom:54})"
});
/* eslint-enable max-len */
}
function* testLegend(graph, index, { highlights, selection, leftmost, rightmost }) {
// Hover.
let legendItems = graph._document.querySelectorAll(".bar-graph-widget-legend-item");
let colorBlock = legendItems[index].querySelector("[view=color]");
let debounced = graph.once("legend-hover");
graph._onLegendMouseOver({ target: colorBlock });
ok(!graph.hasMask(), "The graph shouldn't get highlights immediately.");
let [type, rects] = yield debounced;
ok(graph.hasMask(), "The graph should now have highlights.");
is(type, index,
"The legend item was correctly hovered.");
is(rects.toSource(), highlights,
"The legend item highlighted the correct regions.");
// Unhover.
let unhovered = graph.once("legend-unhover");
graph._onLegendMouseOut();
ok(!graph.hasMask(), "The graph shouldn't have highlights anymore.");
yield unhovered;
ok(true, "The 'legend-mouseout' event was emitted.");
// Select.
let selected = graph.once("legend-selection");
graph._onLegendMouseDown(mockEvent(colorBlock));
ok(graph.hasSelection(), "The graph should now have a selection.");
is(graph.getSelection().toSource(), selection, "The graph has a correct selection.");
let [left, right] = yield selected;
is(left.toSource(), leftmost, "The correct leftmost data block was found.");
is(right.toSource(), rightmost, "The correct rightmost data block was found.");
// Deselect.
graph.dropSelection();
}
function mockEvent(node) {
return {
target: node,
preventDefault: () => {},
stopPropagation: () => {}
};
}

View file

@ -0,0 +1,157 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that canvas graphs can have their selection linked.
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
const BarGraphWidget = require("devtools/client/shared/widgets/BarGraphWidget");
const {CanvasGraphUtils} = require("devtools/client/shared/widgets/Graphs");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let first = document.createElement("div");
first.setAttribute("style", "display: inline-block; width: 100%; height: 50%;");
doc.body.appendChild(first);
let second = document.createElement("div");
second.setAttribute("style", "display: inline-block; width: 100%; height: 50%;");
doc.body.appendChild(second);
let graph1 = new LineGraphWidget(first, "js");
let graph2 = new BarGraphWidget(second);
CanvasGraphUtils.linkAnimation(graph1, graph2);
CanvasGraphUtils.linkSelection(graph1, graph2);
yield graph1.ready();
yield graph2.ready();
testGraphs(graph1, graph2);
yield graph1.destroy();
yield graph2.destroy();
host.destroy();
}
function testGraphs(graph1, graph2) {
info("Making a selection in the first graph.");
dragStart(graph1, 300);
ok(graph1.hasSelectionInProgress(),
"The selection should start (1.1).");
ok(!graph2.hasSelectionInProgress(),
"The selection should not start yet in the second graph (1.2).");
is(graph1.getSelection().start, 300,
"The current selection start value is correct (1.1).");
is(graph2.getSelection().start, 300,
"The current selection start value is correct (1.2).");
is(graph1.getSelection().end, 300,
"The current selection end value is correct (1.1).");
is(graph2.getSelection().end, 300,
"The current selection end value is correct (1.2).");
hover(graph1, 400);
ok(graph1.hasSelectionInProgress(),
"The selection should still be in progress (2.1).");
ok(!graph2.hasSelectionInProgress(),
"The selection should not be in progress in the second graph (2.2).");
is(graph1.getSelection().start, 300,
"The current selection start value is correct (2.1).");
is(graph2.getSelection().start, 300,
"The current selection start value is correct (2.2).");
is(graph1.getSelection().end, 400,
"The current selection end value is correct (2.1).");
is(graph2.getSelection().end, 400,
"The current selection end value is correct (2.2).");
dragStop(graph1, 500);
ok(!graph1.hasSelectionInProgress(),
"The selection should have stopped (3.1).");
ok(!graph2.hasSelectionInProgress(),
"The selection should have stopped (3.2).");
is(graph1.getSelection().start, 300,
"The current selection start value is correct (3.1).");
is(graph2.getSelection().start, 300,
"The current selection start value is correct (3.2).");
is(graph1.getSelection().end, 500,
"The current selection end value is correct (3.1).");
is(graph2.getSelection().end, 500,
"The current selection end value is correct (3.2).");
info("Making a new selection in the second graph.");
dragStart(graph2, 200);
ok(!graph1.hasSelectionInProgress(),
"The selection should not start yet in the first graph (4.1).");
ok(graph2.hasSelectionInProgress(),
"The selection should start (4.2).");
is(graph1.getSelection().start, 200,
"The current selection start value is correct (4.1).");
is(graph2.getSelection().start, 200,
"The current selection start value is correct (4.2).");
is(graph1.getSelection().end, 200,
"The current selection end value is correct (4.1).");
is(graph2.getSelection().end, 200,
"The current selection end value is correct (4.2).");
hover(graph2, 300);
ok(!graph1.hasSelectionInProgress(),
"The selection should not be in progress in the first graph (2.2).");
ok(graph2.hasSelectionInProgress(),
"The selection should still be in progress (5.2).");
is(graph1.getSelection().start, 200,
"The current selection start value is correct (5.1).");
is(graph2.getSelection().start, 200,
"The current selection start value is correct (5.2).");
is(graph1.getSelection().end, 300,
"The current selection end value is correct (5.1).");
is(graph2.getSelection().end, 300,
"The current selection end value is correct (5.2).");
dragStop(graph2, 400);
ok(!graph1.hasSelectionInProgress(),
"The selection should have stopped (6.1).");
ok(!graph2.hasSelectionInProgress(),
"The selection should have stopped (6.2).");
is(graph1.getSelection().start, 200,
"The current selection start value is correct (6.1).");
is(graph2.getSelection().start, 200,
"The current selection start value is correct (6.2).");
is(graph1.getSelection().end, 400,
"The current selection end value is correct (6.1).");
is(graph2.getSelection().end, 400,
"The current selection end value is correct (6.2).");
}
// EventUtils just doesn't work!
function hover(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
}
function dragStart(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
}
function dragStop(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}

View file

@ -0,0 +1,44 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that graph widgets may have a fixed width or height.
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
doc.body.setAttribute("style",
"position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new LineGraphWidget(doc.body, "fps");
graph.fixedWidth = 200;
graph.fixedHeight = 100;
yield graph.ready();
testGraph(host, graph);
yield graph.destroy();
host.destroy();
}
function testGraph(host, graph) {
let bounds = host.frame.getBoundingClientRect();
isnot(graph.width, bounds.width * window.devicePixelRatio,
"The graph should not span all the parent node's width.");
isnot(graph.height, bounds.height * window.devicePixelRatio,
"The graph should not span all the parent node's height.");
is(graph.width, graph.fixedWidth * window.devicePixelRatio,
"The graph has the correct width.");
is(graph.height, graph.fixedHeight * window.devicePixelRatio,
"The graph has the correct height.");
}

View file

@ -0,0 +1,111 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that graph widgets correctly emit mouse input events.
const TEST_DATA = [
{ delta: 112, value: 48 }, { delta: 213, value: 59 },
{ delta: 313, value: 60 }, { delta: 413, value: 59 },
{ delta: 530, value: 59 }, { delta: 646, value: 58 },
{ delta: 747, value: 60 }, { delta: 863, value: 48 },
{ delta: 980, value: 37 }, { delta: 1097, value: 30 },
{ delta: 1213, value: 29 }, { delta: 1330, value: 23 },
{ delta: 1430, value: 10 }, { delta: 1534, value: 17 },
{ delta: 1645, value: 20 }, { delta: 1746, value: 22 },
{ delta: 1846, value: 39 }, { delta: 1963, value: 26 },
{ delta: 2080, value: 27 }, { delta: 2197, value: 35 },
{ delta: 2312, value: 47 }, { delta: 2412, value: 53 },
{ delta: 2514, value: 60 }, { delta: 2630, value: 37 },
{ delta: 2730, value: 36 }, { delta: 2830, value: 37 },
{ delta: 2946, value: 36 }, { delta: 3046, value: 40 },
{ delta: 3163, value: 47 }, { delta: 3280, value: 41 },
{ delta: 3380, value: 35 }, { delta: 3480, value: 27 },
{ delta: 3580, value: 39 }, { delta: 3680, value: 42 },
{ delta: 3780, value: 49 }, { delta: 3880, value: 55 },
{ delta: 3980, value: 60 }, { delta: 4080, value: 60 },
{ delta: 4180, value: 60 }
];
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield testGraph(graph);
yield graph.destroy();
host.destroy();
}
function* testGraph(graph) {
let mouseDownEvents = 0;
let mouseUpEvents = 0;
let scrollEvents = 0;
graph.on("mousedown", () => mouseDownEvents++);
graph.on("mouseup", () => mouseUpEvents++);
graph.on("scroll", () => scrollEvents++);
yield graph.setDataWhenReady(TEST_DATA);
info("Making a selection.");
dragStart(graph, 300);
dragStop(graph, 500);
is(graph.getSelection().start, 300,
"The current selection start value is correct (1).");
is(graph.getSelection().end, 500,
"The current selection end value is correct (1).");
is(mouseDownEvents, 1,
"One mousedown event should have been fired.");
is(mouseUpEvents, 1,
"One mouseup event should have been fired.");
is(scrollEvents, 0,
"No scroll event should have been fired.");
info("Zooming in by scrolling inside the selection.");
scroll(graph, -1000, 400);
is(graph.getSelection().start, 375,
"The current selection start value is correct (2).");
is(graph.getSelection().end, 425,
"The current selection end value is correct (2).");
is(mouseDownEvents, 1,
"No more mousedown events should have been fired.");
is(mouseUpEvents, 1,
"No more mouseup events should have been fired.");
is(scrollEvents, 1,
"One scroll event should have been fired.");
}
// EventUtils just doesn't work!
function dragStart(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseDown({ testX: x, testY: y });
}
function dragStop(graph, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseUp({ testX: x, testY: y });
}
function scroll(graph, wheel, x, y = 1) {
x /= window.devicePixelRatio;
y /= window.devicePixelRatio;
graph._onMouseMove({ testX: x, testY: y });
graph._onMouseWheel({ testX: x, testY: y, detail: wheel });
}

View file

@ -0,0 +1,49 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that graph widgets correctly emit mouse input events.
const FAST_FPS = 60;
const SLOW_FPS = 10;
// Each element represents a second
const FRAMES = [FAST_FPS, FAST_FPS, FAST_FPS, SLOW_FPS, FAST_FPS];
const TEST_DATA = [];
const INTERVAL = 100;
const DURATION = 5000;
var t = 0;
for (let frameRate of FRAMES) {
for (let i = 0; i < frameRate; i++) {
// Duration between frames at this rate
let delta = Math.floor(1000 / frameRate);
t += delta;
TEST_DATA.push(t);
}
}
const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield testGraph(graph);
yield graph.destroy();
host.destroy();
}
function* testGraph(graph) {
console.log("test data", TEST_DATA);
yield graph.setDataFromTimestamps(TEST_DATA, INTERVAL, DURATION);
is(graph._avgTooltip.querySelector("[text=value]").textContent, "50",
"The average tooltip displays the correct value.");
}

View file

@ -0,0 +1,45 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that mounta graphs work as expected.
const MountainGraphWidget = require("devtools/client/shared/widgets/MountainGraphWidget");
const TEST_DATA = [
{ delta: 0, values: [0.1, 0.5, 0.3] },
{ delta: 1, values: [0.25, 0, 0.5] },
{ delta: 2, values: [0.5, 0.25, 0.1] },
{ delta: 3, values: [0, 0.75, 0] },
{ delta: 4, values: [0.75, 0, 0.25] }
];
const SECTIONS = [
{ color: "red" },
{ color: "green" },
{ color: "blue" }
];
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host,, doc] = yield createHost();
let graph = new MountainGraphWidget(doc.body);
yield graph.once("ready");
testGraph(graph);
yield graph.destroy();
host.destroy();
}
function testGraph(graph) {
graph.format = SECTIONS;
graph.setData(TEST_DATA);
ok(true, "The graph didn't throw any erorrs.");
}

View file

@ -0,0 +1,88 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip show & hide methods.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tooltip test">
<vbox flex="1">
<hbox id="box1" flex="1">test1</hbox>
<hbox id="box2" flex="1">test2</hbox>
<hbox id="box3" flex="1">test3</hbox>
<hbox id="box4" flex="1">test4</hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
let useXulWrapper;
function getTooltipContent(doc) {
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "50px";
div.style.boxSizing = "border-box";
div.textContent = "tooltip";
return div;
}
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Run tests for a Tooltip without using a XUL panel");
useXulWrapper = false;
yield runTests(doc);
info("Run tests for a Tooltip with a XUL panel");
useXulWrapper = true;
yield runTests(doc);
});
function* runTests(doc) {
yield addTab("about:blank");
let tooltip = new HTMLTooltip(doc, {useXulWrapper});
info("Set tooltip content");
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
is(tooltip.isVisible(), false, "Tooltip is not visible");
info("Show the tooltip and check the expected events are fired.");
let shown = 0;
tooltip.on("shown", () => shown++);
let onShown = tooltip.once("shown");
tooltip.show(doc.getElementById("box1"));
yield onShown;
is(shown, 1, "Event shown was fired once");
yield waitForReflow(tooltip);
is(tooltip.isVisible(), true, "Tooltip is visible");
info("Hide the tooltip and check the expected events are fired.");
let hidden = 0;
tooltip.on("hidden", () => hidden++);
let onPopupHidden = tooltip.once("hidden");
tooltip.hide();
yield onPopupHidden;
is(hidden, 1, "Event hidden was fired once");
yield waitForReflow(tooltip);
is(tooltip.isVisible(), false, "Tooltip is not visible");
tooltip.destroy();
}

View file

@ -0,0 +1,174 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip is closed when clicking outside of its container.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
htmlns="http://www.w3.org/1999/xhtml"
title="Tooltip test">
<vbox flex="1">
<hbox id="box1" flex="1">test1</hbox>
<hbox id="box2" flex="1">test2</hbox>
<hbox id="box3" flex="1">test3</hbox>
<hbox id="box4" flex="1">test4</hbox>
<iframe id="frame" width="200"></iframe>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
let useXulWrapper;
add_task(function* () {
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Run tests for a Tooltip without using a XUL panel");
useXulWrapper = false;
yield runTests(doc);
info("Run tests for a Tooltip with a XUL panel");
useXulWrapper = true;
yield runTests(doc);
});
function* runTests(doc) {
yield testClickInTooltipContent(doc);
yield testConsumeOutsideClicksFalse(doc);
yield testConsumeOutsideClicksTrue(doc);
yield testConsumeWithRightClick(doc);
yield testClickInOuterIframe(doc);
yield testClickInInnerIframe(doc);
}
function* testClickInTooltipContent(doc) {
info("Test a tooltip is not closed when clicking inside itself");
let tooltip = new HTMLTooltip(doc, {useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
let onTooltipContainerClick = once(tooltip.container, "click");
EventUtils.synthesizeMouseAtCenter(tooltip.container, {}, doc.defaultView);
yield onTooltipContainerClick;
is(tooltip.isVisible(), true, "Tooltip is still visible");
tooltip.destroy();
}
function* testConsumeOutsideClicksFalse(doc) {
info("Test closing a tooltip via click with consumeOutsideClicks: false");
let box4 = doc.getElementById("box4");
let tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: false, useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
let onBox4Clicked = once(box4, "click");
let onHidden = once(tooltip, "hidden");
EventUtils.synthesizeMouseAtCenter(box4, {}, doc.defaultView);
yield onHidden;
yield onBox4Clicked;
is(tooltip.isVisible(), false, "Tooltip is hidden");
tooltip.destroy();
}
function* testConsumeOutsideClicksTrue(doc) {
info("Test closing a tooltip via click with consumeOutsideClicks: true");
let box4 = doc.getElementById("box4");
// Count clicks on box4
let box4clicks = 0;
box4.addEventListener("click", () => box4clicks++);
let tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: true, useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
let onHidden = once(tooltip, "hidden");
EventUtils.synthesizeMouseAtCenter(box4, {}, doc.defaultView);
yield onHidden;
is(box4clicks, 0, "box4 catched no click event");
is(tooltip.isVisible(), false, "Tooltip is hidden");
tooltip.destroy();
}
function* testConsumeWithRightClick(doc) {
info("Test closing a tooltip with a right-click, with consumeOutsideClicks: true");
let box4 = doc.getElementById("box4");
let tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: true, useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
// Only left-click events should be consumed, so we expect to catch a click when using
// {button: 2}, which simulates a right-click.
info("Right click on box4, expect tooltip to be hidden, event should not be consumed");
let onBox4Clicked = once(box4, "click");
let onHidden = once(tooltip, "hidden");
EventUtils.synthesizeMouseAtCenter(box4, {button: 2}, doc.defaultView);
yield onHidden;
yield onBox4Clicked;
is(tooltip.isVisible(), false, "Tooltip is hidden");
tooltip.destroy();
}
function* testClickInOuterIframe(doc) {
info("Test clicking an iframe outside of the tooltip closes the tooltip");
let frame = doc.getElementById("frame");
let tooltip = new HTMLTooltip(doc, {useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
let onHidden = once(tooltip, "hidden");
EventUtils.synthesizeMouseAtCenter(frame, {}, doc.defaultView);
yield onHidden;
is(tooltip.isVisible(), false, "Tooltip is hidden");
tooltip.destroy();
}
function* testClickInInnerIframe(doc) {
info("Test clicking an iframe inside the tooltip content does not close the tooltip");
let tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: false, useXulWrapper});
let iframe = doc.createElementNS(HTML_NS, "iframe");
iframe.style.width = "100px";
iframe.style.height = "50px";
tooltip.setContent(iframe, {width: 100, height: 50});
yield showTooltip(tooltip, doc.getElementById("box1"));
let onTooltipContainerClick = once(tooltip.container, "click");
EventUtils.synthesizeMouseAtCenter(tooltip.container, {}, doc.defaultView);
yield onTooltipContainerClick;
is(tooltip.isVisible(), true, "Tooltip is still visible");
tooltip.destroy();
}
function getTooltipContent(doc) {
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "50px";
div.style.boxSizing = "border-box";
div.textContent = "tooltip";
return div;
}

View file

@ -0,0 +1,155 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip autofocus configuration option.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tooltip test">
<vbox flex="1">
<hbox id="box1" flex="1">
<textbox></textbox>
</hbox>
<hbox id="box2" flex="1">test2</hbox>
<hbox id="box3" flex="1">
<textbox id="box3-input"></textbox>
</hbox>
<hbox id="box4" flex="1">
<textbox id="box4-input"></textbox>
</hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
let useXulWrapper;
add_task(function* () {
yield addTab("about:blank");
let [, , doc] = yield createHost("bottom", TEST_URI);
info("Run tests for a Tooltip without using a XUL panel");
useXulWrapper = false;
yield runTests(doc);
info("Run tests for a Tooltip with a XUL panel");
useXulWrapper = true;
yield runTests(doc);
});
function* runTests(doc) {
yield testNoAutoFocus(doc);
yield testAutoFocus(doc);
yield testAutoFocusPreservesFocusChange(doc);
}
function* testNoAutoFocus(doc) {
yield focusNode(doc, "#box4-input");
ok(doc.activeElement.closest("#box4-input"), "Focus is in the #box4-input");
info("Test a tooltip without autofocus will not take focus");
let tooltip = yield createTooltip(doc, false);
yield showTooltip(tooltip, doc.getElementById("box1"));
ok(doc.activeElement.closest("#box4-input"), "Focus is still in the #box4-input");
yield hideTooltip(tooltip);
yield blurNode(doc, "#box4-input");
tooltip.destroy();
}
function* testAutoFocus(doc) {
yield focusNode(doc, "#box4-input");
ok(doc.activeElement.closest("#box4-input"), "Focus is in the #box4-input");
info("Test autofocus tooltip takes focus when displayed, " +
"and restores the focus when hidden");
let tooltip = yield createTooltip(doc, true);
yield showTooltip(tooltip, doc.getElementById("box1"));
ok(doc.activeElement.closest(".tooltip-content"), "Focus is in the tooltip");
yield hideTooltip(tooltip);
ok(doc.activeElement.closest("#box4-input"), "Focus is in the #box4-input");
info("Blur the textbox before moving to the next test to reset the state.");
yield blurNode(doc, "#box4-input");
tooltip.destroy();
}
function* testAutoFocusPreservesFocusChange(doc) {
yield focusNode(doc, "#box4-input");
ok(doc.activeElement.closest("#box4-input"), "Focus is still in the #box3-input");
info("Test autofocus tooltip takes focus when displayed, " +
"but does not try to restore the active element if it is not focused when hidden");
let tooltip = yield createTooltip(doc, true);
yield showTooltip(tooltip, doc.getElementById("box1"));
ok(doc.activeElement.closest(".tooltip-content"), "Focus is in the tooltip");
info("Move the focus to #box3-input while the tooltip is displayed");
yield focusNode(doc, "#box3-input");
ok(doc.activeElement.closest("#box3-input"), "Focus moved to the #box3-input");
yield hideTooltip(tooltip);
ok(doc.activeElement.closest("#box3-input"), "Focus is still in the #box3-input");
info("Blur the textbox before moving to the next test to reset the state.");
yield blurNode(doc, "#box3-input");
tooltip.destroy();
}
/**
* Fpcus the node corresponding to the provided selector in the provided document. Returns
* a promise that will resolve when receiving the focus event on the node.
*/
function focusNode(doc, selector) {
let node = doc.querySelector(selector);
let onFocus = once(node, "focus");
node.focus();
return onFocus;
}
/**
* Blur the node corresponding to the provided selector in the provided document. Returns
* a promise that will resolve when receiving the blur event on the node.
*/
function blurNode(doc, selector) {
let node = doc.querySelector(selector);
let onBlur = once(node, "blur");
node.blur();
return onBlur;
}
/**
* Create an HTMLTooltip instance with the provided autofocus setting.
*
* @param {Document} doc
* Document in which the tooltip should be created
* @param {Boolean} autofocus
* @return {Promise} promise that will resolve the HTMLTooltip instance created when the
* tooltip content will be ready.
*/
function* createTooltip(doc, autofocus) {
let tooltip = new HTMLTooltip(doc, {autofocus, useXulWrapper});
let div = doc.createElementNS(HTML_NS, "div");
div.classList.add("tooltip-content");
div.style.height = "50px";
div.innerHTML = '<input type="text"></input>';
tooltip.setContent(div, {width: 150, height: 50});
return tooltip;
}

View file

@ -0,0 +1,110 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip positioning for a small tooltip element (should aways
* find a way to fit).
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tooltip test">
<vbox flex="1">
<hbox style="height: 10px">spacer</hbox>
<hbox id="box1" style="height: 50px">test1</hbox>
<hbox id="box2" style="height: 50px">test2</hbox>
<hbox flex="1">MIDDLE</hbox>
<hbox id="box3" style="height: 50px">test3</hbox>
<hbox id="box4" style="height: 50px">test4</hbox>
<hbox style="height: 10px">spacer</hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
const TOOLTIP_HEIGHT = 30;
const TOOLTIP_WIDTH = 100;
add_task(function* () {
// Force the toolbox to be 400px high;
yield pushPref("devtools.toolbox.footer.height", 400);
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Create HTML tooltip");
let tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "100%";
tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
let box1 = doc.getElementById("box1");
let box2 = doc.getElementById("box2");
let box3 = doc.getElementById("box3");
let box4 = doc.getElementById("box4");
let height = TOOLTIP_HEIGHT, width = TOOLTIP_WIDTH;
// box1: Can only fit below box1
info("Display the tooltip on box1.");
yield showTooltip(tooltip, box1);
let expectedTooltipGeometry = {position: "bottom", height, width};
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
yield hideTooltip(tooltip);
info("Try to display the tooltip on top of box1.");
yield showTooltip(tooltip, box1, {position: "top"});
expectedTooltipGeometry = {position: "bottom", height, width};
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
yield hideTooltip(tooltip);
// box2: Can fit above or below, will default to bottom, more height
// available.
info("Try to display the tooltip on box2.");
yield showTooltip(tooltip, box2);
expectedTooltipGeometry = {position: "bottom", height, width};
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
yield hideTooltip(tooltip);
info("Try to display the tooltip on top of box2.");
yield showTooltip(tooltip, box2, {position: "top"});
expectedTooltipGeometry = {position: "top", height, width};
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
yield hideTooltip(tooltip);
// box3: Can fit above or below, will default to top, more height available.
info("Try to display the tooltip on box3.");
yield showTooltip(tooltip, box3);
expectedTooltipGeometry = {position: "top", height, width};
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
yield hideTooltip(tooltip);
info("Try to display the tooltip on bottom of box3.");
yield showTooltip(tooltip, box3, {position: "bottom"});
expectedTooltipGeometry = {position: "bottom", height, width};
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
yield hideTooltip(tooltip);
// box4: Can only fit above box4
info("Display the tooltip on box4.");
yield showTooltip(tooltip, box4);
expectedTooltipGeometry = {position: "top", height, width};
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
yield hideTooltip(tooltip);
info("Try to display the tooltip on bottom of box4.");
yield showTooltip(tooltip, box4, {position: "bottom"});
expectedTooltipGeometry = {position: "top", height, width};
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
yield hideTooltip(tooltip);
is(tooltip.isVisible(), false, "Tooltip is not visible");
tooltip.destroy();
});

View file

@ -0,0 +1,109 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip positioning for a huge tooltip element (can not fit in
* the viewport).
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tooltip test">
<vbox flex="1">
<hbox id="box1" style="height: 50px">test1</hbox>
<hbox id="box2" style="height: 50px">test2</hbox>
<hbox id="box3" style="height: 50px">test3</hbox>
<hbox id="box4" style="height: 50px">test4</hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
const TOOLTIP_HEIGHT = 200;
const TOOLTIP_WIDTH = 200;
add_task(function* () {
// Force the toolbox to be 200px high;
yield pushPref("devtools.toolbox.footer.height", 200);
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Create HTML tooltip");
let tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "100%";
tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
let box1 = doc.getElementById("box1");
let box2 = doc.getElementById("box2");
let box3 = doc.getElementById("box3");
let box4 = doc.getElementById("box4");
let width = TOOLTIP_WIDTH;
// box1: Can not fit above or below box1, default to bottom with a reduced
// height of 150px.
info("Display the tooltip on box1.");
yield showTooltip(tooltip, box1);
let expectedTooltipGeometry = {position: "bottom", height: 150, width};
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
yield hideTooltip(tooltip);
info("Try to display the tooltip on top of box1.");
yield showTooltip(tooltip, box1, {position: "top"});
expectedTooltipGeometry = {position: "bottom", height: 150, width};
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
yield hideTooltip(tooltip);
// box2: Can not fit above or below box2, default to bottom with a reduced
// height of 100px.
info("Try to display the tooltip on box2.");
yield showTooltip(tooltip, box2);
expectedTooltipGeometry = {position: "bottom", height: 100, width};
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
yield hideTooltip(tooltip);
info("Try to display the tooltip on top of box2.");
yield showTooltip(tooltip, box2, {position: "top"});
expectedTooltipGeometry = {position: "bottom", height: 100, width};
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
yield hideTooltip(tooltip);
// box3: Can not fit above or below box3, default to top with a reduced height
// of 100px.
info("Try to display the tooltip on box3.");
yield showTooltip(tooltip, box3);
expectedTooltipGeometry = {position: "top", height: 100, width};
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
yield hideTooltip(tooltip);
info("Try to display the tooltip on bottom of box3.");
yield showTooltip(tooltip, box3, {position: "bottom"});
expectedTooltipGeometry = {position: "top", height: 100, width};
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
yield hideTooltip(tooltip);
// box4: Can not fit above or below box4, default to top with a reduced height
// of 150px.
info("Display the tooltip on box4.");
yield showTooltip(tooltip, box4);
expectedTooltipGeometry = {position: "top", height: 150, width};
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
yield hideTooltip(tooltip);
info("Try to display the tooltip on bottom of box4.");
yield showTooltip(tooltip, box4, {position: "bottom"});
expectedTooltipGeometry = {position: "top", height: 150, width};
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
yield hideTooltip(tooltip);
is(tooltip.isVisible(), false, "Tooltip is not visible");
tooltip.destroy();
});

View file

@ -0,0 +1,108 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip "arrow" type on small anchors. The arrow should remain
* aligned with the anchors as much as possible
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const getAnchor = function (position) {
return `<html:div class="anchor" style="width:10px;
height: 10px;
position: absolute;
background: red;
${position}"></html:div>`;
};
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?>
<window class="theme-light"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="Tooltip test">
<vbox flex="1" style="position: relative">
${getAnchor("top: 0; left: 0;")}
${getAnchor("top: 0; left: 25px;")}
${getAnchor("top: 0; left: 50px;")}
${getAnchor("top: 0; left: 75px;")}
${getAnchor("bottom: 0; left: 0;")}
${getAnchor("bottom: 0; left: 25px;")}
${getAnchor("bottom: 0; left: 50px;")}
${getAnchor("bottom: 0; left: 75px;")}
${getAnchor("bottom: 0; right: 0;")}
${getAnchor("bottom: 0; right: 25px;")}
${getAnchor("bottom: 0; right: 50px;")}
${getAnchor("bottom: 0; right: 75px;")}
${getAnchor("top: 0; right: 0;")}
${getAnchor("top: 0; right: 25px;")}
${getAnchor("top: 0; right: 50px;")}
${getAnchor("top: 0; right: 75px;")}
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
let useXulWrapper;
add_task(function* () {
// Force the toolbox to be 200px high;
yield pushPref("devtools.toolbox.footer.height", 200);
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Run tests for a Tooltip without using a XUL panel");
useXulWrapper = false;
yield runTests(doc);
info("Run tests for a Tooltip with a XUL panel");
useXulWrapper = true;
yield runTests(doc);
});
function* runTests(doc) {
info("Create HTML tooltip");
let tooltip = new HTMLTooltip(doc, {type: "arrow", useXulWrapper});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "35px";
tooltip.setContent(div, {width: 200, height: 35});
let {right: docRight} = doc.documentElement.getBoundingClientRect();
let elements = [...doc.querySelectorAll(".anchor")];
for (let el of elements) {
info("Display the tooltip on an anchor.");
yield showTooltip(tooltip, el);
let arrow = tooltip.arrow;
ok(arrow, "Tooltip has an arrow");
// Get the geometry of the anchor, the tooltip panel & arrow.
let arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].bounds;
let panelBounds = tooltip.panel.getBoxQuads({relativeTo: doc})[0].bounds;
let anchorBounds = el.getBoxQuads({relativeTo: doc})[0].bounds;
let intersects = arrowBounds.left <= anchorBounds.right &&
arrowBounds.right >= anchorBounds.left;
let isBlockedByViewport = arrowBounds.left == 0 ||
arrowBounds.right == docRight;
ok(intersects || isBlockedByViewport,
"Tooltip arrow is aligned with the anchor, or stuck on viewport's edge.");
let isInPanel = arrowBounds.left >= panelBounds.left &&
arrowBounds.right <= panelBounds.right;
ok(isInPanel,
"The tooltip arrow remains inside the tooltip panel horizontally");
yield hideTooltip(tooltip);
}
tooltip.destroy();
}

View file

@ -0,0 +1,100 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip "arrow" type on wide anchors. The arrow should remain
* aligned with the anchors as much as possible
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const getAnchor = function (position) {
return `<html:div class="anchor" style="height: 5px;
position: absolute;
background: red;
${position}"></html:div>`;
};
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?>
<window class="theme-light"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="Tooltip test">
<vbox flex="1" style="position: relative">
${getAnchor("top: 0; left: 0; width: 50px;")}
${getAnchor("top: 10px; left: 0; width: 100px;")}
${getAnchor("top: 20px; left: 0; width: 150px;")}
${getAnchor("top: 30px; left: 0; width: 200px;")}
${getAnchor("top: 40px; left: 0; width: 250px;")}
${getAnchor("top: 50px; left: 100px; width: 250px;")}
${getAnchor("top: 100px; width: 50px; right: 0;")}
${getAnchor("top: 110px; width: 100px; right: 0;")}
${getAnchor("top: 120px; width: 150px; right: 0;")}
${getAnchor("top: 130px; width: 200px; right: 0;")}
${getAnchor("top: 140px; width: 250px; right: 0;")}
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
let useXulWrapper;
add_task(function* () {
// Force the toolbox to be 200px high;
yield pushPref("devtools.toolbox.footer.height", 200);
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Run tests for a Tooltip without using a XUL panel");
useXulWrapper = false;
yield runTests(doc);
info("Run tests for a Tooltip with a XUL panel");
useXulWrapper = true;
yield runTests(doc);
});
function* runTests(doc) {
info("Create HTML tooltip");
let tooltip = new HTMLTooltip(doc, {type: "arrow", useXulWrapper});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "35px";
tooltip.setContent(div, {width: 200, height: 35});
let {right: docRight} = doc.documentElement.getBoundingClientRect();
let elements = [...doc.querySelectorAll(".anchor")];
for (let el of elements) {
info("Display the tooltip on an anchor.");
yield showTooltip(tooltip, el);
let arrow = tooltip.arrow;
ok(arrow, "Tooltip has an arrow");
// Get the geometry of the anchor, the tooltip panel & arrow.
let arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].bounds;
let panelBounds = tooltip.panel.getBoxQuads({relativeTo: doc})[0].bounds;
let anchorBounds = el.getBoxQuads({relativeTo: doc})[0].bounds;
let intersects = arrowBounds.left <= anchorBounds.right &&
arrowBounds.right >= anchorBounds.left;
let isBlockedByViewport = arrowBounds.left == 0 ||
arrowBounds.right == docRight;
ok(intersects || isBlockedByViewport,
"Tooltip arrow is aligned with the anchor, or stuck on viewport's edge.");
let isInPanel = arrowBounds.left >= panelBounds.left &&
arrowBounds.right <= panelBounds.right;
ok(isInPanel,
"The tooltip arrow remains inside the tooltip panel horizontally");
yield hideTooltip(tooltip);
}
tooltip.destroy();
}

View file

@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip show can be called several times. It should move according to the
* new anchor/options and should not leak event listeners.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tooltip test">
<vbox flex="1">
<hbox id="box1" flex="1">test1</hbox>
<hbox id="box2" flex="1">test2</hbox>
<hbox id="box3" flex="1">test3</hbox>
<hbox id="box4" flex="1">test4</hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
function getTooltipContent(doc) {
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "50px";
div.textContent = "tooltip";
return div;
}
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
let box1 = doc.getElementById("box1");
let box2 = doc.getElementById("box2");
let box3 = doc.getElementById("box3");
let box4 = doc.getElementById("box4");
let width = 100, height = 50;
let tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
tooltip.setContent(getTooltipContent(doc), {width, height});
info("Show the tooltip on each of the 4 hbox, without calling hide in between");
info("Show tooltip on box1");
tooltip.show(box1);
checkTooltipGeometry(tooltip, box1, {position: "bottom", width, height});
info("Show tooltip on box2");
tooltip.show(box2);
checkTooltipGeometry(tooltip, box2, {position: "bottom", width, height});
info("Show tooltip on box3");
tooltip.show(box3);
checkTooltipGeometry(tooltip, box3, {position: "top", width, height});
info("Show tooltip on box4");
tooltip.show(box4);
checkTooltipGeometry(tooltip, box4, {position: "top", width, height});
info("Hide tooltip before leaving test");
yield hideTooltip(tooltip);
tooltip.destroy();
});

View file

@ -0,0 +1,65 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the TooltipToggle helper class for HTMLTooltip
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="resource://devtools/client/themes/variables.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
class="theme-light" title="Tooltip hover test">
<vbox id="container" flex="1">
<hbox id="box1" flex="1"><label>test1</label></hbox>
<hbox id="box2" flex="1"><label>test2</label></hbox>
<hbox id="box3" flex="1"><label>test3</label></hbox>
<hbox id="box4" flex="1"><label>test4</label></hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
add_task(function* () {
let [,, doc] = yield createHost("bottom", TEST_URI);
// Wait for full page load before synthesizing events on the page.
yield waitUntil(() => doc.readyState === "complete");
let width = 100, height = 50;
let tooltipContent = doc.createElementNS(HTML_NS, "div");
tooltipContent.textContent = "tooltip";
let tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
tooltip.setContent(tooltipContent, {width, height});
let container = doc.getElementById("container");
tooltip.startTogglingOnHover(container, () => true);
info("Hover on each of the 4 boxes, expect the tooltip to appear");
function* showAndCheck(boxId, position) {
info(`Show tooltip on ${boxId}`);
let box = doc.getElementById(boxId);
let shown = tooltip.once("shown");
EventUtils.synthesizeMouseAtCenter(box, { type: "mousemove" }, doc.defaultView);
yield shown;
checkTooltipGeometry(tooltip, box, {position, width, height});
}
yield showAndCheck("box1", "bottom");
yield showAndCheck("box2", "bottom");
yield showAndCheck("box3", "top");
yield showAndCheck("box4", "top");
info("Move out of the container");
let hidden = tooltip.once("hidden");
EventUtils.synthesizeMouseAtCenter(container, { type: "mouseout" }, doc.defaultView);
yield hidden;
info("Destroy the tooltip and finish");
tooltip.destroy();
});

View file

@ -0,0 +1,99 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip can be displayed with vertical and horizontal offsets.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
htmlns="http://www.w3.org/1999/xhtml"
title="Tooltip test">
<vbox flex="1">
<hbox id="box1" flex="1">test1</hbox>
<hbox id="box2" flex="1">test2</hbox>
<hbox id="box3" flex="1">test3</hbox>
<hbox id="box4" flex="1">test4</hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
add_task(function* () {
// Force the toolbox to be 200px high;
yield pushPref("devtools.toolbox.footer.height", 200);
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Test a tooltip is not closed when clicking inside itself");
let box1 = doc.getElementById("box1");
let box2 = doc.getElementById("box2");
let box3 = doc.getElementById("box3");
let box4 = doc.getElementById("box4");
let tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "100px";
div.style.boxSizing = "border-box";
div.textContent = "tooltip";
tooltip.setContent(div, {width: 50, height: 100});
info("Display the tooltip on box1.");
yield showTooltip(tooltip, box1, {x: 5, y: 10});
let panelRect = tooltip.container.getBoundingClientRect();
let anchorRect = box1.getBoundingClientRect();
// Tooltip will be displayed below box1
is(panelRect.top, anchorRect.bottom + 10, "Tooltip top has 10px offset");
is(panelRect.left, anchorRect.left + 5, "Tooltip left has 5px offset");
is(panelRect.height, 100, "Tooltip height is at 100px as expected");
info("Display the tooltip on box2.");
yield showTooltip(tooltip, box2, {x: 5, y: 10});
panelRect = tooltip.container.getBoundingClientRect();
anchorRect = box2.getBoundingClientRect();
// Tooltip will be displayed below box2, but can't be fully displayed because of the
// offset
is(panelRect.top, anchorRect.bottom + 10, "Tooltip top has 10px offset");
is(panelRect.left, anchorRect.left + 5, "Tooltip left has 5px offset");
is(panelRect.height, 90, "Tooltip height is only 90px");
info("Display the tooltip on box3.");
yield showTooltip(tooltip, box3, {x: 5, y: 10});
panelRect = tooltip.container.getBoundingClientRect();
anchorRect = box3.getBoundingClientRect();
// Tooltip will be displayed above box3, but can't be fully displayed because of the
// offset
is(panelRect.bottom, anchorRect.top - 10, "Tooltip bottom is 10px above anchor");
is(panelRect.left, anchorRect.left + 5, "Tooltip left has 5px offset");
is(panelRect.height, 90, "Tooltip height is only 90px");
info("Display the tooltip on box4.");
yield showTooltip(tooltip, box4, {x: 5, y: 10});
panelRect = tooltip.container.getBoundingClientRect();
anchorRect = box4.getBoundingClientRect();
// Tooltip will be displayed above box4
is(panelRect.bottom, anchorRect.top - 10, "Tooltip bottom is 10px above anchor");
is(panelRect.left, anchorRect.left + 5, "Tooltip left has 5px offset");
is(panelRect.height, 100, "Tooltip height is at 100px as expected");
yield hideTooltip(tooltip);
tooltip.destroy();
});

View file

@ -0,0 +1,140 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip anchor alignment changes with the anchor direction.
* - should be aligned to the right of RTL anchors
* - should be aligned to the left of LTR anchors
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
htmlns="http://www.w3.org/1999/xhtml"
title="Tooltip test">
<hbox style="padding: 90px 0;" flex="1">
<hbox id="box1" flex="1" style="background:red; direction: rtl;">test1</hbox>
<hbox id="box2" flex="1" style="background:blue; direction: rtl;">test2</hbox>
<hbox id="box3" flex="1" style="background:red; direction: ltr;">test3</hbox>
<hbox id="box4" flex="1" style="background:blue; direction: ltr;">test4</hbox>
</hbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
const TOOLBOX_WIDTH = 500;
const TOOLTIP_WIDTH = 150;
const TOOLTIP_HEIGHT = 30;
add_task(function* () {
// Force the toolbox to be 500px wide (min width is 465px);
yield pushPref("devtools.toolbox.sidebar.width", TOOLBOX_WIDTH);
let [,, doc] = yield createHost("side", TEST_URI);
info("Test a tooltip is not closed when clicking inside itself");
let tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
let div = doc.createElementNS(HTML_NS, "div");
div.textContent = "tooltip";
div.style.cssText = "box-sizing: border-box; border: 1px solid black";
tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
yield testRtlAnchors(doc, tooltip);
yield testLtrAnchors(doc, tooltip);
yield hideTooltip(tooltip);
tooltip.destroy();
});
function* testRtlAnchors(doc, tooltip) {
/*
* The layout of the test page is as follows:
* _______________________________
* | toolbox |
* | _____ _____ _____ _____ |
* || | | | | | | ||
* || box1| | box2| | box3| | box4||
* ||_____| |_____| |_____| |_____||
* |_______________________________|
*
* - box1 is aligned with the left edge of the toolbox
* - box2 is displayed right after box1
* - total toolbox width is 500px so each box is 125px wide
*/
let box1 = doc.getElementById("box1");
let box2 = doc.getElementById("box2");
info("Display the tooltip on box1.");
yield showTooltip(tooltip, box1, {position: "bottom"});
let panelRect = tooltip.container.getBoundingClientRect();
let anchorRect = box1.getBoundingClientRect();
// box1 uses RTL direction, so the tooltip should be aligned with the right edge of the
// anchor, but it is shifted to the right to fit in the toolbox.
is(panelRect.left, 0, "Tooltip is aligned with left edge of the toolbox");
is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
info("Display the tooltip on box2.");
yield showTooltip(tooltip, box2, {position: "bottom"});
panelRect = tooltip.container.getBoundingClientRect();
anchorRect = box2.getBoundingClientRect();
// box2 uses RTL direction, so the tooltip is aligned with the right edge of the anchor
is(panelRect.right, anchorRect.right, "Tooltip is aligned with right edge of anchor");
is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
}
function* testLtrAnchors(doc, tooltip) {
/*
* The layout of the test page is as follows:
* _______________________________
* | toolbox |
* | _____ _____ _____ _____ |
* || | | | | | | ||
* || box1| | box2| | box3| | box4||
* ||_____| |_____| |_____| |_____||
* |_______________________________|
*
* - box3 is is displayed right after box2
* - box4 is aligned with the right edge of the toolbox
* - total toolbox width is 500px so each box is 125px wide
*/
let box3 = doc.getElementById("box3");
let box4 = doc.getElementById("box4");
info("Display the tooltip on box3.");
yield showTooltip(tooltip, box3, {position: "bottom"});
let panelRect = tooltip.container.getBoundingClientRect();
let anchorRect = box3.getBoundingClientRect();
// box3 uses LTR direction, so the tooltip is aligned with the left edge of the anchor.
is(panelRect.left, anchorRect.left, "Tooltip is aligned with left edge of anchor");
is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
info("Display the tooltip on box4.");
yield showTooltip(tooltip, box4, {position: "bottom"});
panelRect = tooltip.container.getBoundingClientRect();
anchorRect = box4.getBoundingClientRect();
// box4 uses LTR direction, so the tooltip should be aligned with the left edge of the
// anchor, but it is shifted to the left to fit in the toolbox.
is(panelRect.right, TOOLBOX_WIDTH, "Tooltip is aligned with right edge of toolbox");
is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
}

View file

@ -0,0 +1,75 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip content can have a variable height.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tooltip test">
<vbox flex="1">
<hbox id="box1" flex="1">test1</hbox>
<hbox id="box2" flex="1">test2</hbox>
<hbox id="box3" flex="1">test3</hbox>
<hbox id="box4" flex="1">test4</hbox>
</vbox>
</window>`;
const CONTAINER_HEIGHT = 300;
const CONTAINER_WIDTH = 200;
const TOOLTIP_HEIGHT = 50;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
add_task(function* () {
// Force the toolbox to be 400px tall => 50px for each box.
yield pushPref("devtools.toolbox.footer.height", 400);
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
let tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
info("Set tooltip content 50px tall, but request a container 200px tall");
let tooltipContent = doc.createElementNS(HTML_NS, "div");
tooltipContent.style.cssText = "height: " + TOOLTIP_HEIGHT + "px; background: red;";
tooltip.setContent(tooltipContent, {width: CONTAINER_WIDTH, height: Infinity});
info("Show the tooltip and check the container and panel height.");
yield showTooltip(tooltip, doc.getElementById("box1"));
let containerRect = tooltip.container.getBoundingClientRect();
let panelRect = tooltip.panel.getBoundingClientRect();
is(containerRect.height, CONTAINER_HEIGHT,
"Tooltip container has the expected height.");
is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip panel has the expected height.");
info("Click below the tooltip panel but in the tooltip filler element.");
let onHidden = once(tooltip, "hidden");
EventUtils.synthesizeMouse(tooltip.container, 100, 100, {}, doc.defaultView);
yield onHidden;
info("Show the tooltip one more time, and increase the content height");
yield showTooltip(tooltip, doc.getElementById("box1"));
tooltipContent.style.height = (2 * CONTAINER_HEIGHT) + "px";
info("Click at the same coordinates as earlier, this time it should hit the tooltip.");
let onPanelClick = once(tooltip.panel, "click");
EventUtils.synthesizeMouse(tooltip.container, 100, 100, {}, doc.defaultView);
yield onPanelClick;
is(tooltip.isVisible(), true, "Tooltip is still visible");
info("Click above the tooltip container, the tooltip should be closed.");
onHidden = once(tooltip, "hidden");
EventUtils.synthesizeMouse(tooltip.container, 100, -10, {}, doc.defaultView);
yield onHidden;
tooltip.destroy();
});

View file

@ -0,0 +1,61 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip content can automatically calculate its width based on content.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tooltip test">
<vbox flex="1">
<hbox id="box1" flex="1">test1</hbox>
<hbox id="box2" flex="1">test2</hbox>
<hbox id="box3" flex="1">test3</hbox>
<hbox id="box4" flex="1">test4</hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
let useXulWrapper;
add_task(function* () {
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
info("Run tests for a Tooltip without using a XUL panel");
useXulWrapper = false;
yield runTests(doc);
info("Run tests for a Tooltip with a XUL panel");
useXulWrapper = true;
yield runTests(doc);
});
function* runTests(doc) {
let tooltip = new HTMLTooltip(doc, {useXulWrapper});
info("Create tooltip content width to 150px");
let tooltipContent = doc.createElementNS(HTML_NS, "div");
tooltipContent.style.cssText = "height: 100%; width: 150px; background: red;";
info("Set tooltip content using width:auto");
tooltip.setContent(tooltipContent, {width: "auto", height: 50});
info("Show the tooltip and check the tooltip panel width.");
yield showTooltip(tooltip, doc.getElementById("box1"));
let panelRect = tooltip.panel.getBoundingClientRect();
is(panelRect.width, 150, "Tooltip panel has the expected width.");
yield hideTooltip(tooltip);
tooltip.destroy();
}

View file

@ -0,0 +1,80 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip can overflow out of the toolbox when using a XUL panel wrapper.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tooltip test">
<vbox flex="1">
<hbox id="box1" style="height: 50px">test1</hbox>
<hbox id="box2" style="height: 50px">test2</hbox>
<hbox id="box3" style="height: 50px">test3</hbox>
<hbox id="box4" style="height: 50px">test4</hbox>
</vbox>
</window>`;
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
// The test toolbox will be 200px tall, the anchors are 50px tall, therefore, the maximum
// tooltip height that could fit in the toolbox is 150px. Setting 160px, the tooltip will
// either have to overflow or to be resized.
const TOOLTIP_HEIGHT = 160;
const TOOLTIP_WIDTH = 200;
add_task(function* () {
// Force the toolbox to be 200px high;
yield pushPref("devtools.toolbox.footer.height", 200);
let [, win, doc] = yield createHost("bottom", TEST_URI);
info("Resizing window to have some space below the window.");
let originalWidth = win.top.outerWidth;
let originalHeight = win.top.outerHeight;
win.top.resizeBy(0, -100);
info("Create HTML tooltip");
let tooltip = new HTMLTooltip(doc, {useXulWrapper: true});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "200px";
div.style.background = "red";
tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
let box1 = doc.getElementById("box1");
// Above box1: check that the tooltip can overflow onto the content page.
info("Display the tooltip above box1.");
yield showTooltip(tooltip, box1, {position: "top"});
checkTooltip(tooltip, "top", TOOLTIP_HEIGHT);
yield hideTooltip(tooltip);
// Below box1: check that the tooltip can overflow out of the browser window.
info("Display the tooltip below box1.");
yield showTooltip(tooltip, box1, {position: "bottom"});
checkTooltip(tooltip, "bottom", TOOLTIP_HEIGHT);
yield hideTooltip(tooltip);
is(tooltip.isVisible(), false, "Tooltip is not visible");
tooltip.destroy();
info("Restore original window dimensions.");
win.top.resizeTo(originalWidth, originalHeight);
});
function checkTooltip(tooltip, position, height) {
is(tooltip.position, position, "Actual tooltip position is " + position);
let rect = tooltip.container.getBoundingClientRect();
is(rect.height, height, "Actual tooltip height is " + height);
// Testing the actual left/top offsets is not relevant here as it is handled by the XUL
// panel.
}

View file

@ -0,0 +1,150 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_inplace_editor.js */
"use strict";
loadHelperScript("helper_inplace_editor.js");
// Test the inplace-editor behavior.
add_task(function* () {
yield addTab("data:text/html;charset=utf-8,inline editor tests");
let [host, , doc] = yield createHost();
yield testMultipleInitialization(doc);
yield testReturnCommit(doc);
yield testBlurCommit(doc);
yield testAdvanceCharCommit(doc);
yield testAdvanceCharsFunction(doc);
yield testEscapeCancel(doc);
host.destroy();
gBrowser.removeCurrentTab();
});
function testMultipleInitialization(doc) {
doc.body.innerHTML = "";
let options = {};
let span = options.element = createSpan(doc);
info("Creating multiple inplace-editor fields");
editableField(options);
editableField(options);
info("Clicking on the inplace-editor field to turn to edit mode");
span.click();
is(span.style.display, "none", "The original <span> is hidden");
is(doc.querySelectorAll("input").length, 1, "Only one <input>");
is(doc.querySelectorAll("span").length, 2,
"Correct number of <span> elements");
is(doc.querySelectorAll("span.autosizer").length, 1,
"There is an autosizer element");
}
function testReturnCommit(doc) {
info("Testing that pressing return commits the new value");
let def = defer();
createInplaceEditorAndClick({
initial: "explicit initial",
start: function (editor) {
is(editor.input.value, "explicit initial",
"Explicit initial value should be used.");
editor.input.value = "Test Value";
EventUtils.sendKey("return");
},
done: onDone("Test Value", true, def)
}, doc);
return def.promise;
}
function testBlurCommit(doc) {
info("Testing that bluring the field commits the new value");
let def = defer();
createInplaceEditorAndClick({
start: function (editor) {
is(editor.input.value, "Edit Me!", "textContent of the span used.");
editor.input.value = "Test Value";
editor.input.blur();
},
done: onDone("Test Value", true, def)
}, doc, "Edit Me!");
return def.promise;
}
function testAdvanceCharCommit(doc) {
info("Testing that configured advanceChars commit the new value");
let def = defer();
createInplaceEditorAndClick({
advanceChars: ":",
start: function (editor) {
EventUtils.sendString("Test:");
},
done: onDone("Test", true, def)
}, doc);
return def.promise;
}
function testAdvanceCharsFunction(doc) {
info("Testing advanceChars as a function");
let def = defer();
let firstTime = true;
createInplaceEditorAndClick({
initial: "",
advanceChars: function (charCode, text, insertionPoint) {
if (charCode !== Components.interfaces.nsIDOMKeyEvent.DOM_VK_COLON) {
return false;
}
if (firstTime) {
firstTime = false;
return false;
}
// Just to make sure we check it somehow.
return text.length > 0;
},
start: function (editor) {
for (let ch of ":Test:") {
EventUtils.sendChar(ch);
}
},
done: onDone(":Test", true, def)
}, doc);
return def.promise;
}
function testEscapeCancel(doc) {
info("Testing that escape cancels the new value");
let def = defer();
createInplaceEditorAndClick({
initial: "initial text",
start: function (editor) {
editor.input.value = "Test Value";
EventUtils.sendKey("escape");
},
done: onDone("initial text", false, def)
}, doc);
return def.promise;
}
function onDone(value, isCommit, def) {
return function (actualValue, actualCommit) {
info("Inplace-editor's done callback executed, checking its state");
is(actualValue, value, "The value is correct");
is(actualCommit, isCommit, "The commit boolean is correct");
def.resolve();
};
}

View file

@ -0,0 +1,71 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_inplace_editor.js */
"use strict";
loadHelperScript("helper_inplace_editor.js");
// Test that the trimOutput option for the inplace editor works correctly.
add_task(function* () {
yield addTab("data:text/html;charset=utf-8,inline editor tests");
let [host, , doc] = yield createHost();
yield testNonTrimmed(doc);
yield testTrimmed(doc);
host.destroy();
gBrowser.removeCurrentTab();
});
function testNonTrimmed(doc) {
info("Testing the trimOutput=false option");
let def = defer();
let initial = "\nMultiple\nLines\n";
let changed = " \nMultiple\nLines\n with more whitespace ";
createInplaceEditorAndClick({
trimOutput: false,
multiline: true,
initial: initial,
start: function (editor) {
is(editor.input.value, initial, "Explicit initial value should be used.");
editor.input.value = changed;
EventUtils.sendKey("return");
},
done: onDone(changed, true, def)
}, doc);
return def.promise;
}
function testTrimmed(doc) {
info("Testing the trimOutput=true option (default value)");
let def = defer();
let initial = "\nMultiple\nLines\n";
let changed = " \nMultiple\nLines\n with more whitespace ";
createInplaceEditorAndClick({
initial: initial,
multiline: true,
start: function (editor) {
is(editor.input.value, initial, "Explicit initial value should be used.");
editor.input.value = changed;
EventUtils.sendKey("return");
},
done: onDone(changed.trim(), true, def)
}, doc);
return def.promise;
}
function onDone(value, isCommit, def) {
return function (actualValue, actualCommit) {
info("Inplace-editor's done callback executed, checking its state");
is(actualValue, value, "The value is correct");
is(actualCommit, isCommit, "The commit boolean is correct");
def.resolve();
};
}

View file

@ -0,0 +1,75 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_inplace_editor.js */
"use strict";
const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
const { AutocompletePopup } = require("devtools/client/shared/autocomplete-popup");
loadHelperScript("helper_inplace_editor.js");
// Test the inplace-editor autocomplete popup for CSS properties suggestions.
// Using a mocked list of CSS properties to avoid test failures linked to
// engine changes (new property, removed property, ...).
// format :
// [
// what key to press,
// expected input box value after keypress,
// selected suggestion index (-1 if popup is hidden),
// number of suggestions in the popup (0 if popup is hidden),
// ]
const testData = [
["b", "border", 1, 3],
["VK_DOWN", "box-sizing", 2, 3],
["VK_DOWN", "background", 0, 3],
["VK_DOWN", "border", 1, 3],
["VK_BACK_SPACE", "b", -1, 0],
["VK_BACK_SPACE", "", -1, 0],
["VK_DOWN", "background", 0, 6],
["VK_LEFT", "background", -1, 0],
];
const mockGetCSSPropertyList = function () {
return [
"background",
"border",
"box-sizing",
"color",
"display",
"visibility",
];
};
add_task(function* () {
yield addTab("data:text/html;charset=utf-8," +
"inplace editor CSS property autocomplete");
let [host, win, doc] = yield createHost();
let xulDocument = win.top.document;
let popup = new AutocompletePopup(xulDocument, { autoSelect: true });
yield new Promise(resolve => {
createInplaceEditorAndClick({
start: runPropertyAutocompletionTest,
contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
done: resolve,
popup: popup
}, doc);
});
popup.destroy();
host.destroy();
gBrowser.removeCurrentTab();
});
let runPropertyAutocompletionTest = Task.async(function* (editor) {
info("Starting to test for css property completion");
editor._getCSSPropertyList = mockGetCSSPropertyList;
for (let data of testData) {
yield testCompletion(data, editor);
}
EventUtils.synthesizeKey("VK_RETURN", {}, editor.input.defaultView);
});

View file

@ -0,0 +1,80 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_inplace_editor.js */
"use strict";
const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
const { AutocompletePopup } = require("devtools/client/shared/autocomplete-popup");
loadHelperScript("helper_inplace_editor.js");
// Test the inplace-editor autocomplete popup for CSS values suggestions.
// Using a mocked list of CSS properties to avoid test failures linked to
// engine changes (new property, removed property, ...).
// format :
// [
// what key to press,
// expected input box value after keypress,
// selected suggestion index (-1 if popup is hidden),
// number of suggestions in the popup (0 if popup is hidden),
// ]
const testData = [
["b", "block", -1, 0],
["VK_BACK_SPACE", "b", -1, 0],
["VK_BACK_SPACE", "", -1, 0],
["i", "inline", 0, 2],
["VK_DOWN", "inline-block", 1, 2],
["VK_DOWN", "inline", 0, 2],
["VK_LEFT", "inline", -1, 0],
];
const mockGetCSSValuesForPropertyName = function (propertyName) {
let values = {
"display": [
"block",
"flex",
"inline",
"inline-block",
"none",
]
};
return values[propertyName] || [];
};
add_task(function* () {
yield addTab("data:text/html;charset=utf-8," +
"inplace editor CSS value autocomplete");
let [host, win, doc] = yield createHost();
let xulDocument = win.top.document;
let popup = new AutocompletePopup(xulDocument, { autoSelect: true });
yield new Promise(resolve => {
createInplaceEditorAndClick({
start: runAutocompletionTest,
contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
property: {
name: "display"
},
done: resolve,
popup: popup
}, doc);
});
popup.destroy();
host.destroy();
gBrowser.removeCurrentTab();
});
let runAutocompletionTest = Task.async(function* (editor) {
info("Starting to test for css property completion");
editor._getCSSValuesForPropertyName = mockGetCSSValuesForPropertyName;
for (let data of testData) {
yield testCompletion(data, editor);
}
EventUtils.synthesizeKey("VK_RETURN", {}, editor.input.defaultView);
});

View file

@ -0,0 +1,119 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_inplace_editor.js */
"use strict";
const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
const { AutocompletePopup } = require("devtools/client/shared/autocomplete-popup");
loadHelperScript("helper_inplace_editor.js");
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="resource://devtools/client/themes/common.css"?>
<?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tooltip test">
</window>`;
// Test the inplace-editor autocomplete popup is aligned with the completed query.
// Which means when completing "style=display:flex; color:" the popup will aim to be
// aligned with the ":" next to "color".
// format :
// [
// what key to press,
// expected input box value after keypress,
// selected suggestion index (-1 if popup is hidden),
// number of suggestions in the popup (0 if popup is hidden),
// ]
// or
// ["checkPopupOffset"]
// to measure and test the autocomplete popup left offset.
const testData = [
["VK_RIGHT", "style=", -1, 0],
["d", "style=display", 1, 2],
["checkPopupOffset"],
["VK_RIGHT", "style=display", -1, 0],
[":", "style=display:block", 0, 3],
["checkPopupOffset"],
["f", "style=display:flex", -1, 0],
["VK_RIGHT", "style=display:flex", -1, 0],
[";", "style=display:flex;", -1, 0],
["c", "style=display:flex;color", 1, 2],
["checkPopupOffset"],
["VK_RIGHT", "style=display:flex;color", -1, 0],
[":", "style=display:flex;color:blue", 0, 2],
["checkPopupOffset"],
];
const mockGetCSSPropertyList = function () {
return [
"clear",
"color",
"direction",
"display",
];
};
const mockGetCSSValuesForPropertyName = function (propertyName) {
let values = {
"color": ["blue", "red"],
"display": ["block", "flex", "none"]
};
return values[propertyName] || [];
};
add_task(function* () {
yield addTab("data:text/html;charset=utf-8,inplace editor CSS value autocomplete");
let [host,, doc] = yield createHost("bottom", TEST_URI);
let popup = new AutocompletePopup(doc, { autoSelect: true });
info("Create a CSS_MIXED type autocomplete");
yield new Promise(resolve => {
createInplaceEditorAndClick({
initial: "style=",
start: runAutocompletionTest,
contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
done: resolve,
popup: popup
}, doc);
});
popup.destroy();
host.destroy();
gBrowser.removeCurrentTab();
});
let runAutocompletionTest = Task.async(function* (editor) {
info("Starting autocomplete test for inplace-editor popup offset");
editor._getCSSPropertyList = mockGetCSSPropertyList;
editor._getCSSValuesForPropertyName = mockGetCSSValuesForPropertyName;
let previousOffset = -1;
for (let data of testData) {
if (data[0] === "checkPopupOffset") {
info("Check the popup offset has been modified");
// We are not testing hard coded offset values here, which could be fragile. We only
// want to ensure the popup tries to match the position of the query in the editor
// input.
let offset = getPopupOffset(editor);
ok(offset > previousOffset, "New popup offset is greater than the previous one");
previousOffset = offset;
} else {
yield testCompletion(data, editor);
}
}
EventUtils.synthesizeKey("VK_RETURN", {}, editor.input.defaultView);
});
/**
* Get the autocomplete panel left offset, relative to the provided input's left offset.
*/
function getPopupOffset({popup, input}) {
let popupQuads = popup._panel.getBoxQuads({relativeTo: input});
return popupQuads[0].bounds.left;
}

View file

@ -0,0 +1,114 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_inplace_editor.js */
"use strict";
loadHelperScript("helper_inplace_editor.js");
const MAX_WIDTH = 300;
const START_TEXT = "Start text";
const LONG_TEXT = "I am a long text and I will not fit in a 300px container. " +
"I expect the inplace editor to wrap.";
// Test the inplace-editor behavior with a maxWidth configuration option
// defined.
add_task(function* () {
yield addTab("data:text/html;charset=utf-8,inplace editor max width tests");
let [host, , doc] = yield createHost();
info("Testing the maxWidth option in pixels, to precisely check the size");
yield new Promise(resolve => {
createInplaceEditorAndClick({
multiline: true,
maxWidth: MAX_WIDTH,
start: testMaxWidth,
done: resolve
}, doc, START_TEXT);
});
host.destroy();
gBrowser.removeCurrentTab();
});
let testMaxWidth = Task.async(function* (editor) {
is(editor.input.value, START_TEXT, "Span text content should be used");
ok(editor.input.offsetWidth < MAX_WIDTH,
"Input width should be strictly smaller than MAX_WIDTH");
is(getLines(editor.input), 1, "Input should display 1 line of text");
info("Check a text is on several lines if it does not fit MAX_WIDTH");
for (let key of LONG_TEXT) {
EventUtils.sendChar(key);
checkScrollbars(editor.input);
}
is(editor.input.value, LONG_TEXT, "Long text should be the input value");
is(editor.input.offsetWidth, MAX_WIDTH,
"Input width should be the same as MAX_WIDTH");
is(getLines(editor.input), 3, "Input should display 3 lines of text");
checkScrollbars(editor.input);
info("Delete all characters on line 3.");
while (getLines(editor.input) === 3) {
EventUtils.sendKey("BACK_SPACE");
checkScrollbars(editor.input);
}
is(editor.input.offsetWidth, MAX_WIDTH,
"Input width should be the same as MAX_WIDTH");
is(getLines(editor.input), 2, "Input should display 2 lines of text");
checkScrollbars(editor.input);
info("Delete all characters on line 2.");
while (getLines(editor.input) === 2) {
EventUtils.sendKey("BACK_SPACE");
checkScrollbars(editor.input);
}
is(getLines(editor.input), 1, "Input should display 1 line of text");
checkScrollbars(editor.input);
info("Delete all characters.");
while (editor.input.value !== "") {
EventUtils.sendKey("BACK_SPACE");
checkScrollbars(editor.input);
}
ok(editor.input.offsetWidth < MAX_WIDTH,
"Input width should again be strictly smaller than MAX_WIDTH");
ok(editor.input.offsetWidth > 0,
"Even with no content, the input has a non-zero width");
is(getLines(editor.input), 1, "Input should display 1 line of text");
checkScrollbars(editor.input);
info("Leave the inplace-editor");
EventUtils.sendKey("RETURN");
});
/**
* Retrieve the current number of lines displayed in the provided textarea.
*
* @param {DOMNode} textarea
* @return {Number} the number of lines
*/
function getLines(textarea) {
let win = textarea.ownerDocument.defaultView;
let style = win.getComputedStyle(textarea);
let lineHeight = style.getPropertyCSSValue("line-height").cssText;
return Math.floor(textarea.clientHeight / parseFloat(lineHeight));
}
/**
* Verify that the provided textarea has no vertical or horizontal scrollbar.
*
* @param {DOMNode} textarea
*/
function checkScrollbars(textarea) {
is(textarea.scrollHeight, textarea.clientHeight,
"Textarea should never have vertical scrollbars");
is(textarea.scrollWidth, textarea.clientWidth,
"Textarea should never have horizontal scrollbars");
}

View file

@ -0,0 +1,425 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var isOSX = Services.appinfo.OS === "Darwin";
add_task(function* () {
let shortcuts = new KeyShortcuts({
window
});
yield testSimple(shortcuts);
yield testNonLetterCharacter(shortcuts);
yield testPlusCharacter(shortcuts);
yield testFunctionKey(shortcuts);
yield testMixup(shortcuts);
yield testLooseDigits(shortcuts);
yield testExactModifiers(shortcuts);
yield testLooseShiftModifier(shortcuts);
yield testStrictLetterShiftModifier(shortcuts);
yield testAltModifier(shortcuts);
yield testCommandOrControlModifier(shortcuts);
yield testCtrlModifier(shortcuts);
yield testInvalidShortcutString(shortcuts);
yield testCmdShiftShortcut(shortcuts);
shortcuts.destroy();
yield testTarget();
});
// Test helper to listen to the next key press for a given key,
// returning a promise to help using Tasks.
function once(shortcuts, key, listener) {
let called = false;
return new Promise(done => {
let onShortcut = (key2, event) => {
shortcuts.off(key, onShortcut);
ok(!called, "once listener called only once (i.e. off() works)");
is(key, key2, "listener first argument match the key we listen");
called = true;
listener(key2, event);
done();
};
shortcuts.on(key, onShortcut);
});
}
function* testSimple(shortcuts) {
info("Test simple key shortcuts");
let onKey = once(shortcuts, "0", (key, event) => {
is(event.key, "0");
// Display another key press to ensure that once() correctly stop listening
EventUtils.synthesizeKey("0", {}, window);
});
EventUtils.synthesizeKey("0", {}, window);
yield onKey;
}
function* testNonLetterCharacter(shortcuts) {
info("Test non-naive character key shortcuts");
let onKey = once(shortcuts, "[", (key, event) => {
is(event.key, "[");
});
EventUtils.synthesizeKey("[", {}, window);
yield onKey;
}
function* testFunctionKey(shortcuts) {
info("Test function key shortcuts");
let onKey = once(shortcuts, "F12", (key, event) => {
is(event.key, "F12");
});
EventUtils.synthesizeKey("F12", { keyCode: 123 }, window);
yield onKey;
}
// Plus is special. It's keycode is the one for "=". That's because it requires
// shift to be pressed and is behind "=" key. So it should be considered as a
// character key
function* testPlusCharacter(shortcuts) {
info("Test 'Plus' key shortcuts");
let onKey = once(shortcuts, "Plus", (key, event) => {
is(event.key, "+");
});
EventUtils.synthesizeKey("+", { keyCode: 61, shiftKey: true }, window);
yield onKey;
}
// Test they listeners are not mixed up between shortcuts
function* testMixup(shortcuts) {
info("Test possible listener mixup");
let hitFirst = false, hitSecond = false;
let onFirstKey = once(shortcuts, "0", (key, event) => {
is(event.key, "0");
hitFirst = true;
});
let onSecondKey = once(shortcuts, "Alt+A", (key, event) => {
is(event.key, "a");
ok(event.altKey);
hitSecond = true;
});
// Dispatch the first shortcut and expect only this one to be notified
ok(!hitFirst, "First shortcut isn't notified before firing the key event");
EventUtils.synthesizeKey("0", {}, window);
yield onFirstKey;
ok(hitFirst, "Got the first shortcut notified");
ok(!hitSecond, "No mixup, second shortcut is still not notified (1/2)");
// Wait an extra time, just to be sure this isn't racy
yield new Promise(done => {
window.setTimeout(done, 0);
});
ok(!hitSecond, "No mixup, second shortcut is still not notified (2/2)");
// Finally dispatch the second shortcut
EventUtils.synthesizeKey("a", { altKey: true }, window);
yield onSecondKey;
ok(hitSecond, "Got the second shortcut notified once it is actually fired");
}
// On azerty keyboard, digits are only available by pressing Shift/Capslock,
// but we accept them even if we omit doing that.
function* testLooseDigits(shortcuts) {
info("Test Loose digits");
let onKey = once(shortcuts, "0", (key, event) => {
is(event.key, "à");
ok(!event.altKey);
ok(!event.ctrlKey);
ok(!event.metaKey);
ok(!event.shiftKey);
});
// Simulate a press on the "0" key, without shift pressed on a french
// keyboard
EventUtils.synthesizeKey(
"à",
{ keyCode: 48 },
window);
yield onKey;
onKey = once(shortcuts, "0", (key, event) => {
is(event.key, "0");
ok(!event.altKey);
ok(!event.ctrlKey);
ok(!event.metaKey);
ok(event.shiftKey);
});
// Simulate the same press with shift pressed
EventUtils.synthesizeKey(
"0",
{ keyCode: 48, shiftKey: true },
window);
yield onKey;
}
// Test that shortcuts is notified only when the modifiers match exactly
function* testExactModifiers(shortcuts) {
info("Test exact modifiers match");
let hit = false;
let onKey = once(shortcuts, "Alt+A", (key, event) => {
is(event.key, "a");
ok(event.altKey);
ok(!event.ctrlKey);
ok(!event.metaKey);
ok(!event.shiftKey);
hit = true;
});
// Dispatch with unexpected set of modifiers
ok(!hit, "Shortcut isn't notified before firing the key event");
EventUtils.synthesizeKey("a",
{ accelKey: true, altKey: true, shiftKey: true },
window);
EventUtils.synthesizeKey(
"a",
{ accelKey: true, altKey: false, shiftKey: false },
window);
EventUtils.synthesizeKey(
"a",
{ accelKey: false, altKey: false, shiftKey: true },
window);
EventUtils.synthesizeKey(
"a",
{ accelKey: false, altKey: false, shiftKey: false },
window);
// Wait an extra time to let a chance to call the listener
yield new Promise(done => {
window.setTimeout(done, 0);
});
ok(!hit, "Listener isn't called when modifiers aren't exactly matching");
// Dispatch the expected modifiers
EventUtils.synthesizeKey("a", { accelKey: false, altKey: true, shiftKey: false},
window);
yield onKey;
ok(hit, "Got shortcut notified once it is actually fired");
}
// Some keys are only accessible via shift and listener should also be called
// even if the key didn't explicitely requested Shift modifier.
// For example, `%` on french keyboards is only accessible via Shift.
// Same thing for `@` on US keybords.
function* testLooseShiftModifier(shortcuts) {
info("Test Loose shift modifier");
let onKey = once(shortcuts, "%", (key, event) => {
is(event.key, "%");
ok(!event.altKey);
ok(!event.ctrlKey);
ok(!event.metaKey);
ok(event.shiftKey);
});
EventUtils.synthesizeKey(
"%",
{ accelKey: false, altKey: false, ctrlKey: false, shiftKey: true},
window);
yield onKey;
onKey = once(shortcuts, "@", (key, event) => {
is(event.key, "@");
ok(!event.altKey);
ok(!event.ctrlKey);
ok(!event.metaKey);
ok(event.shiftKey);
});
EventUtils.synthesizeKey(
"@",
{ accelKey: false, altKey: false, ctrlKey: false, shiftKey: true},
window);
yield onKey;
}
// But Shift modifier is strict on all letter characters (a to Z)
function* testStrictLetterShiftModifier(shortcuts) {
info("Test strict shift modifier on letters");
let hitFirst = false;
let onKey = once(shortcuts, "a", (key, event) => {
is(event.key, "a");
ok(!event.altKey);
ok(!event.ctrlKey);
ok(!event.metaKey);
ok(!event.shiftKey);
hitFirst = true;
});
let onShiftKey = once(shortcuts, "Shift+a", (key, event) => {
is(event.key, "a");
ok(!event.altKey);
ok(!event.ctrlKey);
ok(!event.metaKey);
ok(event.shiftKey);
});
EventUtils.synthesizeKey(
"a",
{ shiftKey: true},
window);
yield onShiftKey;
ok(!hitFirst, "Didn't fire the explicit shift+a");
EventUtils.synthesizeKey(
"a",
{ shiftKey: false},
window);
yield onKey;
}
function* testAltModifier(shortcuts) {
info("Test Alt modifier");
let onKey = once(shortcuts, "Alt+F1", (key, event) => {
is(event.keyCode, window.KeyboardEvent.DOM_VK_F1);
ok(event.altKey);
ok(!event.ctrlKey);
ok(!event.metaKey);
ok(!event.shiftKey);
});
EventUtils.synthesizeKey(
"VK_F1",
{ altKey: true },
window);
yield onKey;
}
function* testCommandOrControlModifier(shortcuts) {
info("Test CommandOrControl modifier");
let onKey = once(shortcuts, "CommandOrControl+F1", (key, event) => {
is(event.keyCode, window.KeyboardEvent.DOM_VK_F1);
ok(!event.altKey);
if (isOSX) {
ok(!event.ctrlKey);
ok(event.metaKey);
} else {
ok(event.ctrlKey);
ok(!event.metaKey);
}
ok(!event.shiftKey);
});
let onKeyAlias = once(shortcuts, "CmdOrCtrl+F1", (key, event) => {
is(event.keyCode, window.KeyboardEvent.DOM_VK_F1);
ok(!event.altKey);
if (isOSX) {
ok(!event.ctrlKey);
ok(event.metaKey);
} else {
ok(event.ctrlKey);
ok(!event.metaKey);
}
ok(!event.shiftKey);
});
if (isOSX) {
EventUtils.synthesizeKey(
"VK_F1",
{ metaKey: true },
window);
} else {
EventUtils.synthesizeKey(
"VK_F1",
{ ctrlKey: true },
window);
}
yield onKey;
yield onKeyAlias;
}
function* testCtrlModifier(shortcuts) {
info("Test Ctrl modifier");
let onKey = once(shortcuts, "Ctrl+F1", (key, event) => {
is(event.keyCode, window.KeyboardEvent.DOM_VK_F1);
ok(!event.altKey);
ok(event.ctrlKey);
ok(!event.metaKey);
ok(!event.shiftKey);
});
let onKeyAlias = once(shortcuts, "Control+F1", (key, event) => {
is(event.keyCode, window.KeyboardEvent.DOM_VK_F1);
ok(!event.altKey);
ok(event.ctrlKey);
ok(!event.metaKey);
ok(!event.shiftKey);
});
EventUtils.synthesizeKey(
"VK_F1",
{ ctrlKey: true },
window);
yield onKey;
yield onKeyAlias;
}
function* testCmdShiftShortcut(shortcuts) {
if (!isOSX) {
// This test is OSX only (Bug 1300458).
return;
}
let onCmdKey = once(shortcuts, "CmdOrCtrl+[", (key, event) => {
is(event.key, "[");
ok(!event.altKey);
ok(!event.ctrlKey);
ok(event.metaKey);
ok(!event.shiftKey);
});
let onCmdShiftKey = once(shortcuts, "CmdOrCtrl+Shift+[", (key, event) => {
is(event.key, "[");
ok(!event.altKey);
ok(!event.ctrlKey);
ok(event.metaKey);
ok(event.shiftKey);
});
EventUtils.synthesizeKey(
"[",
{ metaKey: true, shiftKey: true },
window);
EventUtils.synthesizeKey(
"[",
{ metaKey: true },
window);
yield onCmdKey;
yield onCmdShiftKey;
}
function* testTarget() {
info("Test KeyShortcuts with target argument");
let target = document.createElementNS("http://www.w3.org/1999/xhtml",
"input");
document.documentElement.appendChild(target);
target.focus();
let shortcuts = new KeyShortcuts({
window,
target
});
let onKey = once(shortcuts, "0", (key, event) => {
is(event.key, "0");
is(event.target, target);
});
EventUtils.synthesizeKey("0", {}, window);
yield onKey;
target.remove();
shortcuts.destroy();
}
function testInvalidShortcutString(shortcuts) {
info("Test wrong shortcut string");
let shortcut = KeyShortcuts.parseElectronKey(window, "Cmmd+F");
ok(!shortcut, "Passing a invalid shortcut string should return a null object");
shortcuts.on("Cmmd+F", function () {});
ok(true, "on() shouldn't throw when passing invalid shortcut string");
}

View file

@ -0,0 +1,12 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {KeyCodes} = require("devtools/client/shared/keycodes");
add_task(function* () {
for (let key in KeyCodes) {
is(KeyCodes[key], Ci.nsIDOMKeyEvent[key], "checking value for " + key);
}
});

View file

@ -0,0 +1,65 @@
<!doctype html>
<meta charset=utf-8>
<title>Layout Helpers</title>
<style id="styles">
body {
margin: 0;
padding: 0;
}
#hidden-node {
display: none;
}
#simple-node-with-margin-padding-border {
width: 200px;
height: 200px;
background: #f06;
padding: 20px;
margin: 50px;
border: 10px solid black;
}
#scrolled-node {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 100px;
overflow: scroll;
background: linear-gradient(red, pink);
}
#sub-scrolled-node {
width: 200px;
height: 200px;
overflow: scroll;
background: linear-gradient(yellow, green);
}
#inner-scrolled-node {
width: 100px;
height: 400px;
background: linear-gradient(black, white);
}
</style>
<div id="hidden-node"></div>
<div id="simple-node-with-margin-padding-border"></div>
<!-- The inline encoded code below corresponds to:
<iframe style="margin:10px;border:0;width:300px;height:300px;">
<iframe style="margin:10px;border:0;width:200px;height:200px;">
<div id="inner-node" style="width:100px;height:100px;border:10px solid red;margin:10px;padding:10px;"></div>
</iframe>
</iframe>
-->
<iframe src="data:text/html,%3Cstyle%3Ebody%7Bmargin:0;padding:0;%7D%3C/style%3E%3Ciframe%20src=%22data:text/html,%253Cstyle%253Ebody%257Bmargin:0;padding:0;%257D%253C/style%253E%253Cdiv%2520id='inner-node'%2520style='width:100px;height:100px;border:10px%2520solid%2520red;margin:10px;padding:10px;'%253E%253C/div%253E%22%20style=%22margin:10px;border:0;width:200px;height:200px;%22%3E%3C/iframe%3E" style="margin:10px;border:0;width:300px;height:300px;"></iframe>
<div id="scrolled-node">
<div id="sub-scrolled-node">
<div id="inner-scrolled-node"></div>
</div>
</div>
<span id="inline">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porttitor luctus sem id scelerisque. Cras quis velit sed risus euismod lacinia. Donec viverra enim eu ligula efficitur, quis vulputate metus cursus. Duis sed interdum risus. Ut blandit velit vitae faucibus efficitur. Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br/ >
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed vitae dolor metus. Aliquam sed velit sit amet libero vestibulum aliquam vel a lorem. Integer eget ex eget justo auctor ullamcorper.<br/ >
Praesent tristique maximus lacus, nec ultricies neque ultrices non. Phasellus vel lobortis justo. </span>

View file

@ -0,0 +1,219 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests getAdjustedQuads works properly in a variety of use cases including
// iframes, scroll and zoom
"use strict";
const {getAdjustedQuads} = require("devtools/shared/layout/utils");
const TEST_URI = TEST_URI_ROOT + "browser_layoutHelpers-getBoxQuads.html";
add_task(function* () {
let tab = yield addTab(TEST_URI);
let doc = tab.linkedBrowser.contentDocument;
ok(typeof getAdjustedQuads === "function", "getAdjustedQuads is defined");
info("Running tests");
returnsTheRightDataStructure(doc);
isEmptyForMissingNode(doc);
isEmptyForHiddenNodes(doc);
defaultsToBorderBoxIfNoneProvided(doc);
returnsLikeGetBoxQuadsInSimpleCase(doc);
takesIframesOffsetsIntoAccount(doc);
takesScrollingIntoAccount(doc);
yield takesZoomIntoAccount(doc);
returnsMultipleItemsForWrappingInlineElements(doc);
gBrowser.removeCurrentTab();
});
function returnsTheRightDataStructure(doc) {
info("Checks that the returned data contains bounds and 4 points");
let node = doc.querySelector("body");
let [res] = getAdjustedQuads(doc.defaultView, node, "content");
ok("bounds" in res, "The returned data has a bounds property");
ok("p1" in res, "The returned data has a p1 property");
ok("p2" in res, "The returned data has a p2 property");
ok("p3" in res, "The returned data has a p3 property");
ok("p4" in res, "The returned data has a p4 property");
for (let boundProp of
["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
ok(boundProp in res.bounds, "The bounds has a " + boundProp + " property");
}
for (let point of ["p1", "p2", "p3", "p4"]) {
for (let pointProp of ["x", "y", "z", "w"]) {
ok(pointProp in res[point], point + " has a " + pointProp + " property");
}
}
}
function isEmptyForMissingNode(doc) {
info("Checks that null is returned for invalid nodes");
for (let input of [null, undefined, "", 0]) {
is(getAdjustedQuads(doc.defaultView, input).length, 0,
"A 0-length array is returned for input " + input);
}
}
function isEmptyForHiddenNodes(doc) {
info("Checks that null is returned for nodes that aren't rendered");
let style = doc.querySelector("#styles");
is(getAdjustedQuads(doc.defaultView, style).length, 0,
"null is returned for a <style> node");
let hidden = doc.querySelector("#hidden-node");
is(getAdjustedQuads(doc.defaultView, hidden).length, 0,
"null is returned for a hidden node");
}
function defaultsToBorderBoxIfNoneProvided(doc) {
info("Checks that if no boxtype is passed, then border is the default one");
let node = doc.querySelector("#simple-node-with-margin-padding-border");
let [withBoxType] = getAdjustedQuads(doc.defaultView, node, "border");
let [withoutBoxType] = getAdjustedQuads(doc.defaultView, node);
for (let boundProp of
["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
is(withBoxType.bounds[boundProp], withoutBoxType.bounds[boundProp],
boundProp + " bound is equal with or without the border box type");
}
for (let point of ["p1", "p2", "p3", "p4"]) {
for (let pointProp of ["x", "y", "z", "w"]) {
is(withBoxType[point][pointProp], withoutBoxType[point][pointProp],
point + "." + pointProp +
" is equal with or without the border box type");
}
}
}
function returnsLikeGetBoxQuadsInSimpleCase(doc) {
info("Checks that for an element in the main frame, without scroll nor zoom" +
"that the returned value is similar to the returned value of getBoxQuads");
let node = doc.querySelector("#simple-node-with-margin-padding-border");
for (let region of ["content", "padding", "border", "margin"]) {
let expected = node.getBoxQuads({
box: region
})[0];
let [actual] = getAdjustedQuads(doc.defaultView, node, region);
for (let boundProp of
["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
is(actual.bounds[boundProp], expected.bounds[boundProp],
boundProp + " bound is equal to the one returned by getBoxQuads for " +
region + " box");
}
for (let point of ["p1", "p2", "p3", "p4"]) {
for (let pointProp of ["x", "y", "z", "w"]) {
is(actual[point][pointProp], expected[point][pointProp],
point + "." + pointProp +
" is equal to the one returned by getBoxQuads for " + region + " box");
}
}
}
}
function takesIframesOffsetsIntoAccount(doc) {
info("Checks that the quad returned for a node inside iframes that have " +
"margins takes those offsets into account");
let rootIframe = doc.querySelector("iframe");
let subIframe = rootIframe.contentDocument.querySelector("iframe");
let innerNode = subIframe.contentDocument.querySelector("#inner-node");
let [quad] = getAdjustedQuads(doc.defaultView, innerNode, "content");
// rootIframe margin + subIframe margin + node margin + node border + node padding
let p1x = 10 + 10 + 10 + 10 + 10;
is(quad.p1.x, p1x, "The inner node's p1 x position is correct");
// Same as p1x + the inner node width
let p2x = p1x + 100;
is(quad.p2.x, p2x, "The inner node's p2 x position is correct");
}
function takesScrollingIntoAccount(doc) {
info("Checks that the quad returned for a node inside multiple scrolled " +
"containers takes the scroll values into account");
// For info, the container being tested here is absolutely positioned at 0 0
// to simplify asserting the coordinates
info("Scroll the container nodes down");
let scrolledNode = doc.querySelector("#scrolled-node");
scrolledNode.scrollTop = 100;
let subScrolledNode = doc.querySelector("#sub-scrolled-node");
subScrolledNode.scrollTop = 200;
let innerNode = doc.querySelector("#inner-scrolled-node");
let [quad] = getAdjustedQuads(doc.defaultView, innerNode, "content");
is(quad.p1.x, 0, "p1.x of the scrolled node is correct after scrolling down");
is(quad.p1.y, -300, "p1.y of the scrolled node is correct after scrolling down");
info("Scrolling back up");
scrolledNode.scrollTop = 0;
subScrolledNode.scrollTop = 0;
[quad] = getAdjustedQuads(doc.defaultView, innerNode, "content");
is(quad.p1.x, 0, "p1.x of the scrolled node is correct after scrolling up");
is(quad.p1.y, 0, "p1.y of the scrolled node is correct after scrolling up");
}
function* takesZoomIntoAccount(doc) {
info("Checks that if the page is zoomed in/out, the quad returned is correct");
// Hard-coding coordinates in this zoom test is a bad idea as it can vary
// depending on the platform, so we simply test that zooming in produces a
// bigger quad and zooming out produces a smaller quad
let node = doc.querySelector("#simple-node-with-margin-padding-border");
let [defaultQuad] = getAdjustedQuads(doc.defaultView, node);
info("Zoom in");
window.FullZoom.enlarge();
let [zoomedInQuad] = getAdjustedQuads(doc.defaultView, node);
ok(zoomedInQuad.bounds.width > defaultQuad.bounds.width,
"The zoomed in quad is bigger than the default one");
ok(zoomedInQuad.bounds.height > defaultQuad.bounds.height,
"The zoomed in quad is bigger than the default one");
info("Zoom out");
yield window.FullZoom.reset();
window.FullZoom.reduce();
let [zoomedOutQuad] = getAdjustedQuads(doc.defaultView, node);
ok(zoomedOutQuad.bounds.width < defaultQuad.bounds.width,
"The zoomed out quad is smaller than the default one");
ok(zoomedOutQuad.bounds.height < defaultQuad.bounds.height,
"The zoomed out quad is smaller than the default one");
yield window.FullZoom.reset();
}
function returnsMultipleItemsForWrappingInlineElements(doc) {
info("Checks that several quads are returned " +
"for inline elements that span line-breaks");
let node = doc.querySelector("#inline");
let quads = getAdjustedQuads(doc.defaultView, node, "content");
// At least 3 because of the 2 <br />, maybe more depending on the window size.
ok(quads.length >= 3, "Multiple quads were returned");
is(quads.length, node.getBoxQuads().length,
"The same number of boxes as getBoxQuads was returned");
}

View file

@ -0,0 +1,24 @@
<!doctype html>
<meta charset=utf-8>
<title> Layout Helpers </title>
<style>
html {
height: 300%;
width: 300%;
}
div#some {
position: absolute;
background: black;
width: 2px;
height: 2px;
}
iframe {
position: absolute;
width: 40px;
height: 40px;
border: 0;
}
</style>
<div id=some></div>

View file

@ -0,0 +1,93 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that scrollIntoViewIfNeeded works properly.
const {scrollIntoViewIfNeeded} = require("devtools/client/shared/scroll");
const TEST_URI = TEST_URI_ROOT + "browser_layoutHelpers.html";
add_task(function* () {
let [host, win] = yield createHost("bottom", TEST_URI);
runTest(win);
host.destroy();
});
function runTest(win) {
let some = win.document.getElementById("some");
some.style.top = win.innerHeight + "px";
some.style.left = win.innerWidth + "px";
// The tests start with a black 2x2 pixels square below bottom right.
// Do not resize the window during the tests.
let xPos = Math.floor(win.innerWidth / 2);
// Above the viewport.
win.scroll(xPos, win.innerHeight + 2);
scrollIntoViewIfNeeded(some);
is(win.scrollY, Math.floor(win.innerHeight / 2) + 1,
"Element completely hidden above should appear centered.");
is(win.scrollX, xPos,
"scrollX position has not changed.");
// On the top edge.
win.scroll(win.innerWidth / 2, win.innerHeight + 1);
scrollIntoViewIfNeeded(some);
is(win.scrollY, win.innerHeight,
"Element partially visible above should appear above.");
is(win.scrollX, xPos,
"scrollX position has not changed.");
// Just below the viewport.
win.scroll(win.innerWidth / 2, 0);
scrollIntoViewIfNeeded(some);
is(win.scrollY, Math.floor(win.innerHeight / 2) + 1,
"Element completely hidden below should appear centered.");
is(win.scrollX, xPos,
"scrollX position has not changed.");
// On the bottom edge.
win.scroll(win.innerWidth / 2, 1);
scrollIntoViewIfNeeded(some);
is(win.scrollY, 2,
"Element partially visible below should appear below.");
is(win.scrollX, xPos,
"scrollX position has not changed.");
// Above the viewport.
win.scroll(win.innerWidth / 2, win.innerHeight + 2);
scrollIntoViewIfNeeded(some, false);
is(win.scrollY, win.innerHeight,
"Element completely hidden above should appear above " +
"if parameter is false.");
is(win.scrollX, xPos,
"scrollX position has not changed.");
// On the top edge.
win.scroll(win.innerWidth / 2, win.innerHeight + 1);
scrollIntoViewIfNeeded(some, false);
is(win.scrollY, win.innerHeight,
"Element partially visible above should appear above " +
"if parameter is false.");
is(win.scrollX, xPos,
"scrollX position has not changed.");
// Below the viewport.
win.scroll(win.innerWidth / 2, 0);
scrollIntoViewIfNeeded(some, false);
is(win.scrollY, 2,
"Element completely hidden below should appear below " +
"if parameter is false.");
is(win.scrollX, xPos,
"scrollX position has not changed.");
// On the bottom edge.
win.scroll(win.innerWidth / 2, 1);
scrollIntoViewIfNeeded(some, false);
is(win.scrollY, 2,
"Element partially visible below should appear below " +
"if parameter is false.");
is(win.scrollX, xPos,
"scrollX position has not changed.");
}

View file

@ -0,0 +1,168 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests the MdnDocsWidget object, and specifically its
* loadCssDocs() function.
*
* The MdnDocsWidget is initialized with a document which has a specific
* structure. You then call loadCssDocs(), passing in a CSS property name.
* MdnDocsWidget then fetches docs for that property by making an XHR to
* a docs page, and loads the results into the document. While the XHR is
* still not resolved the document is put into an "initializing" state in
* which the devtools throbber is displayed.
*
* In this file we test:
* - the initial state of the document before the docs have loaded
* - the state of the document after the docs have loaded
*/
"use strict";
const {setBaseCssDocsUrl, MdnDocsWidget} = require("devtools/client/shared/widgets/MdnDocsWidget");
/**
* Test properties
*
* In the real tooltip, a CSS property name is used to look up an MDN page
* for that property.
* In the test code, the names defined here is used to look up a page
* served by the test server.
*/
const BASIC_TESTING_PROPERTY = "html-mdn-css-basic-testing.html";
const BASIC_EXPECTED_SUMMARY = "A summary of the property.";
const BASIC_EXPECTED_SYNTAX = [{type: "comment", text: "/* The part we want */"},
{type: "text", text: "\n"},
{type: "property-name", text: "this"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "is-the-part-we-want"},
{type: "text", text: ";"}];
const URI_PARAMS =
"?utm_source=mozilla&utm_medium=firefox-inspector&utm_campaign=default";
add_task(function* () {
setBaseCssDocsUrl(TEST_URI_ROOT);
yield addTab("about:blank");
let [host, win] = yield createHost("bottom", "data:text/html," +
"<div class='mdn-container'></div>");
let widget = new MdnDocsWidget(win.document.querySelector("div"));
yield testTheBasics(widget);
host.destroy();
gBrowser.removeCurrentTab();
});
/**
* Test all the basics
* - initial content, before docs have loaded, is as expected
* - throbber is set before docs have loaded
* - contents are as expected after docs have loaded
* - throbber is gone after docs have loaded
* - mdn link text is correct and onclick behavior is correct
*/
function* testTheBasics(widget) {
info("Test all the basic functionality in the widget");
info("Get the widget state before docs have loaded");
let promise = widget.loadCssDocs(BASIC_TESTING_PROPERTY);
info("Check initial contents before docs have loaded");
checkTooltipContents(widget.elements, {
propertyName: BASIC_TESTING_PROPERTY,
summary: "",
syntax: ""
});
// throbber is set
ok(widget.elements.info.classList.contains("devtools-throbber"),
"Throbber is set");
info("Now let the widget finish loading");
yield promise;
info("Check contents after docs have loaded");
checkTooltipContents(widget.elements, {
propertyName: BASIC_TESTING_PROPERTY,
summary: BASIC_EXPECTED_SUMMARY,
syntax: BASIC_EXPECTED_SYNTAX
});
// throbber is gone
ok(!widget.elements.info.classList.contains("devtools-throbber"),
"Throbber is not set");
info("Check that MDN link text is correct and onclick behavior is correct");
let mdnLink = widget.elements.linkToMdn;
let expectedHref = TEST_URI_ROOT + BASIC_TESTING_PROPERTY + URI_PARAMS;
is(mdnLink.href, expectedHref, "MDN link href is correct");
let uri = yield checkLinkClick(mdnLink);
is(uri, expectedHref, "New tab opened with the expected URI");
}
/**
* Clicking the "Visit MDN Page" in the tooltip panel
* should open a new browser tab with the page loaded.
*
* To test this we'll listen for a new tab opening, and
* when it does, add a listener to that new tab to tell
* us when it has loaded.
*
* Then we click the link.
*
* In the tab's load listener, we'll resolve the promise
* with the URI, which is expected to match the href
* in the orginal link.
*
* One complexity is that when you open a new tab,
* "about:blank" is first loaded into the tab before the
* actual page. So we ignore that first load event, and keep
* listening until "load" is triggered for a different URI.
*/
function checkLinkClick(link) {
function loadListener(tab) {
let browser = getBrowser().getBrowserForTab(tab);
let uri = browser.currentURI.spec;
info("New browser tab has loaded");
gBrowser.removeTab(tab);
info("Resolve promise with new tab URI");
deferred.resolve(uri);
}
function newTabListener(e) {
gBrowser.tabContainer.removeEventListener("TabOpen", newTabListener);
let tab = e.target;
BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url => url != "about:blank")
.then(url => loadListener(tab));
}
let deferred = defer();
info("Check that clicking the link opens a new tab with the correct URI");
gBrowser.tabContainer.addEventListener("TabOpen", newTabListener, false);
info("Click the link to MDN");
link.click();
return deferred.promise;
}
/**
* Utility function to check content of the tooltip.
*/
function checkTooltipContents(doc, expected) {
is(doc.heading.textContent,
expected.propertyName,
"Property name is correct");
is(doc.summary.textContent,
expected.summary,
"Summary is correct");
checkCssSyntaxHighlighterOutput(expected.syntax, doc.syntax);
}

View file

@ -0,0 +1,128 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests the MdnDocsWidget object, and specifically its
* loadCssDocs() function.
*
* The MdnDocsWidget is initialized with a document which has a specific
* structure. You then call loadCssDocs(), passing in a CSS property name.
* MdnDocsWidget then fetches docs for that property by making an XHR to
* a docs page, and loads the results into the document.
*
* In this file we test that the tooltip can properly handle the different
* structures that the docs page might have, including variant structures and
* error conditions like parts of the document being missing.
*
* We also test that the tooltip properly handles the case where the page
* doesn't exist at all.
*/
"use strict";
const {
setBaseCssDocsUrl,
MdnDocsWidget
} = require("devtools/client/shared/widgets/MdnDocsWidget");
const BASIC_EXPECTED_SUMMARY = "A summary of the property.";
const BASIC_EXPECTED_SYNTAX = [{type: "comment", text: "/* The part we want */"},
{type: "text", text: "\n"},
{type: "property-name", text: "this"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "is-the-part-we-want"},
{type: "text", text: ";"}];
const ERROR_MESSAGE = "Could not load docs page.";
/**
* Test properties
*
* In the real tooltip, a CSS property name is used to look up an MDN page
* for that property.
* In the test code, the names defined here are used to look up a page
* served by the test server. We have different properties to test
* different ways that the docs pages might be constructed, including errors
* like pages that don't include docs where we expect.
*/
const SYNTAX_OLD_STYLE = "html-mdn-css-syntax-old-style.html";
const NO_SUMMARY = "html-mdn-css-no-summary.html";
const NO_SYNTAX = "html-mdn-css-no-syntax.html";
const NO_SUMMARY_OR_SYNTAX = "html-mdn-css-no-summary-or-syntax.html";
const TEST_DATA = [{
desc: "Test a property for which we don't have a page",
docsPageUrl: "i-dont-exist.html",
expectedContents: {
propertyName: "i-dont-exist.html",
summary: ERROR_MESSAGE,
syntax: []
}
}, {
desc: "Test a property whose syntax section is specified using an old-style page",
docsPageUrl: SYNTAX_OLD_STYLE,
expectedContents: {
propertyName: SYNTAX_OLD_STYLE,
summary: BASIC_EXPECTED_SUMMARY,
syntax: BASIC_EXPECTED_SYNTAX
}
}, {
desc: "Test a property whose page doesn't have a summary",
docsPageUrl: NO_SUMMARY,
expectedContents: {
propertyName: NO_SUMMARY,
summary: "",
syntax: BASIC_EXPECTED_SYNTAX
}
}, {
desc: "Test a property whose page doesn't have a syntax",
docsPageUrl: NO_SYNTAX,
expectedContents: {
propertyName: NO_SYNTAX,
summary: BASIC_EXPECTED_SUMMARY,
syntax: []
}
}, {
desc: "Test a property whose page doesn't have a summary or a syntax",
docsPageUrl: NO_SUMMARY_OR_SYNTAX,
expectedContents: {
propertyName: NO_SUMMARY_OR_SYNTAX,
summary: ERROR_MESSAGE,
syntax: []
}
}
];
add_task(function* () {
setBaseCssDocsUrl(TEST_URI_ROOT);
yield addTab("about:blank");
let [host, win] = yield createHost("bottom", "data:text/html," +
"<div class='mdn-container'></div>");
let widget = new MdnDocsWidget(win.document.querySelector("div"));
for (let {desc, docsPageUrl, expectedContents} of TEST_DATA) {
info(desc);
yield widget.loadCssDocs(docsPageUrl);
checkTooltipContents(widget.elements, expectedContents);
}
host.destroy();
gBrowser.removeCurrentTab();
});
/*
* Utility function to check content of the tooltip.
*/
function checkTooltipContents(doc, expected) {
is(doc.heading.textContent,
expected.propertyName,
"Property name is correct");
is(doc.summary.textContent,
expected.summary,
"Summary is correct");
checkCssSyntaxHighlighterOutput(expected.syntax, doc.syntax);
}

View file

@ -0,0 +1,277 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests the CSS syntax highlighter in the MdnDocsWidget object.
*
* The CSS syntax highlighter accepts:
* - a string containing CSS
* - a DOM node
*
* It parses the string and creates a collection of DOM nodes for different
* CSS token types. These DOM nodes have CSS classes applied to them,
* to apply the right style for that particular token type. The DOM nodes
* are returned as children of the node that was passed to the function.
*
* This test code defines a number of different strings containing valid and
* invalid CSS in various forms. For each string it defines the DOM nodes
* that it expects to get from the syntax highlighter.
*
* It then calls the syntax highlighter, and checks that the resulting
* collection of DOM nodes is what we expected.
*/
"use strict";
const {appendSyntaxHighlightedCSS} = require("devtools/client/shared/widgets/MdnDocsWidget");
/**
* An array containing the actual test cases.
*
* The test code tests every case in the array. If you want to add more
* test cases, just add more items to the array.
*
* Each test case consists of:
* - description: string describing the salient features of this test case
* - example: the string to test
* - expected: an array of objects, one for each DOM node we expect, that
* captures the information about the node that we expect to test.
*/
const TEST_DATA = [{
description: "Valid syntax, string value.",
example: "name: stringValue;",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, numeric value.",
example: "name: 1;",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "1"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, url value.",
example: "name: url(./name);",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "url(./name)"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, space before ':'.",
example: "name : stringValue;",
expected: [{type: "property-name", text: "name"},
{type: "text", text: " "},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, space before ';'.",
example: "name: stringValue ;",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: " "},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, trailing space.",
example: "name: stringValue; ",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"},
{type: "text", text: " "}
]}, {
description: "Valid syntax, leading space.",
example: " name: stringValue;",
expected: [{type: "text", text: " "},
{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, two spaces.",
example: "name: stringValue;",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, no spaces.",
example: "name:stringValue;",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, two-part value.",
example: "name: stringValue 1;",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: " "},
{type: "property-value", text: "1"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, two declarations.",
example: "name: stringValue;\n" +
"name: 1;",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"},
{type: "text", text: "\n"},
{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "1"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, commented, numeric value.",
example: "/* comment */\n" +
"name: 1;",
expected: [{type: "comment", text: "/* comment */"},
{type: "text", text: "\n"},
{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "1"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, multiline commented, string value.",
example: "/* multiline \n" +
"comment */\n" +
"name: stringValue;",
expected: [{type: "comment", text: "/* multiline \ncomment */"},
{type: "text", text: "\n"},
{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, commented, two declarations.",
example: "/* comment 1 */\n" +
"name: 1;\n" +
"/* comment 2 */\n" +
"name: stringValue;",
expected: [{type: "comment", text: "/* comment 1 */"},
{type: "text", text: "\n"},
{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "1"},
{type: "text", text: ";"},
{type: "text", text: "\n"},
{type: "comment", text: "/* comment 2 */"},
{type: "text", text: "\n"},
{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, multiline.",
example: "name: \n" +
"stringValue;",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " \n"},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"}
]}, {
description: "Valid syntax, multiline, two declarations.",
example: "name: \n" +
"stringValue \n" +
"stringValue2;",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " \n"},
{type: "property-value", text: "stringValue"},
{type: "text", text: " \n"},
{type: "property-value", text: "stringValue2"},
{type: "text", text: ";"}
]}, {
description: "Invalid: not CSS at all.",
example: "not CSS at all",
expected: [{type: "property-name", text: "not"},
{type: "text", text: " "},
{type: "property-name", text: "CSS"},
{type: "text", text: " "},
{type: "property-name", text: "at"},
{type: "text", text: " "},
{type: "property-name", text: "all"}
]}, {
description: "Invalid: switched ':' and ';'.",
example: "name; stringValue:",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ";"},
{type: "text", text: " "},
{type: "property-name", text: "stringValue"},
{type: "text", text: ":"}
]}, {
description: "Invalid: unterminated comment.",
example: "/* unterminated comment\n" +
"name: stringValue;",
expected: [{type: "comment", text: "/* unterminated comment\nname: stringValue;"}
]}, {
description: "Invalid: bad comment syntax.",
example: "// invalid comment\n" +
"name: stringValue;",
expected: [{type: "text", text: "/"},
{type: "text", text: "/"},
{type: "text", text: " "},
{type: "property-name", text: "invalid"},
{type: "text", text: " "},
{type: "property-name", text: "comment"},
{type: "text", text: "\n"},
{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: ";"}
]}, {
description: "Invalid: no trailing ';'.",
example: "name: stringValue\n" +
"name: stringValue2",
expected: [{type: "property-name", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue"},
{type: "text", text: "\n"},
{type: "property-value", text: "name"},
{type: "text", text: ":"},
{type: "text", text: " "},
{type: "property-value", text: "stringValue2"},
]}
];
/**
* Iterate through every test case, calling the syntax highlighter,
* then calling a helper function to check the output.
*/
add_task(function* () {
let doc = gBrowser.selectedTab.ownerDocument;
let parent = doc.createElement("div");
info("Testing all CSS syntax highlighter test cases");
for (let {description, example, expected} of TEST_DATA) {
info("Testing: " + description);
appendSyntaxHighlightedCSS(example, parent);
checkCssSyntaxHighlighterOutput(expected, parent);
while (parent.firstChild) {
parent.firstChild.remove();
}
}
});

View file

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the localization utils work properly.
const { LocalizationHelper } = require("devtools/shared/l10n");
function test() {
let l10n = new LocalizationHelper();
is(l10n.numberWithDecimals(1234.56789, 2), "1,234.57",
"The first number was properly localized.");
is(l10n.numberWithDecimals(0.0001, 2), "0",
"The second number was properly localized.");
is(l10n.numberWithDecimals(1.0001, 2), "1",
"The third number was properly localized.");
is(l10n.numberWithDecimals(NaN, 2), "0",
"NaN was properly localized.");
is(l10n.numberWithDecimals(null, 2), "0",
"`null` was properly localized.");
is(l10n.numberWithDecimals(undefined, 2), "0",
"`undefined` was properly localized.");
finish();
}

View file

@ -0,0 +1,110 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that options-view OptionsView responds to events correctly.
const {OptionsView} = require("devtools/client/shared/options-view");
const BRANCH = "devtools.debugger.";
const BLACK_BOX_PREF = "auto-black-box";
const PRETTY_PRINT_PREF = "auto-pretty-print";
const originalBlackBox = Services.prefs.getBoolPref(BRANCH + BLACK_BOX_PREF);
const originalPrettyPrint = Services.prefs.getBoolPref(BRANCH + PRETTY_PRINT_PREF);
add_task(function* () {
info("Setting a couple of preferences");
Services.prefs.setBoolPref(BRANCH + BLACK_BOX_PREF, false);
Services.prefs.setBoolPref(BRANCH + PRETTY_PRINT_PREF, true);
info("Opening a test tab and a toolbox host to create the options view in");
yield addTab("about:blank");
let [host, win] = yield createHost("bottom", OPTIONS_VIEW_URL);
yield testOptionsView(win);
info("Closing the host and current tab");
host.destroy();
gBrowser.removeCurrentTab();
info("Resetting the preferences");
Services.prefs.setBoolPref(BRANCH + BLACK_BOX_PREF, originalBlackBox);
Services.prefs.setBoolPref(BRANCH + PRETTY_PRINT_PREF, originalPrettyPrint);
});
function* testOptionsView(win) {
let events = [];
let options = createOptionsView(win);
yield options.initialize();
let $ = win.document.querySelector.bind(win.document);
options.on("pref-changed", (_, pref) => events.push(pref));
let ppEl = $("menuitem[data-pref='auto-pretty-print']");
let bbEl = $("menuitem[data-pref='auto-black-box']");
// Test default config
is(ppEl.getAttribute("checked"), "true", "`true` prefs are checked on start");
is(bbEl.getAttribute("checked"), "", "`false` prefs are unchecked on start");
// Test buttons update when preferences update outside of the menu
Services.prefs.setBoolPref(BRANCH + PRETTY_PRINT_PREF, false);
Services.prefs.setBoolPref(BRANCH + BLACK_BOX_PREF, true);
is(options.getPref(PRETTY_PRINT_PREF), false, "getPref returns correct value");
is(options.getPref(BLACK_BOX_PREF), true, "getPref returns correct value");
is(ppEl.getAttribute("checked"), "", "menuitems update when preferences change");
is(bbEl.getAttribute("checked"), "true", "menuitems update when preferences change");
// Tests events are fired when preferences update outside of the menu
is(events.length, 2, "two 'pref-changed' events fired");
is(events[0], "auto-pretty-print",
"correct pref passed in 'pref-changed' event (auto-pretty-print)");
is(events[1], "auto-black-box",
"correct pref passed in 'pref-changed' event (auto-black-box)");
// Test buttons update when clicked and preferences are updated
yield click(options, win, ppEl);
is(ppEl.getAttribute("checked"), "true", "menuitems update when clicked");
is(Services.prefs.getBoolPref(BRANCH + PRETTY_PRINT_PREF),
true, "preference updated via click");
yield click(options, win, bbEl);
is(bbEl.getAttribute("checked"), "", "menuitems update when clicked");
is(Services.prefs.getBoolPref(BRANCH + BLACK_BOX_PREF),
false, "preference updated via click");
// Tests events are fired when preferences updated via click
is(events.length, 4, "two 'pref-changed' events fired");
is(events[2], "auto-pretty-print",
"correct pref passed in 'pref-changed' event (auto-pretty-print)");
is(events[3], "auto-black-box",
"correct pref passed in 'pref-changed' event (auto-black-box)");
yield options.destroy();
}
function createOptionsView(win) {
return new OptionsView({
branchName: BRANCH,
menupopup: win.document.querySelector("#options-menupopup")
});
}
function* click(view, win, menuitem) {
let opened = view.once("options-shown");
let closed = view.once("options-hidden");
let button = win.document.querySelector("#options-button");
EventUtils.synthesizeMouseAtCenter(button, {}, win);
yield opened;
is(button.getAttribute("open"), "true", "button has `open` attribute");
EventUtils.synthesizeMouseAtCenter(menuitem, {}, win);
yield closed;
ok(!button.hasAttribute("open"), "button does not have `open` attribute");
}

View file

@ -0,0 +1,292 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {OutputParser} = require("devtools/client/shared/output-parser");
const {initCssProperties, getCssProperties} = require("devtools/shared/fronts/css-properties");
add_task(function* () {
yield addTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host, , doc] = yield createHost("bottom", "data:text/html," +
"<h1>browser_outputParser.js</h1><div></div>");
// Mock the toolbox that initCssProperties expect so we get the fallback css properties.
let toolbox = {target: {client: {}, hasActor: () => false}};
yield initCssProperties(toolbox);
let cssProperties = getCssProperties(toolbox);
let parser = new OutputParser(doc, cssProperties);
testParseCssProperty(doc, parser);
testParseCssVar(doc, parser);
testParseURL(doc, parser);
testParseFilter(doc, parser);
testParseAngle(doc, parser);
host.destroy();
}
// Class name used in color swatch.
var COLOR_TEST_CLASS = "test-class";
// Create a new CSS color-parsing test. |name| is the name of the CSS
// property. |value| is the CSS text to use. |segments| is an array
// describing the expected result. If an element of |segments| is a
// string, it is simply appended to the expected string. Otherwise,
// it must be an object with a |name| property, which is the color
// name as it appears in the input.
//
// This approach is taken to reduce boilerplate and to make it simpler
// to modify the test when the parseCssProperty output changes.
function makeColorTest(name, value, segments) {
let result = {
name,
value,
expected: ""
};
for (let segment of segments) {
if (typeof (segment) === "string") {
result.expected += segment;
} else {
result.expected += "<span data-color=\"" + segment.name + "\">" +
"<span class=\"" + COLOR_TEST_CLASS + "\" style=\"background-color:" +
segment.name + "\"></span><span>" +
segment.name + "</span></span>";
}
}
result.desc = "Testing " + name + ": " + value;
return result;
}
function testParseCssProperty(doc, parser) {
let tests = [
makeColorTest("border", "1px solid red",
["1px solid ", {name: "red"}]),
makeColorTest("background-image",
"linear-gradient(to right, #F60 10%, rgba(0,0,0,1))",
["linear-gradient(to right, ", {name: "#F60"},
" 10%, ", {name: "rgba(0,0,0,1)"},
")"]),
// In "arial black", "black" is a font, not a color.
makeColorTest("font-family", "arial black", ["arial black"]),
makeColorTest("box-shadow", "0 0 1em red",
["0 0 1em ", {name: "red"}]),
makeColorTest("box-shadow",
"0 0 1em red, 2px 2px 0 0 rgba(0,0,0,.5)",
["0 0 1em ", {name: "red"},
", 2px 2px 0 0 ",
{name: "rgba(0,0,0,.5)"}]),
makeColorTest("content", "\"red\"", ["\"red\""]),
// Invalid property names should not cause exceptions.
makeColorTest("hellothere", "'red'", ["'red'"]),
makeColorTest("filter",
"blur(1px) drop-shadow(0 0 0 blue) url(red.svg#blue)",
["<span data-filters=\"blur(1px) drop-shadow(0 0 0 blue) ",
"url(red.svg#blue)\"><span>",
"blur(1px) drop-shadow(0 0 0 ",
{name: "blue"},
") url(red.svg#blue)</span></span>"]),
makeColorTest("color", "currentColor", ["currentColor"]),
// Test a very long property.
makeColorTest("background-image",
/* eslint-disable max-len */
"linear-gradient(to left, transparent 0, transparent 5%,#F00 0, #F00 10%,#FF0 0, #FF0 15%,#0F0 0, #0F0 20%,#0FF 0, #0FF 25%,#00F 0, #00F 30%,#800 0, #800 35%,#880 0, #880 40%,#080 0, #080 45%,#088 0, #088 50%,#008 0, #008 55%,#FFF 0, #FFF 60%,#EEE 0, #EEE 65%,#CCC 0, #CCC 70%,#999 0, #999 75%,#666 0, #666 80%,#333 0, #333 85%,#111 0, #111 90%,#000 0, #000 95%,transparent 0, transparent 100%)",
/* eslint-enable max-len */
["linear-gradient(to left, ", {name: "transparent"},
" 0, ", {name: "transparent"},
" 5%,", {name: "#F00"},
" 0, ", {name: "#F00"},
" 10%,", {name: "#FF0"},
" 0, ", {name: "#FF0"},
" 15%,", {name: "#0F0"},
" 0, ", {name: "#0F0"},
" 20%,", {name: "#0FF"},
" 0, ", {name: "#0FF"},
" 25%,", {name: "#00F"},
" 0, ", {name: "#00F"},
" 30%,", {name: "#800"},
" 0, ", {name: "#800"},
" 35%,", {name: "#880"},
" 0, ", {name: "#880"},
" 40%,", {name: "#080"},
" 0, ", {name: "#080"},
" 45%,", {name: "#088"},
" 0, ", {name: "#088"},
" 50%,", {name: "#008"},
" 0, ", {name: "#008"},
" 55%,", {name: "#FFF"},
" 0, ", {name: "#FFF"},
" 60%,", {name: "#EEE"},
" 0, ", {name: "#EEE"},
" 65%,", {name: "#CCC"},
" 0, ", {name: "#CCC"},
" 70%,", {name: "#999"},
" 0, ", {name: "#999"},
" 75%,", {name: "#666"},
" 0, ", {name: "#666"},
" 80%,", {name: "#333"},
" 0, ", {name: "#333"},
" 85%,", {name: "#111"},
" 0, ", {name: "#111"},
" 90%,", {name: "#000"},
" 0, ", {name: "#000"},
" 95%,", {name: "transparent"},
" 0, ", {name: "transparent"},
" 100%)"]),
];
let target = doc.querySelector("div");
ok(target, "captain, we have the div");
for (let test of tests) {
info(test.desc);
let frag = parser.parseCssProperty(test.name, test.value, {
colorSwatchClass: COLOR_TEST_CLASS
});
target.appendChild(frag);
is(target.innerHTML, test.expected,
"CSS property correctly parsed for " + test.name + ": " + test.value);
target.innerHTML = "";
}
}
function testParseCssVar(doc, parser) {
let frag = parser.parseCssProperty("color", "var(--some-kind-of-green)", {
colorSwatchClass: "test-colorswatch"
});
let target = doc.querySelector("div");
ok(target, "captain, we have the div");
target.appendChild(frag);
is(target.innerHTML, "var(--some-kind-of-green)",
"CSS property correctly parsed");
target.innerHTML = "";
}
function testParseURL(doc, parser) {
info("Test that URL parsing preserves quoting style");
const tests = [
{
desc: "simple test without quotes",
leader: "url(",
trailer: ")",
},
{
desc: "simple test with single quotes",
leader: "url('",
trailer: "')",
},
{
desc: "simple test with double quotes",
leader: "url(\"",
trailer: "\")",
},
{
desc: "test with single quotes and whitespace",
leader: "url( \t'",
trailer: "'\r\n\f)",
},
{
desc: "simple test with uppercase",
leader: "URL(",
trailer: ")",
},
{
desc: "bad url, missing paren",
leader: "url(",
trailer: "",
expectedTrailer: ")"
},
{
desc: "bad url, missing paren, with baseURI",
baseURI: "data:text/html,<style></style>",
leader: "url(",
trailer: "",
expectedTrailer: ")"
},
{
desc: "bad url, double quote, missing paren",
leader: "url(\"",
trailer: "\"",
expectedTrailer: "\")",
},
{
desc: "bad url, single quote, missing paren and quote",
leader: "url('",
trailer: "",
expectedTrailer: "')"
}
];
for (let test of tests) {
let url = test.leader + "something.jpg" + test.trailer;
let frag = parser.parseCssProperty("background", url, {
urlClass: "test-urlclass",
baseURI: test.baseURI,
});
let target = doc.querySelector("div");
target.appendChild(frag);
let expectedTrailer = test.expectedTrailer || test.trailer;
let expected = test.leader +
"<a target=\"_blank\" class=\"test-urlclass\" " +
"href=\"something.jpg\">something.jpg</a>" +
expectedTrailer;
is(target.innerHTML, expected, test.desc);
target.innerHTML = "";
}
}
function testParseFilter(doc, parser) {
let frag = parser.parseCssProperty("filter", "something invalid", {
filterSwatchClass: "test-filterswatch"
});
let swatchCount = frag.querySelectorAll(".test-filterswatch").length;
is(swatchCount, 1, "filter swatch was created");
}
function testParseAngle(doc, parser) {
let frag = parser.parseCssProperty("image-orientation", "90deg", {
angleSwatchClass: "test-angleswatch"
});
let swatchCount = frag.querySelectorAll(".test-angleswatch").length;
is(swatchCount, 1, "angle swatch was created");
frag = parser.parseCssProperty("background-image",
"linear-gradient(90deg, red, blue", {
angleSwatchClass: "test-angleswatch"
});
swatchCount = frag.querySelectorAll(".test-angleswatch").length;
is(swatchCount, 1, "angle swatch was created");
}

View file

@ -0,0 +1,136 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Poller class.
const { Poller } = require("devtools/client/shared/poller");
add_task(function* () {
let count1 = 0, count2 = 0, count3 = 0;
let poller1 = new Poller(function () {
count1++;
}, 1000000000, true);
let poller2 = new Poller(function () {
count2++;
}, 10);
let poller3 = new Poller(function () {
count3++;
}, 1000000000);
poller2.on();
ok(!poller1.isPolling(), "isPolling() returns false for an off poller");
ok(poller2.isPolling(), "isPolling() returns true for an on poller");
yield waitUntil(() => count2 > 10);
ok(count2 > 10, "poller that was turned on polled several times");
ok(count1 === 0, "poller that was never turned on never polled");
yield poller2.off();
let currentCount2 = count2;
// Really high poll time!
poller1.on();
poller3.on();
yield waitUntil(() => count1 === 1);
ok(true, "Poller calls fn immediately when `immediate` is true");
ok(count3 === 0, "Poller does not call fn immediately when `immediate` is not set");
ok(count2 === currentCount2, "a turned off poller does not continue to poll");
yield poller2.off();
yield poller2.off();
yield poller2.off();
ok(true, "Poller.prototype.off() is idempotent");
// This should still have not polled a second time
is(count1, 1, "wait time works");
ok(poller1.isPolling(), "isPolling() returns true for an on poller");
ok(!poller2.isPolling(), "isPolling() returns false for an off poller");
});
add_task(function* () {
let count = -1;
// Create a poller that returns a promise.
// The promise is resolved asynchronously after adding 9 to the count, ensuring
// that on every poll, we have a multiple of 10.
let asyncPoller = new Poller(function () {
count++;
ok(!(count % 10), `Async poller called with a multiple of 10: ${count}`);
return new Promise(function (resolve, reject) {
let add9 = 9;
let interval = setInterval(() => {
if (add9--) {
count++;
} else {
clearInterval(interval);
resolve();
}
}, 10);
});
});
asyncPoller.on(1);
yield waitUntil(() => count > 50);
yield asyncPoller.off();
});
add_task(function* () {
// Create a poller that returns a promise. This poll call
// is called immediately, and then subsequently turned off.
// The call to `off` should not resolve until the inflight call
// finishes.
let inflightFinished = null;
let pollCalls = 0;
let asyncPoller = new Poller(function () {
pollCalls++;
return new Promise(function (resolve, reject) {
setTimeout(() => {
inflightFinished = true;
resolve();
}, 1000);
});
}, 1, true);
asyncPoller.on();
yield asyncPoller.off();
ok(inflightFinished,
"off() method does not resolve until remaining inflight poll calls finish");
is(pollCalls, 1, "should only be one poll call to occur before turning off polling");
});
add_task(function* () {
// Create a poller that returns a promise. This poll call
// is called immediately, and then subsequently turned off.
// The call to `off` should not resolve until the inflight call
// finishes.
let inflightFinished = null;
let pollCalls = 0;
let asyncPoller = new Poller(function () {
pollCalls++;
return new Promise(function (resolve, reject) {
setTimeout(() => {
inflightFinished = true;
resolve();
}, 1000);
});
}, 1, true);
asyncPoller.on();
yield asyncPoller.destroy();
ok(inflightFinished,
"destroy() method does not resolve until remaining inflight poll calls finish");
is(pollCalls, 1, "should only be one poll call to occur before destroying polling");
try {
asyncPoller.on();
ok(false, "Calling on() after destruction should throw");
} catch (e) {
ok(true, "Calling on() after destruction should throw");
}
});

Some files were not shown because too many files have changed in this diff Show more