☠☠ backed out by 5786d2b246ee ☠ ☠ | |
author | Jared Wein <jwein@mozilla.com> |
Fri, 01 Dec 2017 14:38:27 -0500 | |
changeset 450226 | 90da1b24e686f5350c9ab09720e1745eb2bdb286 |
parent 450225 | 79a6260e52a7f225e6893e7640980d04ebfcf87e |
child 450227 | 7b813845b721a97bb5e2743912635c149f26039c |
push id | 8527 |
push user | Callek@gmail.com |
push date | Thu, 11 Jan 2018 21:05:50 +0000 |
treeherder | mozilla-beta@95342d212a7a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | MattN |
bugs | 1422164 |
milestone | 59.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/toolkit/components/payments/jar.mn +++ b/toolkit/components/payments/jar.mn @@ -5,15 +5,16 @@ toolkit.jar: % content payments %content/payments/ content/payments/paymentDialog.css (content/paymentDialog.css) content/payments/paymentDialog.js (content/paymentDialog.js) content/payments/paymentDialogFrameScript.js (content/paymentDialogFrameScript.js) content/payments/paymentDialog.xhtml (content/paymentDialog.xhtml) % resource payments %res/payments/ res/payments (res/paymentRequest.*) + res/payments/components/ (res/components/*.css) res/payments/components/ (res/components/*.js) res/payments/containers/ (res/containers/*.js) res/payments/debugging.html (res/debugging.html) res/payments/debugging.js (res/debugging.js) res/payments/mixins/ (res/mixins/*.js) res/payments/PaymentsStore.js (res/PaymentsStore.js) res/payments/vendor/ (res/vendor/*)
new file mode 100644 --- /dev/null +++ b/toolkit/components/payments/res/components/address-option.css @@ -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/. */ + +address-option { + display: grid; + grid-row-gap: 5px; + grid-column-gap: 10px; + grid-template-areas: + "recipient " + "addressLine"; + + border-bottom: 1px solid #ddd; + background: #fff; + padding: 5px; + padding-inline-start: 20px; + width: 400px; + font-size: .8em; +} + +rich-select[open] > .rich-select-popup-box > address-option { + grid-template-areas: + "recipient recipient" + "addressLine addressLine" + "email phone "; +} + +address-option > .recipient { + grid-area: recipient; +} + +address-option > .addressLine { + grid-area: addressLine; +} + +address-option > .email { + grid-area: email; +} + +address-option > .phone { + grid-area: phone; +} + +address-option > .recipient, +address-option > .addressLine, +address-option > .email, +address-option > .phone { + white-space: nowrap; +} + +.rich-select-popup-box > address-option[selected] { + background-color: #ffa; +} + +rich-select > .rich-select-selected-clone > .email, +rich-select > .rich-select-selected-clone > .phone { + display: none; +}
new file mode 100644 --- /dev/null +++ b/toolkit/components/payments/res/components/address-option.js @@ -0,0 +1,79 @@ +/* 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/. */ + +/** + * <rich-select> + * <address-option addressLine="1234 Anywhere St" + * city="Some City" + * country="USA" + * dependentLocality="" + * languageCode="en-US" + * phone="" + * postalCode="90210" + * recipient="Jared Wein" + * region="MI"></address-option> + * </rich-select> + */ + +/* global ObservedPropertiesMixin, RichOption */ + +class AddressOption extends ObservedPropertiesMixin(RichOption) { + static get observedAttributes() { + return RichOption.observedAttributes.concat([ + "addressLine", + "city", + "country", + "dependentLocality", + "email", + "languageCode", + "organization", + "phone", + "postalCode", + "recipient", + "region", + "sortingCode", + ]); + } + + connectedCallback() { + for (let child of this.children) { + child.remove(); + } + + let fragment = document.createDocumentFragment(); + let recipient = RichOption._createElement(fragment, "recipient"); + let addressLine = RichOption._createElement(fragment, "addressLine"); + let email = RichOption._createElement(fragment, "email"); + let phone = RichOption._createElement(fragment, "phone"); + this.appendChild(fragment); + + this.elementMap = { + recipient, + addressLine, + email, + phone, + }; + + super.connectedCallback(); + } + + disconnectedCallback() { + this.elementMap = {}; + } + + render() { + if (!this.parentNode) { + return; + } + + this.elementMap.recipient.textContent = this.recipient; + this.elementMap.addressLine.textContent = + `${this.addressLine} ${this.city} ${this.region} ${this.postalCode} ${this.country}`; + this.elementMap.email.textContent = this.email; + this.elementMap.phone.textContent = this.phone; + } +} + +customElements.define("address-option", AddressOption); +
new file mode 100644 --- /dev/null +++ b/toolkit/components/payments/res/components/basic-card-option.css @@ -0,0 +1,56 @@ +/* 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/. */ + +basic-card-option { + display: grid; + grid-row-gap: 5px; + grid-column-gap: 10px; + grid-template-areas: + "owner type" + "number ..."; + + border-bottom: 1px solid #ddd; + background: #fff; + padding: 5px; + padding-inline-start: 20px; + width: 400px; + font-size: .8em; +} + +rich-select[open] > .rich-select-popup-box > basic-card-option { + grid-template-areas: + "owner type" + "number expiration"; +} + +basic-card-option > .number { + grid-area: number; +} + +basic-card-option > .owner { + grid-area: owner; +} + +basic-card-option > .expiration { + grid-area: expiration; +} + +basic-card-option > .type { + grid-area: type; +} + +basic-card-option > .number, +basic-card-option > .owner, +basic-card-option > .expiration, +basic-card-option > .type { + white-space: nowrap; +} + +.rich-select-popup-box > basic-card-option[selected] { + background-color: #ffa; +} + +rich-select > .rich-select-selected-clone > .expiration { + display: none; +}
new file mode 100644 --- /dev/null +++ b/toolkit/components/payments/res/components/basic-card-option.js @@ -0,0 +1,61 @@ +/* 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/. */ + +/** + * <rich-select> + * <basic-card-option></basic-card-option> + * </rich-select> + */ + +/* global ObservedPropertiesMixin, RichOption */ + +class BasicCardOption extends ObservedPropertiesMixin(RichOption) { + static get observedAttributes() { + return RichOption.observedAttributes.concat([ + "expiration", + "number", + "owner", + "type", + ]); + } + + connectedCallback() { + for (let child of this.children) { + child.remove(); + } + + let fragment = document.createDocumentFragment(); + let owner = RichOption._createElement(fragment, "owner"); + let number = RichOption._createElement(fragment, "number"); + let expiration = RichOption._createElement(fragment, "expiration"); + let type = RichOption._createElement(fragment, "type"); + this.appendChild(fragment); + + this.elementMap = { + owner, + number, + expiration, + type, + }; + + super.connectedCallback(); + } + + disconnectedCallback() { + this.elementMap = {}; + } + + render() { + if (!this.parentNode) { + return; + } + + this.elementMap.owner.textContent = this.owner; + this.elementMap.number.textContent = this.number; + this.elementMap.expiration.textContent = this.expiration; + this.elementMap.type.textContent = this.type; + } +} + +customElements.define("basic-card-option", BasicCardOption);
new file mode 100644 --- /dev/null +++ b/toolkit/components/payments/res/components/rich-option.js @@ -0,0 +1,91 @@ +/* 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/. */ + +/** + * <rich-select> + * <rich-option></rich-option> + * </rich-select> + */ + +/* global ObservedPropertiesMixin */ +/* exported RichOption */ + +class RichOption extends ObservedPropertiesMixin(HTMLElement) { + static get observedAttributes() { return ["selected", "hidden"]; } + + constructor() { + super(); + + this.addEventListener("click", this); + this.addEventListener("keypress", this); + } + + connectedCallback() { + this.render(); + let richSelect = this.closest("rich-select"); + if (richSelect && richSelect.render) { + richSelect.render(); + } + } + + handleEvent(event) { + switch (event.type) { + case "click": { + this.onClick(event); + break; + } + case "keypress": { + this.onKeyPress(event); + break; + } + } + } + + onClick(event) { + if (this.closest("rich-select").open && + !this.disabled && + event.button == 0) { + for (let option of this.parentNode.children) { + option.selected = option == this; + } + } + } + + onKeyPress(event) { + if (!this.disabled && + event.which == 13 /* Enter */) { + for (let option of this.parentNode.children) { + option.selected = option == this; + } + } + } + + get selected() { + return this.hasAttribute("selected"); + } + + set selected(value) { + if (value) { + let oldSelectedOptions = this.parentNode.querySelectorAll("[selected]"); + for (let option of oldSelectedOptions) { + option.removeAttribute("selected"); + } + this.setAttribute("selected", value); + } else { + this.removeAttribute("selected"); + } + let richSelect = this.closest("rich-select"); + if (richSelect && richSelect.render) { + richSelect.render(); + } + return value; + } + + static _createElement(fragment, className) { + let element = document.createElement("span"); + element.classList.add(className); + fragment.appendChild(element); + return element; + } +}
new file mode 100644 --- /dev/null +++ b/toolkit/components/payments/res/components/rich-select.css @@ -0,0 +1,17 @@ +/* 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/. */ + +rich-select:not([open]) > .rich-select-popup-box { + display: none; +} + +rich-select[open] { + position: relative; +} + +rich-select[open] > .rich-select-popup-box { + position: absolute; + z-index: 1; + top: 1em; +}
new file mode 100644 --- /dev/null +++ b/toolkit/components/payments/res/components/rich-select.js @@ -0,0 +1,160 @@ +/* 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/. */ + +/** + * <rich-select> + * <rich-option></rich-option> + * </rich-select> + */ + +/* global ObservedPropertiesMixin */ + +class RichSelect extends ObservedPropertiesMixin(HTMLElement) { + static get observedAttributes() { + return [ + "open", + "disabled", + "hidden", + ]; + } + + constructor() { + super(); + + this.addEventListener("blur", this); + this.addEventListener("click", this); + this.addEventListener("keypress", this); + } + + connectedCallback() { + this.setAttribute("tabindex", "0"); + this.render(); + } + + get popupBox() { + return this.querySelector(":scope > .rich-select-popup-box"); + } + + get selectedOption() { + return this.popupBox.querySelector(":scope > [selected]"); + } + + handleEvent(event) { + switch (event.type) { + case "blur": { + this.onBlur(event); + break; + } + case "click": { + this.onClick(event); + break; + } + case "keypress": { + this.onKeyPress(event); + break; + } + } + } + + onBlur(event) { + if (event.target == this) { + this.open = false; + } + } + + onClick(event) { + if (!this.disabled && + event.button == 0) { + this.open = !this.open; + } + } + + onKeyPress(event) { + if (event.key == " ") { + this.open = !this.open; + } else if (event.key == "ArrowDown") { + let selectedOption = this.selectedOption; + let next = selectedOption.nextElementSibling; + if (next) { + next.selected = true; + selectedOption.selected = false; + } + } else if (event.key == "ArrowUp") { + let selectedOption = this.selectedOption; + let next = selectedOption.previousElementSibling; + if (next) { + next.selected = true; + selectedOption.selected = false; + } + } else if (event.key == "Enter" || + event.key == "Escape") { + this.open = false; + } + } + + _optionsAreEquivalent(a, b) { + if (!a || !b) { + return false; + } + + let aAttrs = a.constructor.observedAttributes; + let bAttrs = b.constructor.observedAttributes; + if (aAttrs.length != bAttrs.length) { + return false; + } + + for (let aAttr of aAttrs) { + if (a.getAttribute(aAttr) != b.getAttribute(aAttr)) { + return false; + } + } + + return true; + } + + render() { + let popupBox = this.popupBox; + if (!popupBox) { + popupBox = document.createElement("div"); + popupBox.classList.add("rich-select-popup-box"); + this.appendChild(popupBox); + } + + /* eslint-disable max-len */ + let options = + this.querySelectorAll(":scope > :not(.rich-select-popup-box):not(.rich-select-selected-clone)"); + /* eslint-enable max-len */ + for (let option of options) { + popupBox.appendChild(option); + } + + let selectedChild; + for (let child of popupBox.children) { + if (child.selected) { + selectedChild = child; + } + } + if (!selectedChild && popupBox.children.length) { + selectedChild = popupBox.children[0]; + selectedChild.selected = true; + } + + if (!this._optionsAreEquivalent(this._selectedChild, selectedChild)) { + let selectedClone = this.querySelector(":scope > .rich-select-selected-clone"); + if (selectedClone) { + selectedClone.remove(); + } + + if (selectedChild) { + this._selectedChild = selectedChild; + selectedClone = selectedChild.cloneNode(false); + selectedClone.removeAttribute("id"); + selectedClone.classList.add("rich-select-selected-clone"); + selectedClone = this.appendChild(selectedClone); + } + } + } +} + +customElements.define("rich-select", RichSelect);
--- a/toolkit/components/payments/res/paymentRequest.xhtml +++ b/toolkit/components/payments/res/paymentRequest.xhtml @@ -3,25 +3,30 @@ - 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 http-equiv="Content-Security-Policy" content="default-src 'self'"/> <title></title> <link rel="stylesheet" href="paymentRequest.css"/> + <link rel="stylesheet" href="components/rich-select.css"/> + <link rel="stylesheet" href="components/address-option.css"/> <script src="vendor/custom-elements.min.js"></script> <script src="PaymentsStore.js"></script> <script src="mixins/ObservedPropertiesMixin.js"></script> <script src="mixins/PaymentStateSubscriberMixin.js"></script> <script src="components/currency-amount.js"></script> + <script src="components/rich-select.js"></script> + <script src="components/rich-option.js"></script> + <script src="components/address-option.js"></script> <script src="containers/payment-dialog.js"></script> <script src="paymentRequest.js"></script> <template id="payment-dialog-template"> <div id="host-name"></div> <div id="total"> @@ -32,11 +37,55 @@ <button id="cancel">Cancel</button> <button id="pay">Pay</button> </div> </template> </head> <body> <iframe id="debugging-console" hidden="hidden" src="debugging.html"></iframe> + <rich-select> + <address-option email="emzembrano92@example.com" + recipient="Emily Zembrano" + addressLine="717 Hyde Street #6" + city="San Francisco" + region="CA" + phone="415 203 0845" + postalCode="94109" + country="USA"></address-option> + <address-option email="jenz9382@example.com" + recipient="Jennifer Zembrano" + addressLine="42 Fairydust Lane" + city="Lala Land" + region="HI" + phone="415 439 2827" + postalCode="98765" + country="USA"></address-option> + <address-option email="johnz9382@example.com" + recipient="John Zembrano" + addressLine="42 Fairydust Lane" + city="Lala Land" + missinginformation="true" + region="HI" + phone="415 439 2827" + postalCode="98765" + country="USA"></address-option> + <address-option email="adbrwodne@example.com" + recipient="Andrew Browne" + addressLine="42 Fairydust Lane" + city="Lala Land" + region="HI" + phone="517 410 0845" + postalCode="98765" + country="USA"></address-option> + <address-option email="johnz9382@example.com" + recipient="Jacob Humphrey" + addressLine="1855 Pinecrest Rd" + city="East Lansing" + region="MI" + phone="517 439 2827" + postalCode="48823" + country="USA"></address-option> + </rich-select> + <payment-dialog></payment-dialog> </body> </html>
--- a/toolkit/components/payments/test/mochitest/mochitest.ini +++ b/toolkit/components/payments/test/mochitest/mochitest.ini @@ -1,14 +1,22 @@ [DEFAULT] support-files = ../../../../../testing/modules/sinon-2.3.2.js ../../res/PaymentsStore.js ../../res/components/currency-amount.js + ../../res/components/address-option.js + ../../res/components/address-option.css + ../../res/components/basic-card-option.js + ../../res/components/basic-card-option.css + ../../res/components/rich-option.js + ../../res/components/rich-select.css + ../../res/components/rich-select.js ../../res/mixins/ObservedPropertiesMixin.js ../../res/mixins/PaymentStateSubscriberMixin.js ../../res/vendor/custom-elements.min.js ../../res/vendor/custom-elements.min.js.map payments_common.js [test_currency_amount.html] +[test_rich_select.html] [test_ObservedPropertiesMixin.html] [test_PaymentStateSubscriberMixin.html]
new file mode 100644 --- /dev/null +++ b/toolkit/components/payments/test/mochitest/test_rich_select.html @@ -0,0 +1,317 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test the rich-select component +--> +<head> + <meta charset="utf-8"> + <title>Test the rich-select component</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script src="payments_common.js"></script> + <script src="custom-elements.min.js"></script> + <script src="ObservedPropertiesMixin.js"></script> + <script src="rich-select.js"></script> + <script src="rich-option.js"></script> + <script src="address-option.js"></script> + <script src="basic-card-option.js"></script> + <link rel="stylesheet" type="text/css" href="rich-select.css"/> + <link rel="stylesheet" type="text/css" href="address-option.css"/> + <link rel="stylesheet" type="text/css" href="basic-card-option.css"/> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <p id="display"> + <rich-select id="select1"> + <address-option id="option1" + email="emzembrano92@email.com" + recipient="Emily Zembrano" + addressLine="717 Hyde Street #6" + city="San Francisco" + region="CA" + phone="415 203 0845" + postalCode="94109" + country="USA"></address-option> + <address-option id="option2" + email="jenz9382@email.com" + recipient="Jennifer Zembrano" + addressLine="42 Fairydust Lane" + city="Lala Land" + region="HI" + phone="415 439 2827" + postalCode="98765" + country="USA"></address-option> + <address-option id="option3" + email="johnz9382@email.com" + recipient="John Zembrano" + addressLine="42 Fairydust Lane" + city="Lala Land" + missinginformation="true" + region="HI" + phone="415 439 2827" + postalCode="98765" + country="USA"></address-option> + </rich-select> + + <rich-select id="select2"> + <basic-card-option owner="Jared Wein" + expiration="01/1970" + number="4024007197293599" + type="Visa"></basic-card-option> + <basic-card-option owner="Whimsy Corn" + expiration="01/1970" + number="5220465104517667" + type="Mastercard"></basic-card-option> + <basic-card-option owner="Fire Fox" + expiration="01/1970" + number="6011777095481054" + type="Discover"></basic-card-option> + </rich-select> + </p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<script type="application/javascript"> +/** Test the rich-select address-option component **/ + +/* import-globals-from payments_common.js */ +/* import-globals-from ../../res/components/address-option.js */ +/* import-globals-from ../../res/components/basic-card-option.js */ + +let select1 = document.getElementById("select1"); +let option1 = document.getElementById("option1"); +let option2 = document.getElementById("option2"); +let option3 = document.getElementById("option3"); + +function get_selected_clone() { + return select1.querySelector(".rich-select-selected-clone"); +} + +function is_visible(element, message) { + ok(!isHidden(element), message); +} + +function is_hidden(element, message) { + ok(isHidden(element), message); +} + +function dispatchKeyPress(key, keyCode) { + select1.dispatchEvent(new KeyboardEvent("keypress", {key, keyCode})); +} + +add_task(async function test_addressLine_combines_address_city_region_postalCode_country() { + ok(option1, "option1 exists"); + let addressLine = option1.querySelector(".addressLine"); + /* eslint-disable max-len */ + is(addressLine.textContent, + `${option1.addressLine} ${option1.city} ${option1.region} ${option1.postalCode} ${option1.country}`); + /* eslint-enable max-len */ +}); + +add_task(async function test_no_option_selected_first_displayed() { + ok(select1, "select1 exists"); + + await asyncElementRendered(); + + is_hidden(option1, "option 1 should be hidden when popup is not open"); + is_hidden(option2, "option 2 should be hidden when popup is not open"); + is_hidden(option3, "option 3 should be hidden when popup is not open"); + ok(option1.selected, "option 1 should be selected"); + ok(option1.hasAttribute("selected"), "option 1 should have selected attribute"); + let selectedClone = get_selected_clone(); + is_visible(selectedClone, "The selected clone should be visible at all times"); + is(selectedClone.getAttribute("email"), option1.getAttribute("email"), + "The selected clone email should be equivalent to the selected option 1"); + is(selectedClone.getAttribute("recipient"), option1.getAttribute("recipient"), + "The selected clone recipient should be equivalent to the selected option 1"); +}); + +add_task(async function test_clicking_on_select_shows_all_options() { + ok(select1, "select1 exists"); + ok(!select1.open, "select is not open by default"); + ok(option1.selected, "option 1 should be selected by default"); + + select1.click(); + + ok(select1.open, "select is open after clicking on it"); + ok(option1.selected, "option 1 should be selected when open"); + is_visible(option1, "option 1 is visible when select is open"); + is_visible(option2, "option 2 is visible when select is open"); + is_visible(option3, "option 3 is visible when select is open"); + + option2.click(); + + ok(!select1.open, "select is not open after blur"); + ok(!option1.selected, "option 1 is not selected after click on option 2"); + ok(option2.selected, "option 2 is selected after clicking on it"); + is_hidden(option1, "option 1 is hidden when select is closed"); + is_hidden(option2, "option 2 is hidden when select is closed"); + is_hidden(option3, "option 3 is hidden when select is closed"); + + await asyncElementRendered(); + + let selectedClone = get_selected_clone(); + is_visible(selectedClone, "The selected clone should be visible at all times"); + is(selectedClone.getAttribute("email"), option2.getAttribute("email"), + "The selected clone email should be equivalent to the selected option 2"); + is(selectedClone.getAttribute("recipient"), option2.getAttribute("recipient"), + "The selected clone recipient should be equivalent to the selected option 2"); +}); + +add_task(async function test_changing_option_selected_affects_other_options() { + ok(option2.selected, "Option 2 should be selected from prior test"); + + option1.selected = true; + ok(!option2.selected, "Option 2 should no longer be selected after making option 1 selected"); + ok(option1.hasAttribute("selected"), "Option 1 should now have selected attribute"); +}); + +add_task(async function test_up_down_keys_change_selected_item() { + let openObserver = new MutationObserver(mutations => { + for (let mutation of mutations) { + ok(mutation.attributeName != "open", "the select should not open/close during this test"); + } + }); + openObserver.observe(select1, {attributes: true}); + + ok(select1, "select1 exists"); + ok(option1.selected, "option 1 should be selected by default"); + + ok(!select1.open, "select should not be open before focusing"); + select1.focus(); + ok(!select1.open, "select should not be open after focusing"); + + dispatchKeyPress("ArrowDown", 40); + ok(!option1.selected, "option 1 should no longer be selected"); + ok(option2.selected, "option 2 should now be selected"); + + dispatchKeyPress("ArrowDown", 40); + ok(!option2.selected, "option 2 should no longer be selected"); + ok(option3.selected, "option 3 should now be selected"); + + dispatchKeyPress("ArrowDown", 40); + ok(option3.selected, "option 3 should remain selected"); + ok(!option1.selected, "option 1 should not be selected"); + + dispatchKeyPress("ArrowUp", 38); + ok(!option3.selected, "option 3 should no longer be selected"); + ok(option2.selected, "option 2 should now be selected"); + + dispatchKeyPress("ArrowUp", 38); + ok(!option2.selected, "option 2 should no longer be selected"); + ok(option1.selected, "option 1 should now be selected"); + + dispatchKeyPress("ArrowUp", 38); + ok(option1.selected, "option 1 should remain selected"); + ok(!option3.selected, "option 3 should not be selected"); + + // Wait for any mutation observer notifications to fire before exiting. + await Promise.resolve(); + + openObserver.disconnect(); +}); + +add_task(async function test_open_close_from_keyboard() { + select1.focus(); + + ok(!select1.open, "select should not be open by default"); + + dispatchKeyPress(" ", 32); + ok(select1.open, "select should now be open"); + ok(option1.selected, "option 1 should be selected by default"); + + dispatchKeyPress("ArrowDown", 40); + ok(!option1.selected, "option 1 should not be selected"); + ok(option2.selected, "option 2 should now be selected"); + ok(select1.open, "select should remain open"); + + dispatchKeyPress("ArrowUp", 38); + ok(option1.selected, "option 1 should now be selected"); + ok(!option2.selected, "option 2 should not be selected"); + ok(select1.open, "select should remain open"); + + dispatchKeyPress("Enter", 13); + ok(option1.selected, "option 1 should now be selected"); + ok(!select1.open, "select should be closed"); + + dispatchKeyPress(" ", 32); + ok(select1.open, "select should now be open"); + + dispatchKeyPress("Escape", 27); + ok(!select1.open, "select should be closed"); +}); + +add_task(async function test_clicking_on_options_maintain_one_item_always_selected() { + ok(!select1.open, "select should be closed by default"); + ok(option1.selected, "option 1 should be selected by default"); + select1.click(); + ok(select1.open, "select should now be open"); + + option3.click(); + ok(!select1.open, "select should be closed"); + ok(!option1.selected, "option 1 should be unselected"); + ok(option3.selected, "option 3 should be selected"); + + select1.click(); + ok(select1.open, "select should open"); + ok(!option1.selected, "option 1 should be unselected"); + ok(option3.selected, "option 3 should be selected"); + + option1.click(); + ok(!select1.open, "select should be closed"); + ok(option1.selected, "option 1 should be selected"); + ok(!option3.selected, "option 3 should be unselected"); +}); + +add_task(async function test_selected_clone_should_equal_selected_option() { + ok(option1.selected, "option 1 should be selected"); + await asyncElementRendered(); + + let clonedOptions = select1.querySelectorAll(".rich-select-selected-clone"); + is(clonedOptions.length, 1, "there should only be one cloned option"); + + let clonedOption = clonedOptions[0]; + for (let attrName of AddressOption.observedAttributes) { + is(clonedOption.attributes[attrName] && clonedOption.attributes[attrName].value, + option1.attributes[attrName] && option1.attributes[attrName].value, + "attributes should have matching value; name=" + attrName); + } + + option2.selected = true; + await asyncElementRendered(); + + clonedOptions = select1.querySelectorAll(".rich-select-selected-clone"); + is(clonedOptions.length, 1, "there should only be one cloned option"); + + clonedOption = clonedOptions[0]; + for (let attrName of AddressOption.observedAttributes) { + is(clonedOption.attributes[attrName] && clonedOption.attributes[attrName].value, + option2.attributes[attrName] && option2.attributes[attrName].value, + "attributes should have matching value; name=" + attrName); + } +}); + +add_task(async function test_basic_card_simple() { + let select2 = document.getElementById("select2"); + ok(select2, "basic card select should exist"); + let selectPopupBox = select2.querySelector(".rich-select-popup-box"); + ok(selectPopupBox, "basic card popup box exists"); + + is(selectPopupBox.childElementCount, 3, "There should be three children in the popup box"); + + let clonedOption = select2.querySelector(".rich-select-selected-clone"); + let selectedOption = selectPopupBox.firstChild; + for (let attrName of BasicCardOption.observedAttributes) { + is(clonedOption.attributes[attrName] && clonedOption.attributes[attrName].value, + selectedOption.attributes[attrName] && selectedOption.attributes[attrName].value, + "attributes should have matching value; name=" + attrName); + } +}); + +</script> + +</body> +</html>