summaryrefslogtreecommitdiffstats
path: root/comm/mail/base/test/browser/browser_paneSplitter.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/base/test/browser/browser_paneSplitter.js')
-rw-r--r--comm/mail/base/test/browser/browser_paneSplitter.js572
1 files changed, 572 insertions, 0 deletions
diff --git a/comm/mail/base/test/browser/browser_paneSplitter.js b/comm/mail/base/test/browser/browser_paneSplitter.js
new file mode 100644
index 0000000000..1646703b96
--- /dev/null
+++ b/comm/mail/base/test/browser/browser_paneSplitter.js
@@ -0,0 +1,572 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let tabmail = document.getElementById("tabmail");
+registerCleanupFunction(() => {
+ tabmail.closeOtherTabs(tabmail.tabInfo[0]);
+});
+
+// Increase this value to slow the test down if you want to see what it is doing.
+let MOUSE_DELAY = 0;
+
+let win, doc;
+
+let resizingEvents = 0;
+let resizedEvents = 0;
+let collapsedEvents = 0;
+let expandedEvents = 0;
+
+// This object keeps the test simple by removing the differences between
+// horizontal and vertical, and which pane is controlled by the splitter.
+let testRunner = {
+ outer: null, // The container for the splitter and panes.
+ splitter: null, // The splitter.
+ resizedIsBefore: null, // Whether resized is before the splitter.
+ resized: null, // The pane the splitter controls the size of.
+ fill: null, // The pane that splitter doesn't control.
+ dimension: null, // Which dimension the splitter resizes.
+
+ getSize(element) {
+ return element.getBoundingClientRect()[this.dimension];
+ },
+
+ assertElementSizes(size, msg = "") {
+ Assert.equal(
+ this.getSize(this.resized),
+ size,
+ `Resized element should take up the expected ${this.dimension}: ${msg}`
+ );
+ Assert.equal(
+ this.getSize(this.fill),
+ 500 - size,
+ `Fill element should take up the rest of the ${this.dimension}: ${msg}`
+ );
+ },
+
+ assertSplitterSize(size, msg = "") {
+ Assert.equal(
+ this.splitter[this.dimension],
+ size,
+ `Splitter ${this.dimension} should match expected ${size}: ${msg}`
+ );
+ },
+
+ get minSizeProperty() {
+ return this.dimension == "width" ? "minWidth" : "minHeight";
+ },
+
+ get maxSizeProperty() {
+ return this.dimension == "width" ? "maxWidth" : "maxHeight";
+ },
+
+ get collapseSizeAttribute() {
+ return this.dimension == "width" ? "collapse-width" : "collapse-height";
+ },
+
+ setCollapseSize(size) {
+ this.splitter.setAttribute(this.collapseSizeAttribute, size);
+ },
+
+ clearCollapseSize() {
+ this.splitter.removeAttribute(this.collapseSizeAttribute);
+ },
+
+ async synthMouse(position, type = "mousemove", otherPosition = 50) {
+ let x, y;
+ if (!this.resizedIsBefore) {
+ position = 500 - position;
+ }
+ if (this.dimension == "width") {
+ [x, y] = [position, otherPosition];
+ } else {
+ [x, y] = [otherPosition, position];
+ }
+ EventUtils.synthesizeMouse(
+ this.splitter.parentNode,
+ x,
+ y,
+ { type, buttons: 1 },
+ win
+ );
+
+ if (MOUSE_DELAY) {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, MOUSE_DELAY));
+ }
+
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ },
+};
+
+add_setup(async function () {
+ let tab = tabmail.openTab("contentTab", {
+ url: "chrome://mochitests/content/browser/comm/mail/base/test/browser/files/paneSplitter.xhtml",
+ });
+
+ await BrowserTestUtils.browserLoaded(tab.browser);
+ tab.browser.focus();
+
+ win = tab.browser.contentWindow;
+ doc = win.document;
+
+ win.addEventListener("splitter-resizing", event => resizingEvents++);
+ win.addEventListener("splitter-resized", event => resizedEvents++);
+ win.addEventListener("splitter-collapsed", event => collapsedEvents++);
+ win.addEventListener("splitter-expanded", event => expandedEvents++);
+});
+
+add_task(async function testHorizontalBefore() {
+ let outer = doc.getElementById("horizontal-before");
+ let resized = outer.querySelector(".resized");
+ let splitter = outer.querySelector(`hr[is="pane-splitter"]`);
+ let fill = outer.querySelector(".fill");
+
+ Assert.equal(resized.clientWidth, 200);
+ Assert.equal(fill.clientWidth, 300);
+ Assert.equal(win.getComputedStyle(splitter).cursor, "ew-resize");
+
+ testRunner.outer = outer;
+ testRunner.splitter = splitter;
+ testRunner.resizedIsBefore = true;
+ testRunner.resized = resized;
+ testRunner.fill = fill;
+ testRunner.dimension = "width";
+
+ await subtestDrag();
+ await subtestDragSizeBounds();
+ await subtestDragAutoCollapse();
+ await subtestCollapseExpand();
+});
+
+add_task(async function testHorizontalAfter() {
+ let outer = doc.getElementById("horizontal-after");
+ let fill = outer.querySelector(".fill");
+ let splitter = outer.querySelector(`hr[is="pane-splitter"]`);
+ let resized = outer.querySelector(".resized");
+
+ Assert.equal(fill.clientWidth, 300);
+ Assert.equal(resized.clientWidth, 200);
+ Assert.equal(win.getComputedStyle(splitter).cursor, "ew-resize");
+
+ testRunner.outer = outer;
+ testRunner.splitter = splitter;
+ testRunner.resizedIsBefore = false;
+ testRunner.resized = resized;
+ testRunner.fill = fill;
+ testRunner.dimension = "width";
+
+ await subtestDrag();
+ await subtestDragSizeBounds();
+ await subtestDragAutoCollapse();
+ await subtestCollapseExpand();
+});
+
+add_task(async function testVerticalBefore() {
+ let outer = doc.getElementById("vertical-before");
+ let resized = outer.querySelector(".resized");
+ let splitter = outer.querySelector(`hr[is="pane-splitter"]`);
+ let fill = outer.querySelector(".fill");
+
+ Assert.equal(resized.clientHeight, 200);
+ Assert.equal(fill.clientHeight, 300);
+ Assert.equal(win.getComputedStyle(splitter).cursor, "ns-resize");
+
+ testRunner.outer = outer;
+ testRunner.splitter = splitter;
+ testRunner.resizedIsBefore = true;
+ testRunner.resized = resized;
+ testRunner.fill = fill;
+ testRunner.dimension = "height";
+
+ await subtestDrag();
+ await subtestDragSizeBounds();
+ await subtestDragAutoCollapse();
+ await subtestCollapseExpand();
+});
+
+add_task(async function testVerticalAfter() {
+ let outer = doc.getElementById("vertical-after");
+ let fill = outer.querySelector(".fill");
+ let splitter = outer.querySelector(`hr[is="pane-splitter"]`);
+ let resized = outer.querySelector(".resized");
+
+ testRunner.outer = outer;
+ testRunner.splitter = splitter;
+ testRunner.resizedIsBefore = false;
+ testRunner.resized = resized;
+ testRunner.fill = fill;
+ testRunner.dimension = "height";
+
+ Assert.equal(fill.clientHeight, 300);
+ Assert.equal(resized.clientHeight, 200);
+ Assert.equal(win.getComputedStyle(splitter).cursor, "ns-resize");
+
+ await subtestDrag();
+ await subtestDragSizeBounds();
+ await subtestDragAutoCollapse();
+ await subtestCollapseExpand();
+});
+
+async function subtestDrag() {
+ info("subtestDrag");
+ resizingEvents = 0;
+ resizedEvents = 0;
+
+ let originalPosition = testRunner.getSize(testRunner.resized);
+ let position = 200;
+
+ await testRunner.synthMouse(position, "mousedown");
+
+ await testRunner.synthMouse(position, "mousemove", 25);
+ Assert.equal(resizingEvents, 0, "moving up the splitter does nothing");
+ await testRunner.synthMouse(position, "mousemove", 75);
+ Assert.equal(resizingEvents, 0, "moving down the splitter does nothing");
+
+ position--;
+ await testRunner.synthMouse(position);
+ Assert.equal(resizingEvents, 0, "moving 1px does nothing");
+
+ position--;
+ await testRunner.synthMouse(position);
+ Assert.equal(resizingEvents, 0, "moving 2px does nothing");
+
+ position--;
+ await testRunner.synthMouse(position);
+ Assert.equal(resizingEvents, 1, "a resizing event fired");
+
+ // Drag in steps to the left-hand/top end.
+ for (; position >= 0; position -= 50) {
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(position);
+ }
+
+ // Drag beyond the left-hand/top end.
+ position = -50;
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(0);
+
+ // Drag in steps to the right-hand/bottom end.
+ for (let position = 0; position <= 500; position += 50) {
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(position);
+ }
+
+ // Drag beyond the right-hand/bottom end.
+ position = 550;
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(500);
+
+ // Drop.
+ position = 400;
+ Assert.equal(resizingEvents, 1, "no more resizing events fired");
+ Assert.equal(resizedEvents, 0, "no resized events fired");
+ await testRunner.synthMouse(position);
+ await testRunner.synthMouse(position, "mouseup");
+ testRunner.assertElementSizes(400);
+ Assert.equal(resizingEvents, 1, "no more resizing events fired");
+ Assert.equal(resizedEvents, 1, "a resized event fired");
+
+ // Pick up again.
+ await testRunner.synthMouse(position, "mousedown");
+
+ // Move.
+ for (; position >= originalPosition; position -= 50) {
+ await testRunner.synthMouse(position);
+ }
+
+ // Drop.
+ Assert.equal(resizingEvents, 2, "a resizing event fired");
+ Assert.equal(resizedEvents, 1, "no more resized events fired");
+ await testRunner.synthMouse(position, "mouseup");
+ testRunner.assertElementSizes(originalPosition);
+ Assert.equal(resizingEvents, 2, "no more resizing events fired");
+ Assert.equal(resizedEvents, 2, "a resized event fired");
+}
+
+async function subtestDragSizeBounds() {
+ info("subtestDragSizeBounds");
+
+ let { splitter, resized, fill, minSizeProperty, maxSizeProperty } =
+ testRunner;
+
+ // Various min or max sizes to set on the resized and fill elements.
+ // NOTE: the sum of the max sizes is greater than 500px.
+ // Moreover, the resized element's min size is below 200px, and the max size
+ // above it. Similarly, the fill element's min size is below 300px. This
+ // ensures that the initial sizes of 200px and 300px are within their
+ // respective min-max bounds.
+ // NOTE: We do not set a max size on the fill element. The grid layout does
+ // not handle this. Nor is it an expected usage of the splitter.
+ for (let [minResized, min] of [
+ [null, 0],
+ ["100.5px", 100.5],
+ ]) {
+ for (let [maxResized, expectMax1] of [
+ [null, 500],
+ ["360px", 360],
+ ]) {
+ for (let [minFill, expectMax2] of [
+ [null, 500],
+ ["148px", 352],
+ ]) {
+ info(`Bounds [${minResized}, ${maxResized}] and [${minFill}, none]`);
+ let max = Math.min(expectMax1, expectMax2);
+ info(`Overall bound [${min}px, ${max}px]`);
+
+ // Construct a set of positions we are interested in.
+ let roundMin = Math.floor(min);
+ let roundMax = Math.ceil(max);
+ let positionSet = [-50, 150, 350, 550];
+ // Include specific positions around the minimum and maximum points.
+ positionSet.push(roundMin - 1, roundMin, roundMin + 1);
+ positionSet.push(roundMax - 1, roundMax, roundMax + 1);
+ positionSet.sort();
+
+ // Reset the splitter.
+ splitter.width = null;
+ splitter.height = null;
+
+ resized.style[minSizeProperty] = minResized;
+ resized.style[maxSizeProperty] = maxResized;
+ fill.style[minSizeProperty] = minFill;
+
+ testRunner.assertElementSizes(200, "initial position");
+ await testRunner.synthMouse(200, "mousedown");
+
+ for (let position of positionSet) {
+ await testRunner.synthMouse(position);
+ let size = Math.min(Math.max(position, min), max);
+ testRunner.assertElementSizes(size, `Moved forward to ${position}`);
+ testRunner.assertSplitterSize(size, `Moved forward to ${position}`);
+ }
+
+ await testRunner.synthMouse(500);
+ await testRunner.synthMouse(500, "mouseup");
+ testRunner.assertElementSizes(max, "positioned at max");
+ testRunner.assertSplitterSize(max, "positioned at max");
+
+ // Reverse.
+ await testRunner.synthMouse(max, "mousedown");
+
+ for (let position of positionSet.reverse()) {
+ await testRunner.synthMouse(position);
+ let size = Math.min(Math.max(position, min), max);
+ testRunner.assertElementSizes(size, `Moved backward to ${position}`);
+ testRunner.assertSplitterSize(size, `Moved backward to ${position}`);
+ }
+
+ await testRunner.synthMouse(0);
+ await testRunner.synthMouse(0, "mouseup");
+ testRunner.assertElementSizes(min, "positioned at min");
+ testRunner.assertSplitterSize(min, "positioned at min");
+ }
+ }
+ }
+
+ // Reset.
+ splitter.width = null;
+ splitter.height = null;
+ resized.style[minSizeProperty] = null;
+ resized.style[maxSizeProperty] = null;
+ fill.style[minSizeProperty] = null;
+}
+
+async function subtestDragAutoCollapse() {
+ info("subtestDragAutoCollapse");
+ testRunner.setCollapseSize(78);
+
+ collapsedEvents = 0;
+ expandedEvents = 0;
+
+ let { splitter } = testRunner;
+
+ let originalPosition = 200;
+
+ // Drag in steps toward the left-hand/top end.
+ await testRunner.synthMouse(200, "mousedown");
+ for (let position of [180, 160, 140, 120, 100, 80, 78]) {
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(
+ position,
+ `Should have ${position} size at ${position}`
+ );
+ Assert.ok(!splitter.isCollapsed, `Should not be collapsed at ${position}`);
+ }
+
+ // For the first 20 pixels inside the minimum size, nothing happens.
+ for (let position of [74, 68, 64, 60, 58]) {
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(
+ 78,
+ `Should be at collapse-size at ${position}`
+ );
+ Assert.ok(!splitter.isCollapsed, `Should not be collapsed at ${position}`);
+ }
+
+ // Then the pane collapses.
+ await testRunner.synthMouse(57);
+ Assert.equal(collapsedEvents, 1, "collapsed event fired");
+ for (let position of [57, 55, 51, 40, 20, 0, -20]) {
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(0, `Should have no size at ${position}`);
+ Assert.ok(splitter.isCollapsed, `Should be collapsed at ${position}`);
+ }
+
+ await testRunner.synthMouse(-20, "mouseup");
+ testRunner.assertElementSizes(
+ 0,
+ "Should be at min size after releasing mouse"
+ );
+ Assert.ok(splitter.isCollapsed, "Should be collapsed after releasing mouse");
+
+ // Drag it from the collapsed state.
+ await testRunner.synthMouse(0, "mousedown");
+ for (let position of [0, 8, 16, 19]) {
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(
+ 0,
+ `Should still have no size at ${position}`
+ );
+ Assert.ok(splitter.isCollapsed, `Should still be collapsed at ${position}`);
+ }
+
+ // Then the pane expands. For the first 20 pixels, nothing happens.
+ await testRunner.synthMouse(20);
+ Assert.equal(expandedEvents, 1, "expanded event fired");
+ for (let position of [40, 60, 78]) {
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(
+ 78,
+ `Should expand to collapse-size at ${position}`
+ );
+ Assert.ok(
+ !splitter.isCollapsed,
+ `Should no longer be collapsed at ${position}`
+ );
+ }
+
+ for (let position of [79, 100, 120, 200, 250, 300, 400, 450]) {
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(
+ position,
+ `Should have ${position} size at ${position}`
+ );
+ Assert.ok(!splitter.isCollapsed, `Should not be collapsed at ${position}`);
+ }
+
+ await testRunner.synthMouse(450, "mouseup");
+ testRunner.assertElementSizes(
+ 450,
+ "Should be at final size after releasing mouse"
+ );
+ Assert.ok(
+ !splitter.isCollapsed,
+ "Should not be collapsed after releasing mouse"
+ );
+
+ // Test that collapse and expand can happen in the same drag.
+ await testRunner.synthMouse(450, "mousedown");
+ let position;
+ let expectedSize;
+ for ([position, expectedSize] of [
+ [58, 78],
+ [57, 0],
+ [58, 78],
+ [57, 0],
+ ]) {
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(
+ expectedSize,
+ `Should have ${expectedSize} size at ${position}`
+ );
+ }
+ Assert.equal(collapsedEvents, 3, "collapsed events fired");
+ Assert.equal(expandedEvents, 2, "expanded events fired");
+ await testRunner.synthMouse(position, "mouseup");
+
+ // Test that expansion from collapsed reverts to normal behaviour after
+ // dragging out to the minimum size.
+ await testRunner.synthMouse(0, "mousedown");
+ for ([position, expectedSize] of [
+ [0, 0],
+ [10, 0],
+ [20, 78],
+ [40, 78],
+ [60, 78],
+ [40, 0],
+ [60, 78],
+ [40, 0],
+ [80, 80],
+ [100, 100],
+ ]) {
+ await testRunner.synthMouse(position);
+ testRunner.assertElementSizes(
+ expectedSize,
+ `Should have ${expectedSize} size at ${position}`
+ );
+ }
+ Assert.equal(collapsedEvents, 5, "collapsed events fired");
+ Assert.equal(expandedEvents, 5, "expanded events fired");
+ await testRunner.synthMouse(position, "mouseup");
+
+ // Restore the original position.
+ await testRunner.synthMouse(position, "mousedown");
+ position = originalPosition;
+ await testRunner.synthMouse(position);
+ await testRunner.synthMouse(position, "mouseup");
+ testRunner.assertElementSizes(originalPosition);
+
+ testRunner.clearCollapseSize();
+}
+
+async function subtestCollapseExpand() {
+ info("subtestCollapseExpand");
+ collapsedEvents = 0;
+ expandedEvents = 0;
+
+ let { splitter } = testRunner;
+
+ let originalSize = testRunner.getSize(testRunner.resized);
+
+ // Collapse.
+ Assert.ok(!splitter.isCollapsed, "splitter is not collapsed");
+ Assert.equal(collapsedEvents, 0, "no collapsed events have fired");
+
+ splitter.collapse();
+ testRunner.assertElementSizes(0);
+ Assert.ok(splitter.isCollapsed, "splitter is collapsed");
+ Assert.equal(collapsedEvents, 1, "a collapsed event fired");
+
+ splitter.collapse();
+ Assert.ok(splitter.isCollapsed, "splitter is collapsed");
+ Assert.equal(collapsedEvents, 1, "no more collapsed events have fired");
+
+ // Expand.
+ splitter.expand();
+ testRunner.assertElementSizes(originalSize);
+ Assert.ok(!splitter.isCollapsed, "splitter is not collapsed");
+ Assert.equal(expandedEvents, 1, "an expanded event fired");
+
+ splitter.expand();
+ Assert.ok(!splitter.isCollapsed, "splitter is not collapsed");
+ Assert.equal(expandedEvents, 1, "no more expanded events have fired");
+
+ collapsedEvents = 0;
+ expandedEvents = 0;
+
+ // Collapse again. Then drag to expand.
+ splitter.collapse();
+ Assert.equal(collapsedEvents, 1, "a collapsed event fired");
+
+ testRunner.setCollapseSize(78);
+
+ await testRunner.synthMouse(0, "mousedown");
+ await testRunner.synthMouse(200);
+ await testRunner.synthMouse(200, "mouseup");
+ testRunner.assertElementSizes(200);
+ Assert.ok(!splitter.isCollapsed, "splitter is not collapsed");
+ Assert.equal(expandedEvents, 1, "an expanded event fired");
+
+ testRunner.clearCollapseSize();
+}