diff options
Diffstat (limited to 'comm/mail/base/test/browser/browser_orderableTreeListbox.js')
-rw-r--r-- | comm/mail/base/test/browser/browser_orderableTreeListbox.js | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/comm/mail/base/test/browser/browser_orderableTreeListbox.js b/comm/mail/base/test/browser/browser_orderableTreeListbox.js new file mode 100644 index 0000000000..54634cbd88 --- /dev/null +++ b/comm/mail/base/test/browser/browser_orderableTreeListbox.js @@ -0,0 +1,481 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint mozilla/no-arbitrary-setTimeout: off */ + +let dragService = Cc["@mozilla.org/widget/dragservice;1"].getService( + Ci.nsIDragService +); + +let WAIT_TIME = 0; + +let tabmail = document.getElementById("tabmail"); +registerCleanupFunction(() => { + tabmail.closeOtherTabs(tabmail.tabInfo[0]); + Services.prefs.clearUserPref("ui.prefersReducedMotion"); + Services.prefs.clearUserPref("mailnews.default_view_flags"); +}); + +async function withMotion(subtest) { + Services.prefs.setIntPref("ui.prefersReducedMotion", 0); + WAIT_TIME = 300; + await TestUtils.waitForCondition( + () => !matchMedia("(prefers-reduced-motion)").matches + ); + return subtest(); +} + +async function withoutMotion(subtest) { + Services.prefs.setIntPref("ui.prefersReducedMotion", 1); + WAIT_TIME = 0; + await TestUtils.waitForCondition( + () => matchMedia("(prefers-reduced-motion)").matches + ); + await subtest(); +} + +let win, doc, list, dataTransfer; + +async function orderWithKeys(key) { + selectHandler.reset(); + orderedHandler.reset(); + + list.addEventListener("select", selectHandler); + list.addEventListener("ordered", orderedHandler); + EventUtils.synthesizeKey(key, { altKey: true }, win); + await new Promise(resolve => win.setTimeout(resolve, WAIT_TIME)); + list.removeEventListener("select", selectHandler); + list.removeEventListener("ordered", orderedHandler); + + await checkNoTransformations(); +} + +async function startDrag(index) { + let listRect = list.getBoundingClientRect(); + let clientY = listRect.top + index * 32 + 4; + + dragService.startDragSessionForTests(Ci.nsIDragService.DRAGDROP_ACTION_NONE); + [, dataTransfer] = EventUtils.synthesizeDragOver( + list.rows[index], + list, + null, + null, + win, + win, + { + clientY, + _domDispatchOnly: true, + } + ); + + await new Promise(resolve => setTimeout(resolve, WAIT_TIME)); +} + +async function continueDrag(index) { + let listRect = list.getBoundingClientRect(); + let destClientX = listRect.left + listRect.width / 2; + let destClientY = listRect.top + index * 32 + 4; + let destScreenX = win.mozInnerScreenX + destClientX; + let destScreenY = win.mozInnerScreenY + destClientY; + + let result = EventUtils.sendDragEvent( + { + type: "dragover", + screenX: destScreenX, + screenY: destScreenY, + clientX: destClientX, + clientY: destClientY, + dataTransfer, + _domDispatchOnly: true, + }, + list, + win + ); + + await new Promise(resolve => setTimeout(resolve, WAIT_TIME)); + return result; +} + +async function endDrag(index) { + let listRect = list.getBoundingClientRect(); + let clientY = listRect.top + index * 32 + 4; + + EventUtils.synthesizeDropAfterDragOver(false, dataTransfer, list, win, { + clientY, + _domDispatchOnly: true, + }); + list.dispatchEvent(new CustomEvent("dragend", { bubbles: true })); + dragService.endDragSession(true); + + await new Promise(resolve => setTimeout(resolve, WAIT_TIME)); +} + +function checkRowOrder(expectedOrder) { + expectedOrder = expectedOrder.split(" ").map(i => `row-${i}`); + Assert.equal(list.rowCount, expectedOrder.length, "rowCount is correct"); + Assert.deepEqual( + list.rows.map(row => row.id), + expectedOrder, + "order in DOM is correct" + ); + + let apparentOrder = list.rows.sort( + (a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top + ); + Assert.deepEqual( + apparentOrder.map(row => row.id), + expectedOrder, + "order on screen is correct" + ); + + if (orderedHandler.orderAtEvent) { + Assert.deepEqual( + orderedHandler.orderAtEvent, + expectedOrder, + "order at the last 'ordered' event was correct" + ); + } +} + +function checkYPositions(...expectedPositions) { + let offset = list.getBoundingClientRect().top; + + for (let i = 0; i < 5; i++) { + let id = `row-${i + 1}`; + let row = doc.getElementById(id); + Assert.equal( + row.getBoundingClientRect().top - offset, + expectedPositions[i], + id + ); + } +} + +async function checkNoTransformations() { + for (let row of list.children) { + await TestUtils.waitForCondition( + () => win.getComputedStyle(row).transform == "none", + `${row.id} has no transforms` + ); + Assert.equal( + row + .getAnimations() + .filter(animation => animation.transitionProperty != "opacity").length, + 0, + `${row.id} has no animations` + ); + } +} + +let selectHandler = { + seenEvent: null, + + reset() { + this.seenEvent = null; + }, + handleEvent(event) { + this.seenEvent = event; + }, +}; + +let orderedHandler = { + seenEvent: null, + orderAtEvent: null, + + reset() { + this.seenEvent = null; + this.orderAtEvent = null; + }, + handleEvent(event) { + if (this.seenEvent) { + throw new Error("we already have an 'ordered' event"); + } + this.seenEvent = event; + this.orderAtEvent = list.rows.map(row => row.id); + }, +}; + +/** Test Alt+Up and Alt+Down. */ +async function subtestKeyReorder() { + list.focus(); + list.selectedIndex = 0; + + // Move row 1 down the list to the bottom. + + await orderWithKeys("KEY_ArrowDown"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("2 2-1 2-2 1 3 3-1 3-2 3-3 4 5 5-1 5-2"); + + // Some additional checks to prove the right row is selected. + + Assert.ok(!selectHandler.seenEvent); + Assert.equal(list.selectedIndex, 3, "correct index is selected"); + Assert.equal( + list.querySelector(".selected").id, + "row-1", + "correct row is selected" + ); + + EventUtils.synthesizeKey("KEY_ArrowUp", {}, win); + Assert.equal( + list.querySelector(".selected").id, + "row-2-2", + "key press moved to the correct row" + ); + EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); + + await orderWithKeys("KEY_ArrowDown"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("2 2-1 2-2 3 3-1 3-2 3-3 1 4 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowDown"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("2 2-1 2-2 3 3-1 3-2 3-3 4 1 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowDown"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("2 2-1 2-2 3 3-1 3-2 3-3 4 5 5-1 5-2 1"); + + // Move row 1 back to the top. + + await orderWithKeys("KEY_ArrowUp"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("2 2-1 2-2 3 3-1 3-2 3-3 4 1 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowUp"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("2 2-1 2-2 3 3-1 3-2 3-3 1 4 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowUp"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("2 2-1 2-2 1 3 3-1 3-2 3-3 4 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowUp"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("1 2 2-1 2-2 3 3-1 3-2 3-3 4 5 5-1 5-2"); + + // Move row 3 around. Row 3 has children, so we're checking they move with it. + + list.selectedIndex = 4; + + await orderWithKeys("KEY_ArrowUp"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("1 3 3-1 3-2 3-3 2 2-1 2-2 4 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowUp"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("3 3-1 3-2 3-3 1 2 2-1 2-2 4 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowDown"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("1 3 3-1 3-2 3-3 2 2-1 2-2 4 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowDown"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("1 2 2-1 2-2 3 3-1 3-2 3-3 4 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowDown"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("1 2 2-1 2-2 4 3 3-1 3-2 3-3 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowDown"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("1 2 2-1 2-2 4 5 5-1 5-2 3 3-1 3-2 3-3"); + + await orderWithKeys("KEY_ArrowUp"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("1 2 2-1 2-2 4 3 3-1 3-2 3-3 5 5-1 5-2"); + + await orderWithKeys("KEY_ArrowUp"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("1 2 2-1 2-2 3 3-1 3-2 3-3 4 5 5-1 5-2"); +} + +/** Drag the first item to the end. */ +async function subtestDragReorder1() { + orderedHandler.reset(); + list.addEventListener("ordered", orderedHandler); + + checkYPositions(1, 33, 129, 257, 289); + + await startDrag(0); + checkYPositions(1, 33, 129, 257, 289); + + await continueDrag(2); + checkYPositions(52, 1, 129, 257, 289); + await continueDrag(3); + checkYPositions(84, 1, 129, 257, 289); + await continueDrag(4); + checkYPositions(116, 1, 129, 257, 289); + await continueDrag(5); + checkYPositions(148, 1, 129, 257, 289); + await continueDrag(6); + checkYPositions(180, 1, 97, 257, 289); + await continueDrag(7); + checkYPositions(212, 1, 97, 257, 289); + await continueDrag(8); + checkYPositions(244, 1, 97, 225, 289); + await continueDrag(9); + checkYPositions(276, 1, 97, 225, 289); + await continueDrag(10); + checkYPositions(308, 1, 97, 225, 257); + await continueDrag(11); + checkYPositions(340, 1, 97, 225, 257); + await continueDrag(12); + checkYPositions(353, 1, 97, 225, 257); + + await endDrag(12); + list.removeEventListener("ordered", orderedHandler); + + Assert.ok(orderedHandler.seenEvent); + checkYPositions(353, 1, 97, 225, 257); + checkRowOrder("2 2-1 2-2 3 3-1 3-2 3-3 4 5 5-1 5-2 1"); + await checkNoTransformations(); +} + +/** Drag the (now) last item back to the start. */ +async function subtestDragReorder2() { + orderedHandler.reset(); + list.addEventListener("ordered", orderedHandler); + + await startDrag(11); + checkYPositions(340, 1, 97, 225, 257); + + await continueDrag(9); + checkYPositions(276, 1, 97, 225, 289); + + await continueDrag(7); + checkYPositions(212, 1, 97, 257, 289); + + await continueDrag(4); + checkYPositions(116, 1, 129, 257, 289); + + await continueDrag(1); + checkYPositions(20, 33, 129, 257, 289); + + await endDrag(0); + list.removeEventListener("ordered", orderedHandler); + + Assert.ok(orderedHandler.seenEvent); + checkYPositions(1, 33, 129, 257, 289); + checkRowOrder("1 2 2-1 2-2 3 3-1 3-2 3-3 4 5 5-1 5-2"); + await checkNoTransformations(); +} + +/** + * Listen for the 'ordering' event and prevent dropping on some rows. + * + * In this test, we'll prevent dragging an item below the last one - row-5 and + * its descendants. Other use cases may be possible but haven't been needed + * yet, so they are untested. + */ +async function subtestDragUndroppable() { + let originalGetter = list.__lookupGetter__("_orderableChildren"); + list.__defineGetter__("_orderableChildren", function () { + let rows = [...this.children]; + rows.pop(); + return rows; + }); + + orderedHandler.reset(); + list.addEventListener("ordered", orderedHandler); + + checkYPositions(1, 33, 129, 257, 289); + + await startDrag(0); + checkYPositions(1, 33, 129, 257, 289); + + await continueDrag(8); + checkYPositions(244, 1, 97, 225, 289); + await continueDrag(9); + checkYPositions(257, 1, 97, 225, 289); + await continueDrag(10); + checkYPositions(257, 1, 97, 225, 289); + await continueDrag(11); + checkYPositions(257, 1, 97, 225, 289); + await continueDrag(12); + checkYPositions(257, 1, 97, 225, 289); + + await endDrag(12); + list.removeEventListener("ordered", orderedHandler); + + Assert.ok(orderedHandler.seenEvent); + checkYPositions(257, 1, 97, 225, 289); + checkRowOrder("2 2-1 2-2 3 3-1 3-2 3-3 4 1 5 5-1 5-2"); + await checkNoTransformations(); + + // Move row-3 down with the keyboard. + + list.selectedIndex = 7; + await orderWithKeys("KEY_ArrowDown"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("2 2-1 2-2 3 3-1 3-2 3-3 1 4 5 5-1 5-2"); + + // It should not move further down. + + await orderWithKeys("KEY_ArrowDown"); + Assert.ok(!orderedHandler.seenEvent); + checkRowOrder("2 2-1 2-2 3 3-1 3-2 3-3 1 4 5 5-1 5-2"); + + // Reset the order. + + await orderWithKeys("KEY_ArrowUp"); + Assert.ok(orderedHandler.seenEvent); + checkRowOrder("2 2-1 2-2 3 3-1 3-2 3-3 4 1 5 5-1 5-2"); + + orderedHandler.reset(); + await startDrag(8); + await continueDrag(1); + await endDrag(1); + checkRowOrder("1 2 2-1 2-2 3 3-1 3-2 3-3 4 5 5-1 5-2"); + + list.__defineGetter__("_orderableChildren", originalGetter); +} + +add_setup(async function () { + // Make sure the whole test runs with an unthreaded view in all folders. + Services.prefs.setIntPref("mailnews.default_view_flags", 0); + + let tab = tabmail.openTab("contentTab", { + url: "chrome://mochitests/content/browser/comm/mail/base/test/browser/files/orderableTreeListbox.xhtml", + }); + + await BrowserTestUtils.browserLoaded(tab.browser); + tab.browser.focus(); + + win = tab.browser.contentWindow; + doc = win.document; + + list = doc.querySelector(`ol[is="orderable-tree-listbox"]`); + Assert.ok(!!list, "the list exists"); + + checkRowOrder("1 2 2-1 2-2 3 3-1 3-2 3-3 4 5 5-1 5-2"); + Assert.equal(list.selectedIndex, 0, "selectedIndex is set to 0"); +}); + +add_task(async function testKeyReorder() { + await withMotion(subtestKeyReorder); +}); +add_task(async function testDragReorder1() { + await withMotion(subtestDragReorder1); +}); +add_task(async function testDragReorder2() { + await withMotion(subtestDragReorder2); +}); +add_task(async function testDragUndroppable() { + await withMotion(subtestDragUndroppable); +}); + +add_task(async function testKeyReorderReducedMotion() { + await withoutMotion(subtestKeyReorder); +}); +add_task(async function testDragReorder1ReducedMotion() { + await withoutMotion(subtestDragReorder1); +}); +add_task(async function testDragReorder2ReducedMotion() { + await withoutMotion(subtestDragReorder2); +}); +add_task(async function testDragUndroppableReducedMotion() { + await withoutMotion(subtestDragUndroppable); +}); |