diff options
Diffstat (limited to 'comm/mail/base/test/browser/files')
19 files changed, 1754 insertions, 0 deletions
diff --git a/comm/mail/base/test/browser/files/formContent.html b/comm/mail/base/test/browser/files/formContent.html new file mode 100644 index 0000000000..6779051746 --- /dev/null +++ b/comm/mail/base/test/browser/files/formContent.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <title>Form Content</title> + </head> + <body> + <form> + <div> + <input type="date" /> + </div> + <div> + <select> + <option value=""></option> + <option value="3.141592654">π</option> + <option value="6.283185308">τ</option> + </select> + </div> + <div> + <input list="letters"/> + <datalist id="letters"> + <option value="alpha"/> + <option value="beta"/> + <option value="gamma"/> + <option value="delta"/> + <option value="epsilon"/> + <option value="zeta"/> + <option value="eta"/> + <option value="theta"/> + <option value="iota"/> + <option value="kappa"/> + </datalist> + </div> + </form> + </body> +</html> diff --git a/comm/mail/base/test/browser/files/links.html b/comm/mail/base/test/browser/files/links.html new file mode 100644 index 0000000000..f5703dc4ef --- /dev/null +++ b/comm/mail/base/test/browser/files/links.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"/> + <title>Links to other places</title> +</head> +<body> + <h1>Links to things</h1> + <p>This page is a test of what happens when you click on links. It should be loaded from http://example.org:80.</p> + + <h2>This page:</h2> + <ul> + <li><a id="this-hash" href="#hash">Anchor on this page</a></li> + <li><a id="this-nohash" href="links.html">This page</a></li> + </ul> + + <h2>Pages on this domain:</h2> + <ul> + <li><a id="local-here" href="sampleContent.html">A page in the same directory</a></li> + <li><a id="local-elsewhere" href="/browser/comm/mail/components/extensions/test/browser/data/content.html">A page elsewhere</a></li> + </ul> + + <h2>Pages on other places on this TLD:</h2> + <ul> + <li><a id="other-https" href="https://example.org/browser/comm/mail/base/test/browser/files/links.html">This page, but over HTTPS</a></li> + <li><a id="other-port" href="http://example.org:8000/browser/comm/mail/base/test/browser/files/links.html">This page, but on example.com:8000</a></li> + <li><a id="other-subdomain" href="http://test1.example.org/browser/comm/mail/base/test/browser/files/links.html">This page, but on test1.example.com</a></li> + <li><a id="other-subsubdomain" href="http://sub1.test1.example.org/browser/comm/mail/base/test/browser/files/links.html">This page, but on sub1.test1.example.com</a></li> + </ul> + + <h2>Pages on a completely different domain:</h2> + <ul style="margin-bottom: 100vh;"> + <li><a id="other-domain" href="http://mochi.test:8888/browser/comm/mail/base/test/browser/files/links.html">This page, but on mochi.test</a></li> + </ul> + + <h2 id="hash">This is the hash target!</h2> +</body> +</html> diff --git a/comm/mail/base/test/browser/files/menulist.xhtml b/comm/mail/base/test/browser/files/menulist.xhtml new file mode 100644 index 0000000000..cba2bbcf86 --- /dev/null +++ b/comm/mail/base/test/browser/files/menulist.xhtml @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin/global.css"?> +<?xml-stylesheet type="text/css" href="chrome://messenger/skin/menulist.css"?> + +<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"> + <button id="before" label="I'm just a button" onclick="alert('I\'m a button!')"/> + + <menulist> + <menupopup> + <menuitem value="foo" label="foo"/> + <menuitem value="bar" label="bar"/> + </menupopup> + </menulist> + + <menulist is="menulist-editable"> + <menupopup> + <menuitem value="foo" label="foo"/> + <menuitem value="bar" label="bar"/> + </menupopup> + </menulist> + + <menulist is="menulist-editable" editable="true" width="100"> + <menupopup> + <menuitem value="foo" label="foo"/> + <menuitem value="bar" label="bar"/> + </menupopup> + </menulist> + + <button id="after" label="I'm just a button"/> +</window> diff --git a/comm/mail/base/test/browser/files/orderableTreeListbox.xhtml b/comm/mail/base/test/browser/files/orderableTreeListbox.xhtml new file mode 100644 index 0000000000..63154cbce9 --- /dev/null +++ b/comm/mail/base/test/browser/files/orderableTreeListbox.xhtml @@ -0,0 +1,171 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="utf-8" /> + <title>Test for the orderable-tree-listbox custom element</title> + <style> + :focus { + outline: 3px blue solid; + } + html { + height: 100%; + } + body { + height: 100%; + display: flex; + margin: 0; + } + #list { + overflow-y: auto; + white-space: nowrap; + margin: 1em; + border: 1px solid black; + width: 400px; + outline: none; + } + @media not (prefers-reduced-motion) { + #list { + scroll-behavior: smooth; + } + } + ol, ul { + margin: 0; + padding: 0; + list-style: none; + } + li > div { + display: flex; + align-items: center; + padding: 4px; + line-height: 24px; + } + li.selected > div { + color: white; + background-color: blue; + } + li > ul > li > div { + padding-inline-start: calc(1em + 8px); + } + li.collapsed > ul { + display: none; + } + div.twisty { + width: 1em; + height: 1em; + margin-inline-end: 4px; + } + li.children > div > div.twisty { + background-color: green; + } + li.children.collapsed > div > div.twisty { + background-color: red; + } + + #list > li { + transition: opacity 250ms; + } + #list > li.dragging { + opacity: 0.75; + } + </style> + <!-- This script is used for the automated test. --> + <script defer="defer" src="chrome://messenger/content/tree-listbox.js"></script> + <!-- This script is used when this file is loaded in a browser. --> + <script defer="defer" src="../../../content/widgets/tree-listbox.js"></script> +</head> +<body> + <ol id="list" is="orderable-tree-listbox" role="tree"> + <li id="row-1"> + <div draggable="true"> + <div class="twisty"></div> + Item 1 + </div> + </li> + <li id="row-2"> + <div draggable="true"> + <div class="twisty"></div> + Item 2 + </div> + <ul> + <li id="row-2-1"> + <div> + <div class="twisty"></div> + First child + </div> + </li> + <li id="row-2-2"> + <div> + <div class="twisty"></div> + Second child + </div> + </li> + </ul> + </li> + <li id="row-3"> + <div draggable="true"> + <div class="twisty"></div> + Item 3 + </div> + <ul> + <li id="row-3-1"> + <div> + <div class="twisty"></div> + First child + </div> + </li> + <li id="row-3-2"> + <div> + <div class="twisty"></div> + Second child + </div> + </li> + <li id="row-3-3"> + <div> + <div class="twisty"></div> + Third child + </div> + </li> + </ul> + </li> + <li id="row-4"> + <div draggable="true"> + <div class="twisty"></div> + Item 4 + </div> + </li> + <li id="row-5"> + <div draggable="true"> + <div class="twisty"></div> + Item 5 + </div> + <ul> + <li id="row-5-1"> + <div> + <div class="twisty"></div> + First child + </div> + </li> + <li id="row-5-2"> + <div> + <div class="twisty"></div> + Second child + </div> + </li> + </ul> + </li> + </ol> + + <div id="marker" style="position: absolute; left: 500px; border-top: 1px red solid;"></div> + <script> + function moveMarker(event) { + let marker = document.getElementById("marker"); + marker.style.top = `${event.clientY}px`; + marker.textContent = `${event.type} event here`; + } + + document.addEventListener("dragstart", moveMarker); + document.addEventListener("dragover", moveMarker); + document.addEventListener("drop", moveMarker); + </script> +</body> +</html> diff --git a/comm/mail/base/test/browser/files/paneSplitter.xhtml b/comm/mail/base/test/browser/files/paneSplitter.xhtml new file mode 100644 index 0000000000..7d25e5596e --- /dev/null +++ b/comm/mail/base/test/browser/files/paneSplitter.xhtml @@ -0,0 +1,122 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="utf-8" /> + <title>Test for the pane-splitter custom element</title> + <style> + hr[is="pane-splitter"] { + margin: 0 -3px; + border: none; + z-index: 1; + cursor: ew-resize; + opacity: .4; + background-color: red; + } + + #splitter3, + #splitter4 { + margin: -3px 0; + cursor: ns-resize; + } + + #horizontal-before { + display: grid; + grid-template-columns: minmax(auto, var(--splitter1-width)) 0 auto; + width: 500px; + height: 100px; + --splitter1-width: 200px; + margin: 1em; + } + + #horizontal-after { + display: grid; + grid-template-columns: auto 0 minmax(auto, var(--splitter2-width)); + width: 500px; + height: 100px; + --splitter2-width: 200px; + margin: 1em; + } + + #vertical-before { + display: inline-grid; + grid-template-rows: minmax(auto, var(--splitter3-height)) 0 auto; + width: 100px; + height: 500px; + --splitter3-height: 200px; + margin: 1em; + } + + #vertical-after { + display: inline-grid; + grid-template-rows: auto 0 minmax(auto, var(--splitter4-height)); + width: 100px; + height: 500px; + --splitter4-height: 200px; + margin: 1em; + } + + .resized { + background-color: lightblue; + } + + .fill { + background-color: lightslategrey; + } + </style> + <!-- This path is used for the automated test. --> + <script src="chrome://messenger/content/pane-splitter.js"></script> + <!-- This path is used when this file is loaded in a browser. --> + <script src="../../../content/widgets/pane-splitter.js"></script> + <script> + function moveMarker(event) { + let markerX = document.getElementById("markerX"); + markerX.style.left = `${event.clientX + window.scrollX}px`; + markerX.textContent = `${event.type} event here`; + + let markerY = document.getElementById("markerY"); + markerY.style.top = `${event.clientY + window.scrollY}px`; + markerY.textContent = `${event.type} event here`; + } + + document.addEventListener("mousedown", moveMarker); + document.addEventListener("mousemove", moveMarker); + document.addEventListener("mouseup", moveMarker); + + window.addEventListener("load", () => { + for (let splitter of document.querySelectorAll('hr[is="pane-splitter"]')) { + splitter.resizeElement = splitter.parentNode.querySelector(".resized"); + } + }); + </script> +</head> +<body> + <div id="horizontal-before"> + <div id="splitter1-before" class="resized"></div> + <hr is="pane-splitter" id="splitter1" resize-direction="horizontal" /> + <div id="splitter1-after" class="fill"></div> + </div> + + <div id="horizontal-after"> + <div id="splitter2-before" class="fill"></div> + <hr is="pane-splitter" id="splitter2" resize="next" resize-direction="horizontal" /> + <div id="splitter2-after" class="resized"></div> + </div> + + <div style="display: flex;"> + <div id="vertical-before"> + <div id="splitter3-before" class="resized"></div> + <hr is="pane-splitter" id="splitter3" /> + <div id="splitter3-after" class="fill"></div> + </div> + + <div id="vertical-after"> + <div id="splitter4-before" class="fill"></div> + <hr is="pane-splitter" id="splitter4" resize="next" /> + <div id="splitter4-after" class="resized"></div> + </div> + </div> + + <div id="markerX" style="position: absolute; top: 0px; border-left: 1px red solid;"></div> + <div id="markerY" style="position: absolute; left: 550px; border-top: 1px red solid;"></div> +</body> +</html> diff --git a/comm/mail/base/test/browser/files/rss.xml b/comm/mail/base/test/browser/files/rss.xml new file mode 100644 index 0000000000..8ff0540a66 --- /dev/null +++ b/comm/mail/base/test/browser/files/rss.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0"> + <channel> + <title>Test Feed</title> + <link>https://example.org/</link> + <description></description> + <lastBuildDate>Thu, 21 Jan 2021 17:57:54 +0000</lastBuildDate> + <language>en-US</language> + + <item> + <title>Test Article</title> + <link>https://example.org/browser/comm/mail/base/test/browser/files/sampleContent.html</link> + <pubDate>Wed, 20 Jan 2021 17:00:39 +0000</pubDate> + </item> + </channel> +</rss> diff --git a/comm/mail/base/test/browser/files/sampleContent.eml b/comm/mail/base/test/browser/files/sampleContent.eml new file mode 100644 index 0000000000..f0465ad2bd --- /dev/null +++ b/comm/mail/base/test/browser/files/sampleContent.eml @@ -0,0 +1,160 @@ +From andy@anway.invalid +Content-Type: multipart/related; + boundary="--------------CHOPCHOP0" +Subject: Big Meeting Today +From: "Andy Anway" <andy@anway.invalid> +To: "Bob Bell" <bob@bell.invalid> +Message-Id: <0@made.up.invalid> +Date: Tue, 01 Feb 2000 00:00:00 +1300 + +This is a multi-part message in MIME format. +----------------CHOPCHOP0 +Content-Type: text/html; charset=ISO-8859-1; format=flowed +Content-Transfer-Encoding: 7bit + +<!DOCTYPE html> +<html> + <head> + <link rel="icon" href="http://mochi.test:8888/browser/comm/mail/base/test/browser/files/tb-logo.png" /> + </head> + <body> + <p>This is a page of sample content for tests.</p> + <p><a href="https://www.thunderbird.net/">Link to a web page</a></p> + <form> + <input type="text" /> + </form> + <p><img src="cid:logo" width="304" height="84" /></p> + </body> +</html> + +----------------CHOPCHOP0 +Content-Type: image/png; charset=ISO-8859-1; format=flowed; + name="tb-logo.png" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; + filename="tb-logo.png" +Content-ID: <logo> + +iVBORw0KGgoAAAANSUhEUgAAATAAAABUCAMAAAAyN5s5AAAC91BMVEVMaXErLDVPU1wYFx1O +T1RPT1RNT1MTExoFBwZNTlNOTlNNT1RNTlNMTFRNTlMGBgoDBgYFBQZNTlQGBgdOTlNNTlMA +AwMEBQVNTlNOUFRMTFUAAABNTlMEBgdNT1QFBwoEBAZOTlNNTlMuOoNNTlQACghNTlRNTlNO +T1RNTlMTDytNT1N/qs1NTlMxPolNTlSZydyfmI8pMHosPIYaFSFNTlMdEkRNUFVNTlMrM38U +EBczT5hNTlMtNX8UDy8+XqIqN4AkGmCTiIEwN4JHaKgRCygnLHdzmsNYdq2Fd3N2nMRvlL8g +G00RCi0TCzAsQYsqHGJMbq5pjLsWDj9HYqV/qdh6nsxpYFljhsL////7+PL38+z59u////z8 ++/f8+vT28ufz7+UoG2D+/fnx7ODu6NvRyb3g2c7k39bSzcXr5NbIvq/Z0si9sqLJw7jDuKjb +0cCJhYHOxbbn3s/n5eGjn5mtqKPY1M+0r6rl2MS9ubVwbW54dXZgXl5HREZnZWHHxcJWVlU/ +PjevpJXazbR/fX0cJGV/d29UaaFPTka5rZx0b2P/++T77c9UVEphW01BQFtXTl6QnavN3exg +hcBIa65bgL0rS5dOcbGKt9NKZ6dPdLZdhbk8ZKlDcbNVd7RWe7l0pcszX6c5WpyKvN5EYaI9 +aK8yWaNJdrRff7OBsdc0RZUvUppukr8xN3knRZItLXkoUqFiisUoPIlsnMVpirxKfcdekL+Z +yusoQY4EAQktOIQOBVVWicRBaqgqMmp3msRTgr40VJ1SiLhAbrFwmc1glc5DWaEqMoBRcKtp +kMh0ntRMfrlHdronKVek1vYVCzd/ptg7aaEYGG83UZM6X6JDc6taea1cZXwkIUJDV5kLAyW3 +6P2PwOlhp+A4QIIcDg8+TpYoIXQiFVY0SYuClLAFABlZKzUdFWgyN4zU//82MTUrIGkpHWVO +T1SUjoosIm0eK3oSCS89RYwpGjNNQHREVIaXdlSjqrsmJCHZ+P6hTAAuJnRz9RRfAAAAWXRS +TlMAAw0H2DaeARv9u/JCJ39bSVawUY/kECNSFBsL6jdqQS2nyiBjFnOGWS6XSv75/cP+/Y1X +gpf+INIxbcWMRrneatP7sn305HhA1FEovMfh4/W3xtjNzNPUx47MeaUAABWdSURBVHhe7JTH +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 + +----------------CHOPCHOP0-- + diff --git a/comm/mail/base/test/browser/files/sampleContent.html b/comm/mail/base/test/browser/files/sampleContent.html new file mode 100644 index 0000000000..05528ac9f1 --- /dev/null +++ b/comm/mail/base/test/browser/files/sampleContent.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <title>Sample Content</title> + <link rel="icon" href="tb-logo.png" /> + </head> + <body> + <p>This is a page of sample content for tests.</p> + <p><a href="https://www.thunderbird.net/">Link to a web page</a></p> + <form> + <input type="text" /> + </form> + <p><img src="tb-logo.png" width="304" height="84" /></p> + </body> +</html> diff --git a/comm/mail/base/test/browser/files/selectionWidget.js b/comm/mail/base/test/browser/files/selectionWidget.js new file mode 100644 index 0000000000..b1e5f98e25 --- /dev/null +++ b/comm/mail/base/test/browser/files/selectionWidget.js @@ -0,0 +1,225 @@ +/* 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/. */ + +var { SelectionWidgetController } = ChromeUtils.import( + "resource:///modules/SelectionWidgetController.jsm" +); + +/** + * Data for a selectable item. + * + * @typedef {object} ItemData + * @property {HTMLElement} element - The DOM node for the item. + * @property {boolean} selected - Whether the item is selected. + */ + +class TestSelectionWidget extends HTMLElement { + /** + * The selectable items for this widget, in DOM ordering. + * + * @type {ItemData[]} + */ + items = []; + #focusItem = this; + #controller = null; + + connectedCallback() { + let widget = this; + + widget.tabIndex = 0; + widget.setAttribute("role", "listbox"); + widget.setAttribute("aria-label", "Test selection widget"); + widget.setAttribute( + "aria-orientation", + widget.getAttribute("layout-direction") + ); + let model = widget.getAttribute("selection-model"); + widget.setAttribute("aria-multiselectable", model == "browse-multi"); + + this.#controller = new SelectionWidgetController(widget, model, { + getLayoutDirection() { + return widget.getAttribute("layout-direction"); + }, + indexFromTarget(target) { + for (let i = 0; i < widget.items.length; i++) { + if (widget.items[i].element.contains(target)) { + return i; + } + } + return null; + }, + getPageSizeDetails() { + if (widget.hasAttribute("no-pages")) { + return null; + } + let itemRect = widget.items[0]?.element.getBoundingClientRect(); + if (widget.getAttribute("layout-direction") == "vertical") { + return { + itemSize: itemRect?.height ?? null, + viewSize: widget.clientHeight, + viewOffset: widget.scrollTop, + }; + } + return { + itemSize: itemRect?.width ?? null, + viewSize: widget.clientWidth, + viewOffset: Math.abs(widget.scrollLeft), + }; + }, + setFocusableItem(index, focus) { + widget.#focusItem.tabIndex = -1; + widget.#focusItem = + index == null ? widget : widget.items[index].element; + widget.#focusItem.tabIndex = 0; + if (focus) { + widget.#focusItem.focus(); + widget.#focusItem.scrollIntoView({ + block: "nearest", + inline: "nearest", + }); + } + }, + setItemSelectionState(index, number, selected) { + for (let i = index; i < index + number; i++) { + widget.items[i].selected = selected; + widget.items[i].element.classList.toggle("selected", selected); + widget.items[i].element.setAttribute("aria-selected", selected); + } + }, + }); + } + + #createItemElement(text) { + for (let { element } of this.items) { + if (element.textContent == text) { + throw new Error(`An item with the text "${text}" already exists`); + } + } + let element = this.ownerDocument.createElement("span"); + element.textContent = text; + element.setAttribute("role", "option"); + element.tabIndex = -1; + element.draggable = this.hasAttribute("items-draggable"); + return element; + } + + /** + * Create new items and add them to the widget. + * + * @param {number} index - The starting index at which to add the items. + * @param {string[]} textList - The textContent for the items to add. Each + * entry in the array will create one item in the same order. + */ + addItems(index, textList) { + for (let [i, text] of textList.entries()) { + let element = this.#createItemElement(text); + this.insertBefore(element, this.items[index + i]?.element ?? null); + this.items.splice(index + i, 0, { element }); + } + this.#controller.addedSelectableItems(index, textList.length); + // Force re-layout. This is needed for the items to be able to enter the + // focus cycle immediately. + this.getBoundingClientRect(); + } + + /** + * Remove items from the widget. + * + * @param {number} index - The starting index at which to remove items. + * @param {number} number - How many items to remove. + */ + removeItems(index, number) { + this.#controller.removeSelectableItems(index, number, () => { + for (let { element } of this.items.splice(index, number)) { + element.remove(); + } + }); + } + + /** + * Move items within the widget. + * + * @param {number} from - The index at which to move items from. + * @param {number} to - The index at which to move items to. + * @param {number} number - How many items to move. + * @param {boolean} reCreate - Whether to recreate the item when + * moving it. Otherwise the existing item is used. + */ + moveItems(from, to, number, reCreate) { + if (reCreate == undefined) { + throw new Error("Missing reCreate argument"); + } + this.#controller.moveSelectableItems(from, to, number, () => { + let moving = this.items.splice(from, number); + for (let [i, item] of moving.entries()) { + item.element.remove(); + if (reCreate) { + let text = item.element.textContent; + item = { element: this.#createItemElement(text) }; + } + this.insertBefore(item.element, this.items[to + i]?.element ?? null); + this.items.splice(to + i, 0, item); + } + }); + } + + /** + * Selects a single item via the SelectionWidgetController.selectSingleItem + * method. + * + * @param {number} index - The index of the item to select. + */ + selectSingleItem(index) { + this.#controller.selectSingleItem(index); + } + + /** + * Changes the selection state of an item via the + * SelectionWidgetController.setItemSelected method. + * + * @param {number} index - The index of the item to set the selection state + * of. + * @param {boolean} select - Whether to select the item. + */ + setItemSelected(index, select) { + this.#controller.setItemSelected(index, select); + } + + /** + * Get the list of selected item's indices. + * + * @returns {number[]} - The indices for selected items. + */ + selectedIndices() { + let indices = []; + for (let i = 0; i < this.items.length; i++) { + // Assert that the item has a defined selection state set in + // setItemSelectionState. + if (typeof this.items[i].selected != "boolean") { + throw new Error(`Item ${i} has an undefined selection state`); + } + // Assert that our stored selection state matches that returned by the + // controller API. + let itemIsSelected = this.#controller.itemIsSelected(i); + if (this.items[i].selected != itemIsSelected) { + throw new Error( + `itemIsSelected(${i}): "${itemIsSelected}" does not match stored selection state "${this.items[i].selected}"` + ); + } + if (itemIsSelected) { + indices.push(i); + } + } + return indices; + } + + /** + * Get the return of SelectionWidgetController.getSelectionRanges + */ + getSelectionRanges() { + return this.#controller.getSelectionRanges(); + } +} + +customElements.define("test-selection-widget", TestSelectionWidget); diff --git a/comm/mail/base/test/browser/files/selectionWidget.xhtml b/comm/mail/base/test/browser/files/selectionWidget.xhtml new file mode 100644 index 0000000000..e5f66fc30c --- /dev/null +++ b/comm/mail/base/test/browser/files/selectionWidget.xhtml @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="utf-8" /> + <title>Test for SelectionWidgetController</title> + <style> + test-selection-widget { + display: flex; + align-items: start; + border: 1px solid black; + width: 600px; + height: 600px; + overflow: auto; + } + + test-selection-widget[layout-direction="vertical"] { + flex-direction: column; + } + + /* Fit 20 items in the view. */ + test-selection-widget[layout-direction="vertical"] > * { + height: 30px; + } + test-selection-widget[layout-direction="horizontal"] > * { + width: 30px; + writing-mode: vertical-rl; + } + + test-selection-widget > * { + padding-inline: 10px; + box-sizing: border-box; + border: 1px solid grey; + white-space: nowrap; + flex: 0 0 auto; + } + + .selected { + background: pink; + } + + :focus { + outline: 3px dashed black; + outline-offset: -3px; + } + + :focus-visible { + outline-color: blue; + } + </style> + <!-- Load the SelectionWidgetController class inline if testing in a browser. + <script src="../../../../modules/SelectionWidgetController.jsm"></script> + --> + <script defer="defer" src="selectionWidget.js"></script> +</head> +<body> +</body> +</html> diff --git a/comm/mail/base/test/browser/files/tb-logo.png b/comm/mail/base/test/browser/files/tb-logo.png Binary files differnew file mode 100644 index 0000000000..aac56e2546 --- /dev/null +++ b/comm/mail/base/test/browser/files/tb-logo.png diff --git a/comm/mail/base/test/browser/files/tree-element-test-common.js b/comm/mail/base/test/browser/files/tree-element-test-common.js new file mode 100644 index 0000000000..6f22962aca --- /dev/null +++ b/comm/mail/base/test/browser/files/tree-element-test-common.js @@ -0,0 +1,73 @@ +/* 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/. */ + +// FIXME: Wrap the whole method around the document load listener to prevent the +// undefined state of the "tree-view-table-row" element. This is due to the .mjs +// nature of the class file. +window.addEventListener("load", () => { + class AlternativeCardRow extends customElements.get("tree-view-table-row") { + static ROW_HEIGHT = 80; + + connectedCallback() { + if (this.hasConnected) { + return; + } + + super.connectedCallback(); + + this.cell = this.appendChild(document.createElement("td")); + } + + get index() { + return super.index; + } + + set index(index) { + super.index = index; + this.cell.textContent = this.view.getCellText(index, { + id: "GeneratedName", + }); + } + } + customElements.define("alternative-row", AlternativeCardRow, { + extends: "tr", + }); + + class TestView { + values = []; + + constructor(start, count) { + for (let i = start; i < start + count; i++) { + this.values.push(i); + } + } + + get rowCount() { + return this.values.length; + } + + getCellText(index, column) { + return `${column.id} ${this.values[index]}`; + } + + isContainer() { + return false; + } + + isContainerOpen() { + return false; + } + + selectionChanged() {} + + setTree() {} + } + + const tree = document.getElementById("testTree"); + tree.table.setBodyID("testBody"); + tree.addEventListener("select", () => { + console.log("select event, selected indices:", tree.selectedIndices); + }); + tree.view = new TestView(0, 150); +}); diff --git a/comm/mail/base/test/browser/files/tree-element-test-header.js b/comm/mail/base/test/browser/files/tree-element-test-header.js new file mode 100644 index 0000000000..37d3b583e4 --- /dev/null +++ b/comm/mail/base/test/browser/files/tree-element-test-header.js @@ -0,0 +1,64 @@ +/* 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/. */ + +// FIXME: Wrap the whole method around the document load listener to prevent the +// undefined state of the "tree-view-table-row" element. This is due to the .mjs +// nature of the class file. +window.addEventListener("load", () => { + class TestCardRow extends customElements.get("tree-view-table-row") { + static ROW_HEIGHT = 50; + + static COLUMNS = [ + { + id: "testCol", + // Ensure that a table header is rendered in order to verify that the + // header's presence doesn't cause issues with scroll calculations. + l10n: { + header: "threadpane-column-header-subject", + menuitem: "threadpane-column-label-subject", + }, + }, + ]; + + connectedCallback() { + if (this.hasConnected) { + return; + } + + super.connectedCallback(); + + this.cell = this.appendChild(document.createElement("td")); + let container = this.cell.appendChild(document.createElement("div")); + + this.d1 = container.appendChild(document.createElement("div")); + this.d1.classList.add("d1"); + + this.d2 = this.d1.appendChild(document.createElement("div")); + this.d2.classList.add("d2"); + + this.d3 = this.d1.appendChild(document.createElement("div")); + this.d3.classList.add("d3"); + } + + get index() { + return super.index; + } + + set index(index) { + super.index = index; + this.d2.textContent = this.view.getCellText(index, { + id: "GeneratedName", + }); + this.d3.textContent = this.view.getCellText(index, { + id: "PrimaryEmail", + }); + this.dataset.value = this.view.values[index]; + } + } + customElements.define("test-row", TestCardRow, { extends: "tr" }); + + const tree = document.getElementById("testTree"); + tree.setAttribute("rows", "test-row"); + tree.table.setColumns(TestCardRow.COLUMNS); +}); diff --git a/comm/mail/base/test/browser/files/tree-element-test-header.xhtml b/comm/mail/base/test/browser/files/tree-element-test-header.xhtml new file mode 100644 index 0000000000..522e3e5c60 --- /dev/null +++ b/comm/mail/base/test/browser/files/tree-element-test-header.xhtml @@ -0,0 +1,61 @@ +<?xml version="1.0"?> +<!-- 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/. --> + +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="utf-8" /> + <title>Test for the tree-view custom element</title> + <link rel="stylesheet" href="chrome://messenger/skin/shared/tree-listbox.css" /> + <!-- Localization is necessary for the table header to display text. --> + <link rel="localization" href="messenger/about3Pane.ftl" /> + <style> + :root { + --color-gray-20: gray; + --selected-item-color: rebeccapurple; + --selected-item-text-color: white; + } + + /* We want a total visible row area of 630px, but we need to account for the + * height of the header as well. */ + #testTree { + height: calc(var(--tree-header-table-height) + 630px); + } + + .tree-view-scrollable-container { + scroll-behavior: unset; + } + + tr[is="test-row"] td > div { + display: flex; + align-items: center; + box-sizing: border-box; + } + + tr[is="test-row"] td div.d1 { + flex: 1; + } + + tr[is="test-row"] td div.d1 > div.d2 { + line-height: 1.2; + } + + tr[is="test-row"] td div.d1 > div.d3 { + line-height: 1.2; + font-size: 13px; + } + </style> + <script type="module" src="chrome://messenger/content/tree-view.mjs"></script> + <script src="tree-element-test-header.js"></script> + <script src="tree-element-test-common.js"></script> +</head> +<!-- We force layout-table in order to ensure that table header rows are + displayed.--> +<body class="layout-table"> + <input id="before" placeholder="something to focus on" /> + <tree-view id="testTree" data-select-delay="250"/> + <input id="after" placeholder="something to focus on" /> +</body> +</html> diff --git a/comm/mail/base/test/browser/files/tree-element-test-levels.js b/comm/mail/base/test/browser/files/tree-element-test-levels.js new file mode 100644 index 0000000000..7ea7eb8232 --- /dev/null +++ b/comm/mail/base/test/browser/files/tree-element-test-levels.js @@ -0,0 +1,118 @@ +/* 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/. */ + +/* globals PROTO_TREE_VIEW */ + +// FIXME: Wrap the whole method around the document load listener to prevent the +// undefined state of the "tree-view-table-row" element. This is due to the .mjs +// nature of the class file. +window.addEventListener("load", () => { + class TestCardRow extends customElements.get("tree-view-table-row") { + static ROW_HEIGHT = 30; + + static COLUMNS = [ + { + id: "testCol", + }, + ]; + + connectedCallback() { + if (this.hasConnected) { + return; + } + + super.connectedCallback(); + + this.cell = this.appendChild(document.createElement("td")); + let container = this.cell.appendChild(document.createElement("div")); + + this.threader = container.appendChild(document.createElement("button")); + this.threader.textContent = "↳"; + this.threader.classList.add("tree-button-thread"); + + this.twisty = container.appendChild(document.createElement("div")); + this.twisty.textContent = "v"; + this.twisty.classList.add("twisty"); + + this.d2 = container.appendChild(document.createElement("div")); + this.d2.classList.add("d2"); + } + + get index() { + return super.index; + } + + set index(index) { + super.index = index; + this.id = this.view.getRowProperties(index); + this.classList.remove("level0", "level1", "level2"); + this.classList.add(`level${this.view.getLevel(index)}`); + this.d2.textContent = this.view.getCellText(index, { id: "text" }); + } + } + customElements.define("test-row", TestCardRow, { extends: "tr" }); + + class TreeItem { + _children = []; + + constructor(id, text, open = false, level = 0) { + this._id = id; + this._text = text; + this._open = open; + this._level = level; + } + + getText() { + return this._text; + } + + get open() { + return this._open; + } + + get level() { + return this._level; + } + + get children() { + return this._children; + } + + getProperties() { + return this._id; + } + + addChild(treeItem) { + treeItem._parent = this; + treeItem._level = this._level + 1; + this.children.push(treeItem); + } + } + + let testView = new PROTO_TREE_VIEW(); + testView._rowMap.push(new TreeItem("row-1", "Item with no children")); + testView._rowMap.push(new TreeItem("row-2", "Item with children")); + testView._rowMap.push(new TreeItem("row-3", "Item with grandchildren")); + testView._rowMap[1].addChild(new TreeItem("row-2-1", "First child")); + testView._rowMap[1].addChild(new TreeItem("row-2-2", "Second child")); + testView._rowMap[2].addChild(new TreeItem("row-3-1", "First child")); + testView._rowMap[2].children[0].addChild( + new TreeItem("row-3-1-1", "First grandchild") + ); + testView._rowMap[2].children[0].addChild( + new TreeItem("row-3-1-2", "Second grandchild") + ); + testView.toggleOpenState(1); + testView.toggleOpenState(4); + testView.toggleOpenState(5); + + let tree = document.getElementById("testTree"); + tree.table.setBodyID("testBody"); + tree.setAttribute("rows", "test-row"); + tree.table.setColumns(TestCardRow.COLUMNS); + tree.addEventListener("select", () => { + console.log("select event, selected indices:", tree.selectedIndices); + }); + tree.view = testView; +}); diff --git a/comm/mail/base/test/browser/files/tree-element-test-levels.xhtml b/comm/mail/base/test/browser/files/tree-element-test-levels.xhtml new file mode 100644 index 0000000000..1175887e74 --- /dev/null +++ b/comm/mail/base/test/browser/files/tree-element-test-levels.xhtml @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="utf-8" /> + <title>Test for the tree-view custom element</title> + <link rel="stylesheet" href="chrome://messenger/skin/shared/tree-listbox.css" /> + <style> + :root { + --color-gray-20: gray; + --selected-item-color: rebeccapurple; + --selected-item-text-color: white; + } + + .tree-view-scrollable-container { + height: 630px; + scroll-behavior: unset; + } + + tr[is="test-row"] td > div { + display: flex; + align-items: center; + } + + button.threader { + width: 1em; + height: 1em; + } + + div.twisty { + width: 1em; + height: 1em; + } + + tr[is="test-row"].children button.threader { + display: inline-block; + } + + tr[is="test-row"] button.threader { + display: hidden; + } + + tr[is="test-row"].children div.twisty { + background-color: green; + } + + tr[is="test-row"].children.collapsed div.twisty { + background-color: red; + } + + tr[is="test-row"].level1 .d2 { + padding-inline-start: 1em; + } + + tr[is="test-row"].level2 .d2 { + padding-inline-start: 2em; + } + </style> + <script type="module" defer="defer" src="chrome://messenger/content/tree-view.mjs"></script> + <script defer="defer" src="chrome://messenger/content/jsTreeView.js"></script> + <script defer="defer" src="tree-element-test-levels.js"></script> +</head> +<body> + <tree-view id="testTree" data-select-delay="250"/> +</body> +</html> diff --git a/comm/mail/base/test/browser/files/tree-element-test-no-header.js b/comm/mail/base/test/browser/files/tree-element-test-no-header.js new file mode 100644 index 0000000000..8a515be5e2 --- /dev/null +++ b/comm/mail/base/test/browser/files/tree-element-test-no-header.js @@ -0,0 +1,58 @@ +/* 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/. */ + +// FIXME: Wrap the whole method around the document load listener to prevent the +// undefined state of the "tree-view-table-row" element. This is due to the .mjs +// nature of the class file. +window.addEventListener("load", () => { + class TestCardRow extends customElements.get("tree-view-table-row") { + static ROW_HEIGHT = 50; + + static COLUMNS = [ + { + id: "testCol", + }, + ]; + + connectedCallback() { + if (this.hasConnected) { + return; + } + + super.connectedCallback(); + + this.cell = this.appendChild(document.createElement("td")); + let container = this.cell.appendChild(document.createElement("div")); + + this.d1 = container.appendChild(document.createElement("div")); + this.d1.classList.add("d1"); + + this.d2 = this.d1.appendChild(document.createElement("div")); + this.d2.classList.add("d2"); + + this.d3 = this.d1.appendChild(document.createElement("div")); + this.d3.classList.add("d3"); + } + + get index() { + return super.index; + } + + set index(index) { + super.index = index; + this.d2.textContent = this.view.getCellText(index, { + id: "GeneratedName", + }); + this.d3.textContent = this.view.getCellText(index, { + id: "PrimaryEmail", + }); + this.dataset.value = this.view.values[index]; + } + } + customElements.define("test-row", TestCardRow, { extends: "tr" }); + + const tree = document.getElementById("testTree"); + tree.setAttribute("rows", "test-row"); + tree.table.setColumns(TestCardRow.COLUMNS); +}); diff --git a/comm/mail/base/test/browser/files/tree-element-test-no-header.xhtml b/comm/mail/base/test/browser/files/tree-element-test-no-header.xhtml new file mode 100644 index 0000000000..7605279ba6 --- /dev/null +++ b/comm/mail/base/test/browser/files/tree-element-test-no-header.xhtml @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<!-- 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/. --> + +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="utf-8" /> + <title>Test for the tree-view custom element</title> + <link rel="stylesheet" href="chrome://messenger/skin/shared/tree-listbox.css" /> + <style> + :root { + --color-gray-20: gray; + --selected-item-color: rebeccapurple; + --selected-item-text-color: white; + } + + /* We want a total visible row area of 630px. Intentionally avoid leaving + * room for a header. */ + .tree-view-scrollable-container { + height: 630px; + scroll-behavior: unset; + } + + tr[is="test-row"] td > div { + display: flex; + align-items: center; + box-sizing: border-box; + } + + tr[is="test-row"] td div.d1 { + flex: 1; + } + + tr[is="test-row"] td div.d1 > div.d2 { + line-height: 1.2; + } + + tr[is="test-row"] td div.d1 > div.d3 { + line-height: 1.2; + font-size: 13px; + } + </style> + <script type="module" src="chrome://messenger/content/tree-view.mjs"></script> + <script src="tree-element-test-no-header.js"></script> + <script src="tree-element-test-common.js"></script> +</head> +<body> + <input id="before" placeholder="something to focus on" /> + <tree-view id="testTree" data-select-delay="250"/> + <input id="after" placeholder="something to focus on" /> +</body> +</html> diff --git a/comm/mail/base/test/browser/files/treeListbox.xhtml b/comm/mail/base/test/browser/files/treeListbox.xhtml new file mode 100644 index 0000000000..a760ca141d --- /dev/null +++ b/comm/mail/base/test/browser/files/treeListbox.xhtml @@ -0,0 +1,390 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="utf-8" /> + <title>Test for the tree-listbox custom element</title> + <style> + :focus { + outline: 3px blue solid; + } + html { + height: 100%; + } + body { + height: 100%; + display: flex; + margin: 0; + } + ul[is="tree-listbox"] { + overflow-y: auto; + white-space: nowrap; + } + ul { + margin: 0; + padding: 0; + list-style: none; + } + li > div { + display: flex; + align-items: center; + } + li.selected > div { + color: white; + background-color: blue; + } + li > ul { + padding-inline-start: 1em; + } + li.collapsed > ul { + display: none; + } + div.twisty { + width: 1em; + height: 1em; + } + li.children > div > div.twisty { + background-color: green; + } + li.children.collapsed > div > div.twisty { + background-color: red; + } + li.unselectable > div { + background-color: red; + } + </style> + <script defer="defer" src="chrome://messenger/content/tree-listbox.js"></script> +</head> +<body> + <ul is="tree-listbox" role="tree"> + <li id="row-1"> + <div> + <div class="twisty"></div> + Item with no children + </div> + </li> + <li id="row-2"> + <div> + <div class="twisty"></div> + Item with children + </div> + <ul> + <li id="row-2-1"> + <div> + <div class="twisty"></div> + First child + </div> + </li> + <li id="row-2-2"> + <div> + <div class="twisty"></div> + Second child + </div> + </li> + </ul> + </li> + <li id="row-3"> + <div> + <div class="twisty"></div> + Item with grandchildren + </div> + <ul> + <li id="row-3-1"> + <div> + <div class="twisty"></div> + First child + </div> + <ul> + <li id="row-3-1-1"> + <div> + <div class="twisty"></div> + First grandchild + </div> + </li> + <li id="row-3-1-2"> + <div> + <div class="twisty"></div> + Second grandchild + </div> + </li> + </ul> + </li> + </ul> + </li> + </ul> + <template id="rowToAdd"> + <li id="new-row"> + <div> + <div class="twisty"></div> + New row + </div> + </li> + </template> + <template id="rowsToAdd"> + <li id="added-row"> + <div> + <div class="twisty"></div> + Added row + </div> + <ul> + <li id="added-row-1"> + <div> + <div class="twisty"></div> + Added child + </div> + <ul> + <li id="added-row-1-1"> + <div> + <div class="twisty"></div> + Added grandchild + </div> + </li> + </ul> + </li> + <li id="added-row-2"> + <div> + <div class="twisty"></div> + Added child + </div> + </li> + </ul> + </li> + </template> + <!-- Larger tree for deleting from --> + <ul> + <li>Before</li> + <li> + <!-- Place under a plain <li> an <ul> to make sure our selector logic + - doesn't break down. --> + <ul is="tree-listbox" id="deleteTree" role="tree"> + <li id="dRow-1" class="collapsed"> + <div> + <div class="twisty"></div> + Item with collapsed children + </div> + <ul> + <li id="dRow-1-1"> + <div> + <div class="twisty"></div> + Hidden child + </div> + </li> + </ul> + </li> + <li id="dRow-2"> + <div> + <div class="twisty"></div> + Item with children + </div> + <ul> + <li id="dRow-2-1"> + <div> + <div class="twisty"></div> + First child + </div> + </li> + <li id="dRow-2-2"> + <div> + <div class="twisty"></div> + Second child + </div> + </li> + </ul> + </li> + <li id="dRow-3"> + <div> + <div class="twisty"></div> + Item with grandchildren + </div> + <ul> + <li id="dRow-3-1"> + <div> + <div class="twisty"></div> + First child + </div> + <ul> + <li id="dRow-3-1-1" class="collapsed"> + <div> + <div class="twisty"></div> + First grandchild + </div> + <ul> + <li id="dRow-3-1-1-1"> + <div> + <div class="twisty"></div> + Hidden child + </div> + </li> + </ul> + </li> + <li id="dRow-3-1-2"> + <div> + <div class="twisty"></div> + Second grandchild + </div> + </li> + <li id="dRow-3-1-3" class="collapsed"> + <div> + <div class="twisty"></div> + Third grandchild + </div> + <ul> + <li id="dRow-3-1-3-1"> + <div> + <div class="twisty"></div> + Hidden child + </div> + </li> + </ul> + </li> + </ul> + </li> + </ul> + </li> + <li id="dRow-4"> + <div> + <div class="twisty"></div> + Fourth item + </div> + <ul> + <li id="dRow-4-1" class="collapsed"> + <div> + <div class="twisty"></div> + First child + </div> + <ul> + <li id="dRow-4-1-1"> + <div> + <div class="twisty"></div> + Hidden child 1 + </div> + </li> + <li id="dRow-4-1-2"> + <div> + <div class="twisty"></div> + Hidden child 2 + </div> + </li> + </ul> + </li> + <li id="dRow-4-2"> + <div> + <div class="twisty"></div> + Second child + </div> + </li> + <li id="dRow-4-3"> + <div> + <div class="twisty"></div> + Third child + </div> + <ul> + <li id="dRow-4-3-1"> + <div> + <div class="twisty"></div> + First Grand child + </div> + </li> + <li id="dRow-4-3-2"> + <div> + <div class="twisty"></div> + Second Grand child + </div> + </li> + </ul> + </li> + <li id="dRow-4-4" class="collapsed"> + <div> + <div class="twisty"></div> + Fourth child + </div> + <ul> + <li id="dRow-4-4-1"> + <div> + <div class="twisty"></div> + Hidden child 1 + </div> + </li> + <li id="dRow-4-4-2"> + <div> + <div class="twisty"></div> + Hidden child 2 + </div> + </li> + </ul> + </li> + </ul> + </li> + <li id="dRow-5"> + <div> + <div class="twisty"></div> + Second last item + </div> + <ul> + <li id="dRow-5-1"> + <div> + <div class="twisty"></div> + Last child + </div> + </li> + </ul> + </li> + <li id="dRow-6"> + <div> + <div class="twisty"></div> + Last item + </div> + </li> + </ul> + </li> + <li>After</li> + </ul> + <!-- Tree with unselectable rows --> + <ul is="tree-listbox" id="unselectableTree" role="tree"> + <li id="uRow-1" class="unselectable"> + <div>Item with no children</div> + </li> + <li id="uRow-2" class="unselectable"> + <div>Item with children</div> + <ul> + <li id="uRow-2-1"> + <div> + <div class="twisty"></div> + First child + </div> + </li> + <li id="uRow-2-2"> + <div> + <div class="twisty"></div> + Second child + </div> + </li> + </ul> + </li> + <li id="uRow-3" class="unselectable"> + <div>Item with grandchildren</div> + <ul> + <li id="uRow-3-1"> + <div> + <div class="twisty"></div> + First child + </div> + <ul> + <li id="uRow-3-1-1"> + <div> + <div class="twisty"></div> + First grandchild + </div> + </li> + <li id="uRow-3-1-2"> + <div> + <div class="twisty"></div> + Second grandchild + </div> + </li> + </ul> + </li> + </ul> + </li> + </ul> +</body> +</html> |