diff options
Diffstat (limited to 'devtools/client/inspector/rules/test/browser_rules_linear-easing-swatch.js')
-rw-r--r-- | devtools/client/inspector/rules/test/browser_rules_linear-easing-swatch.js | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/devtools/client/inspector/rules/test/browser_rules_linear-easing-swatch.js b/devtools/client/inspector/rules/test/browser_rules_linear-easing-swatch.js new file mode 100644 index 0000000000..3739e6ff9c --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_linear-easing-swatch.js @@ -0,0 +1,487 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that linear easing widget pickers appear when clicking on linear +// swatches. + +const TEST_URI = ` + <style type="text/css"> + div { + animation: move 3s linear(0, 0.2, 1); + transition: top 4s linear(0 10%, 0.5 20% 80%, 0 90%); + } + .test { + animation-timing-function: linear(0, 1 50% 100%); + transition-timing-function: linear(1 -10%, 0, -1 110%); + } + </style> + <div class="test">Testing the linear easing tooltip!</div> +`; + +add_task(async function testSwatches() { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + await selectNode("div", inspector); + + const tooltip = view.tooltips.getTooltip("linearEaseFunction"); + ok(tooltip, "The rule-view has the expected linear tooltip"); + const panel = tooltip.tooltip.panel; + ok(panel, "The XUL panel for the linear tooltip exists"); + + const expectedData = [ + { + selector: "div", + property: "animation", + expectedPoints: [ + [0, 0], + [0.5, 0.2], + [1, 1], + ], + }, + { + selector: "div", + property: "transition", + expectedPoints: [ + [0.1, 0], + [0.2, 0.5], + [0.8, 0.5], + [0.9, 0], + ], + }, + { + selector: ".test", + property: "animation-timing-function", + expectedPoints: [ + [0, 0], + [0.5, 1], + [1, 1], + ], + }, + { + selector: ".test", + property: "transition-timing-function", + expectedPoints: [ + [-0.1, 1], + [0.5, 0], + [1.1, -1], + ], + }, + ]; + + for (const { selector, property, expectedPoints } of expectedData) { + const messagePrefix = `[${selector}|${property}]`; + const swatch = getRuleViewLinearEasingSwatch(view, selector, property); + ok(swatch, `${messagePrefix} the swatch exists`); + + const onWidgetReady = tooltip.once("ready"); + swatch.click(); + await onWidgetReady; + ok(true, `${messagePrefix} clicking the swatch displayed the tooltip`); + + ok( + !inplaceEditor(swatch.parentNode), + `${messagePrefix} inplace editor wasn't shown` + ); + + checkChartState(panel, expectedPoints); + + await hideTooltipAndWaitForRuleViewChanged(tooltip, view); + } +}); + +add_task(async function testChart() { + await pushPref("ui.prefersReducedMotion", 0); + + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + await selectNode("div", inspector); + + const tooltip = view.tooltips.getTooltip("linearEaseFunction"); + ok(tooltip, "The rule-view has the expected linear tooltip"); + const panel = tooltip.tooltip.panel; + ok(panel, "The XUL panel for the linear tooltip exists"); + + const selector = ".test"; + const property = "animation-timing-function"; + + const swatch = getRuleViewLinearEasingSwatch(view, selector, property); + const onWidgetReady = tooltip.once("ready"); + swatch.click(); + await onWidgetReady; + const widget = await tooltip.widget; + + const svgEl = panel.querySelector(`svg.chart`); + const svgRect = svgEl.getBoundingClientRect(); + + checkChartState( + panel, + [ + [0, 0], + [0.5, 1], + [1, 1], + ], + "testChart - initial state:" + ); + + info("Check that double clicking a point removes it"); + const middlePoint = panel.querySelector( + `svg.chart .control-points-group .control-point:nth-of-type(2)` + ); + let onWidgetUpdated = widget.once("updated"); + let onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.sendMouseEvent( + { type: "dblclick" }, + middlePoint, + widget.parent.ownerGlobal + ); + + let newValue = await onWidgetUpdated; + is(newValue, `linear(0 0%, 1 100%)`); + checkChartState( + panel, + [ + [0, 0], + [1, 1], + ], + "testChart - after point removed:" + ); + await onRuleViewChanged; + await checkRuleView(view, selector, property, newValue); + + info( + "Check that double clicking a point when there are only 2 points on the line does not remove it" + ); + const timeoutRes = Symbol(); + let onTimeout = wait(1000).then(() => timeoutRes); + onWidgetUpdated = widget.once("updated"); + EventUtils.sendMouseEvent( + { type: "dblclick" }, + panel.querySelector(`svg.chart .control-points-group .control-point`), + widget.parent.ownerGlobal + ); + let raceWinner = await Promise.race([onWidgetUpdated, onTimeout]); + is( + raceWinner, + timeoutRes, + "The widget wasn't updated after double clicking one of the 2 last points" + ); + checkChartState( + panel, + [ + [0, 0], + [1, 1], + ], + "testChart - no point removed:" + ); + + info("Check that double clicking on the svg does add a point"); + onWidgetUpdated = widget.once("updated"); + onRuleViewChanged = view.once("ruleview-changed"); + // Clicking on svg center with shiftKey so it snaps to the grid + EventUtils.synthesizeMouseAtCenter( + panel.querySelector(`svg.chart`), + { clickCount: 2, shiftKey: true }, + widget.parent.ownerGlobal + ); + + newValue = await onWidgetUpdated; + is( + newValue, + `linear(0 0%, 0.5 50%, 1 100%)`, + "Widget was updated with expected value" + ); + checkChartState( + panel, + [ + [0, 0], + [0.5, 0.5], + [1, 1], + ], + "testChart - new point added" + ); + await onRuleViewChanged; + await checkRuleView(view, selector, property, newValue); + + info("Check that points can be moved"); + onWidgetUpdated = widget.once("updated"); + onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.synthesizeMouseAtCenter( + panel.querySelector(`svg.chart .control-points-group .control-point`), + { type: "mousedown" }, + widget.parent.ownerGlobal + ); + + EventUtils.synthesizeMouse( + svgEl, + svgRect.width / 3, + svgRect.height / 3, + { type: "mousemove", shiftKey: true }, + widget.parent.ownerGlobal + ); + newValue = await onWidgetUpdated; + is( + newValue, + `linear(0.7 30%, 0.5 50%, 1 100%)`, + "Widget was updated with expected value" + ); + checkChartState( + panel, + [ + [0.3, 0.7], + [0.5, 0.5], + [1, 1], + ], + "testChart - point moved" + ); + await onRuleViewChanged; + await checkRuleView(view, selector, property, newValue); + + info("Check that the points can be moved past the next point"); + onWidgetUpdated = widget.once("updated"); + onRuleViewChanged = view.once("ruleview-changed"); + + // the second point is at 50%, so simulate a mousemove all the way to the right (which + // should be ~100%) + EventUtils.synthesizeMouse( + svgEl, + svgRect.width, + svgRect.height / 3, + { type: "mousemove", shiftKey: true }, + widget.parent.ownerGlobal + ); + newValue = await onWidgetUpdated; + is( + newValue, + `linear(0.7 50%, 0.5 50%, 1 100%)`, + "point wasn't moved past the next point (50%)" + ); + checkChartState( + panel, + [ + [0.5, 0.7], + [0.5, 0.5], + [1, 1], + ], + "testChart - point moved constrained by next point" + ); + await onRuleViewChanged; + await checkRuleView(view, selector, property, newValue); + + info("Stop dragging"); + EventUtils.synthesizeMouseAtCenter( + svgEl, + { type: "mouseup" }, + widget.parent.ownerGlobal + ); + + onTimeout = wait(1000).then(() => timeoutRes); + onWidgetUpdated = widget.once("updated"); + EventUtils.synthesizeMouseAtCenter( + svgEl, + { type: "mousemove" }, + widget.parent.ownerGlobal + ); + raceWinner = await Promise.race([onWidgetUpdated, onTimeout]); + is(raceWinner, timeoutRes, "Dragging is disabled after mouseup"); + + info("Add another point, which should be the first one for the line"); + onWidgetUpdated = widget.once("updated"); + onRuleViewChanged = view.once("ruleview-changed"); + + EventUtils.synthesizeMouse( + svgEl, + svgRect.width / 3, + svgRect.height - 1, + { clickCount: 2, shiftKey: true }, + widget.parent.ownerGlobal + ); + + newValue = await onWidgetUpdated; + is( + newValue, + `linear(0 30%, 0.7 50%, 0.5 50%, 1 100%)`, + "Widget was updated with expected value" + ); + checkChartState( + panel, + [ + [0.3, 0], + [0.5, 0.7], + [0.5, 0.5], + [1, 1], + ], + "testChart - point added at beginning" + ); + await onRuleViewChanged; + await checkRuleView(view, selector, property, newValue); + + info("Check that the points can't be moved past previous point"); + onWidgetUpdated = widget.once("updated"); + onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.synthesizeMouseAtCenter( + panel.querySelector( + `svg.chart .control-points-group .control-point:nth-of-type(2)` + ), + { type: "mousedown" }, + widget.parent.ownerGlobal + ); + + EventUtils.synthesizeMouse( + svgEl, + 0, + svgRect.height / 3, + { type: "mousemove", shiftKey: true }, + widget.parent.ownerGlobal + ); + newValue = await onWidgetUpdated; + is( + newValue, + `linear(0 30%, 0.7 30%, 0.5 50%, 1 100%)`, + "point wasn't moved past previous point (30%)" + ); + checkChartState( + panel, + [ + [0.3, 0], + [0.3, 0.7], + [0.5, 0.5], + [1, 1], + ], + "testChart - point moved constrained by previous point" + ); + await onRuleViewChanged; + await checkRuleView(view, selector, property, newValue); + + info("Stop dragging"); + EventUtils.synthesizeMouseAtCenter( + svgEl, + { type: "mouseup" }, + widget.parent.ownerGlobal + ); + + info( + "Check that timing preview is destroyed if prefers-reduced-motion gets enabled" + ); + const getTimingFunctionPreview = () => + panel.querySelector(".timing-function-preview"); + ok(getTimingFunctionPreview(), "By default, timing preview is visible"); + info("Enable prefersReducedMotion"); + await pushPref("ui.prefersReducedMotion", 1); + await waitFor(() => !getTimingFunctionPreview()); + ok(true, "timing preview was removed after enabling prefersReducedMotion"); + + info("Disable prefersReducedMotion"); + await pushPref("ui.prefersReducedMotion", 0); + await waitFor(() => getTimingFunctionPreview()); + ok( + true, + "timing preview was added back after disabling prefersReducedMotion" + ); + + info("Hide tooltip with escape to cancel modification"); + const onHidden = tooltip.tooltip.once("hidden"); + const onModifications = view.once("ruleview-changed"); + focusAndSendKey(widget.parent.ownerDocument.defaultView, "ESCAPE"); + await onHidden; + await onModifications; + + await checkRuleView( + view, + selector, + property, + "linear(0, 1 50% 100%)", + "linear(0 0%, 1 50%, 1 100%)" + ); +}); + +/** + * Check that the svg chart line and control points are placed where we expect them. + * + * @param {ToolipPanel} panel + * @param {Array<Array<Number>>} expectedPoints: Array of coordinated + * @param {String} messagePrefix + */ +function checkChartState(panel, expectedPoints, messagePrefix = "") { + const svgLine = panel.querySelector("svg.chart .chart-linear"); + is( + svgLine.getAttribute("points"), + expectedPoints.map(([x, y]) => `${x},${1 - y}`).join(" "), + `${messagePrefix} line has the expected points` + ); + + const controlPoints = panel.querySelectorAll( + `svg.chart .control-points-group .control-point` + ); + + is( + controlPoints.length, + expectedPoints.length, + `${messagePrefix} the expected number of control points were created` + ); + controlPoints.forEach((controlPoint, i) => { + is( + parseFloat(controlPoint.getAttribute("cx")), + expectedPoints[i][0], + `${messagePrefix} Control point ${i} has correct cx` + ); + is( + parseFloat(controlPoint.getAttribute("cy")), + // XXX work around floating point issues + Math.round((1 - expectedPoints[i][1]) * 10) / 10, + `${messagePrefix} Control point ${i} has correct cy` + ); + }); +} + +/** + * Checks if the property in the rule view has the expected state + * + * @param {RuleView} view + * @param {String} selector + * @param {String} property + * @param {String} expectedLinearValue: Expected value in the rule view + * @param {String} expectedComputedLinearValue: Expected computed value. Defaults to expectedLinearValue. + * @returns {Element|null} + */ +async function checkRuleView( + view, + selector, + property, + expectedLinearValue, + expectedComputedLinearValue = expectedLinearValue +) { + await waitForComputedStyleProperty( + selector, + null, + property, + expectedComputedLinearValue + ); + + is( + getRuleViewProperty(view, selector, property).valueSpan.textContent, + expectedLinearValue, + `${selector} ${property} has expected value` + ); + const swatch = getRuleViewLinearEasingSwatch(view, selector, property); + is( + swatch.getAttribute("data-linear"), + expectedLinearValue, + `${selector} ${property} swatch has expected "data-linear" attribute` + ); +} + +/** + * Returns the linear easing swatch for a rule (defined by its selector), and a property. + * + * @param {RuleView} view + * @param {String} selector + * @param {String} property + * @returns {Element|null} + */ +function getRuleViewLinearEasingSwatch(view, selector, property) { + return getRuleViewProperty(view, selector, property).valueSpan.querySelector( + ".ruleview-lineareasingswatch" + ); +} |