Bug 1463545 - Update the <basic-card-option> layout to match the UI spec. r=sfoster
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Thu, 20 Sep 2018 21:07:18 +0000
changeset 437549 b6cc6d47b5be
parent 437548 913329feffe4
child 437550 1f44117fee2e
push id34685
push userrgurzau@mozilla.com
push dateFri, 21 Sep 2018 04:12:55 +0000
treeherdermozilla-central@8d8dc3f35c3d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfoster
bugs1463545
milestone64.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
Bug 1463545 - Update the <basic-card-option> layout to match the UI spec. r=sfoster Depends on D6330 Differential Revision: https://phabricator.services.mozilla.com/D6331
browser/components/payments/res/components/basic-card-option.css
browser/components/payments/res/components/basic-card-option.js
browser/components/payments/res/debugging.js
browser/components/payments/test/mochitest/mochitest.ini
browser/components/payments/test/mochitest/test_basic_card_option.html
--- a/browser/components/payments/res/components/basic-card-option.css
+++ b/browser/components/payments/res/components/basic-card-option.css
@@ -1,33 +1,40 @@
 /* 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 {
-  grid-column-gap: 1ch;
-  grid-template-areas: "type cc-number cc-exp cc-name";
+  grid-column-gap: 1em;
+  grid-template-areas: "cc-type cc-number cc-exp cc-name";
+  /* Need to set a minimum width for the cc-type svg in the <img> to fill */
+  grid-template-columns: minmax(1em, auto);
   justify-content: start;
 }
 
+basic-card-option > .cc-number,
+basic-card-option > .cc-name,
+basic-card-option > .cc-exp,
+basic-card-option > .cc-type {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
 basic-card-option > .cc-number {
   grid-area: cc-number;
+  /* Don't truncate the card number */
+  overflow: visible;
 }
 
 basic-card-option > .cc-name {
   grid-area: cc-name;
 }
 
 basic-card-option > .cc-exp {
   grid-area: cc-exp;
 }
 
-basic-card-option > .type {
-  grid-area: type;
-  display: none;
+basic-card-option > .cc-type {
+  grid-area: cc-type;
+  height: 100%;
+  text-transform: capitalize;
 }
-
-basic-card-option > .cc-number,
-basic-card-option > .cc-name,
-basic-card-option > .cc-exp,
-basic-card-option > .type {
-  white-space: nowrap;
-}
--- a/browser/components/payments/res/components/basic-card-option.js
+++ b/browser/components/payments/res/components/basic-card-option.js
@@ -25,39 +25,60 @@ export default class BasicCardOption ext
   static get observedAttributes() {
     return RichOption.observedAttributes.concat(BasicCardOption.recordAttributes);
   }
 
   constructor() {
     super();
 
     for (let name of ["cc-name", "cc-number", "cc-exp", "cc-type"]) {
-      this[`_${name}`] = document.createElement("span");
+      this[`_${name}`] = document.createElement(name == "cc-type" ? "img" : "span");
       this[`_${name}`].classList.add(name);
     }
   }
 
   connectedCallback() {
     for (let name of ["cc-name", "cc-number", "cc-exp", "cc-type"]) {
       this.appendChild(this[`_${name}`]);
     }
     super.connectedCallback();
   }
 
+  static formatCCNumber(ccNumber) {
+    // XXX: Bug 1470175 - This should probably be unified with CreditCard.jsm logic.
+    return ccNumber ? ccNumber.replace(/[*]{4,}/, "****") : "";
+  }
+
   static formatSingleLineLabel(basicCard) {
-    // Fall back to empty strings to prevent 'undefined' from appearing.
-    let ccNumber = basicCard["cc-number"] || "";
-    let ccExp = basicCard["cc-exp"] || "";
-    let ccName = basicCard["cc-name"] || "";
+    let ccNumber = BasicCardOption.formatCCNumber(basicCard["cc-number"]);
+
+    // XXX Bug 1473772 - Hard-coded string
+    let ccExp = basicCard["cc-exp"] ? "Exp. " + basicCard["cc-exp"] : "";
+    let ccName = basicCard["cc-name"];
     // XXX: Bug 1491040, displaying cc-type in this context may need its own localized string
     let ccType = basicCard["cc-type"] || "";
-    return `${ccType} ${ccNumber} ${ccExp} ${ccName}`;
+    // Filter out empty/undefined tokens before joining by three spaces
+    // (&nbsp; in the middle of two normal spaces to avoid them visually collapsing in HTML)
+    return [
+      ccType.replace(/^[a-z]/, $0 => $0.toUpperCase()),
+      ccNumber,
+      ccExp,
+      ccName,
+      // XXX Bug 1473772 - Hard-coded string:
+    ].filter(str => !!str).join(" \xa0 ");
+  }
+
+  get requiredFields() {
+    return BasicCardOption.recordAttributes;
   }
 
   render() {
-    this["_cc-name"].textContent = this.ccName;
-    this["_cc-number"].textContent = this.ccNumber;
-    this["_cc-exp"].textContent = this.ccExp;
-    this["_cc-type"].textContent = this.ccType;
+    this["_cc-name"].textContent = this.ccName || "";
+    this["_cc-number"].textContent = BasicCardOption.formatCCNumber(this.ccNumber);
+    // XXX Bug 1473772 - Hard-coded string:
+    this["_cc-exp"].textContent = this.ccExp ? "Exp. " + this.ccExp : "";
+    // XXX: Bug 1491040, displaying cc-type in this context may need its own localized string
+    this["_cc-type"].alt = this.ccType || "";
+    this["_cc-type"].src = "chrome://formautofill/content/icon-credit-card-generic.svg";
   }
 }
 
 customElements.define("basic-card-option", BasicCardOption);
--- a/browser/components/payments/res/debugging.js
+++ b/browser/components/payments/res/debugging.js
@@ -87,17 +87,29 @@ let REQUEST_1 = {
   shippingOption: "456",
 };
 
 let REQUEST_2 = {
   tabId: 9,
   topLevelPrincipal: {URI: {displayHost: "example.com"}},
   requestId: "3797081f-a96b-c34b-a58b-1083c6e66e25",
   completeStatus: "",
-  paymentMethods: [],
+  paymentMethods: [
+    {
+      "supportedMethods": "basic-card",
+      "data": {
+        "supportedNetworks": [
+          "amex",
+          "discover",
+          "mastercard",
+          "visa",
+        ],
+      },
+    },
+  ],
   paymentDetails: {
     id: "",
     totalItem: {label: "", amount: {currency: "CAD", value: "25.75"}, pending: false},
     displayItems: [
       {
         label: "Triangle",
         amount: {
           currency: "CAD",
@@ -338,17 +350,16 @@ let BASIC_CARDS_1 = {
     "version": 1,
     "timeCreated": 1517890536491,
     "timeLastModified": 1517890564518,
     "timeLastUsed": 0,
     "timesUsed": 0,
     "cc-exp-month": 8,
     "cc-exp-year": 2024,
     "cc-exp": "2024-08",
-    "cc-type": "amex",
   },
 };
 
 let buttonActions = {
   debugFrame() {
     let event = new CustomEvent("paymentContentToChrome", {
       bubbles: true,
       detail: {
--- a/browser/components/payments/test/mochitest/mochitest.ini
+++ b/browser/components/payments/test/mochitest/mochitest.ini
@@ -6,26 +6,27 @@ support-files =
    !/browser/extensions/formautofill/content/editCreditCard.xhtml
    ../../../../../browser/extensions/formautofill/content/autofillEditForms.js
    ../../../../../browser/extensions/formautofill/skin/shared/editDialog-shared.css
    ../../../../../testing/modules/sinon-2.3.2.js
    ../../res/**
    payments_common.js
 skip-if = !e10s
 
+[test_accepted_cards.html]
 [test_address_form.html]
 [test_address_picker.html]
 [test_basic_card_form.html]
+[test_basic_card_option.html]
 [test_completion_error_page.html]
 [test_currency_amount.html]
 [test_labelled_checkbox.html]
 [test_order_details.html]
 [test_payer_address_picker.html]
 [test_payment_dialog.html]
 [test_payment_dialog_required_top_level_items.html]
 [test_payment_details_item.html]
 [test_payment_method_picker.html]
 [test_rich_select.html]
 [test_shipping_option_picker.html]
 [test_ObservedPropertiesMixin.html]
 [test_PaymentsStore.html]
 [test_PaymentStateSubscriberMixin.html]
-[test_accepted_cards.html]
new file mode 100644
--- /dev/null
+++ b/browser/components/payments/test/mochitest/test_basic_card_option.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test the basic-card-option component
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test the basic-card-option component</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/AddTask.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script src="payments_common.js"></script>
+  <script src="../../res/vendor/custom-elements.min.js"></script>
+  <script src="../../res/unprivileged-fallbacks.js"></script>
+
+  <link rel="stylesheet" type="text/css" href="../../res/components/rich-select.css"/>
+  <link rel="stylesheet" type="text/css" href="../../res/components/basic-card-option.css"/>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+  <p id="display">
+    <option id="option1"
+            value="option1"
+            cc-exp="2024-06"
+            cc-name="John Smith"
+            cc-number="************5461"
+            cc-type="visa"
+            guid="option1"></option>
+    <option id="option2"
+            value="option2"
+            cc-number="************1111"
+            guid="option2"></option>
+
+    <rich-select id="richSelect1"
+                 option-type="basic-card-option"></rich-select>
+  </p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script type="module">
+/** Test the basic-card-option component **/
+
+import "../../res/components/basic-card-option.js";
+import "../../res/components/rich-select.js";
+
+let option1 = document.getElementById("option1");
+let option2 = document.getElementById("option2");
+let richSelect1 = document.getElementById("richSelect1");
+
+add_task(async function test_populated_option_rendering() {
+  richSelect1.popupBox.appendChild(option1);
+  richSelect1.value = option1.value;
+  await asyncElementRendered();
+
+  let richOption = richSelect1.selectedRichOption;
+  is(richOption.ccExp, "2024-06", "Check ccExp getter");
+  is(richOption.ccName, "John Smith", "Check ccName getter");
+  is(richOption.ccNumber, "************5461", "Check ccNumber getter");
+  is(richOption.ccType, "visa", "Check ccType getter");
+
+  ok(!richOption.innerText.includes("undefined"), "Check for presence of 'undefined'");
+  ok(!richOption.innerText.includes("null"), "Check for presence of 'null'");
+
+  // Note that innerText takes visibility into account so that's why it's used over textContent here
+  is(richOption["_cc-exp"].innerText, "Exp. 2024-06", "cc-exp text");
+  is(richOption["_cc-name"].innerText, "John Smith", "cc-name text");
+  is(richOption["_cc-number"].innerText, "****5461", "cc-number text");
+  is(richOption["_cc-type"].localName, "img", "cc-type localName");
+  is(richOption["_cc-type"].alt, "visa", "cc-type img alt");
+});
+
+add_task(async function test_minimal_option_rendering() {
+  richSelect1.popupBox.appendChild(option2);
+  richSelect1.value = option2.value;
+  await asyncElementRendered();
+
+  let richOption = richSelect1.selectedRichOption;
+  is(richOption.ccExp, null, "Check ccExp getter");
+  is(richOption.ccName, null, "Check ccName getter");
+  is(richOption.ccNumber, "************1111", "Check ccNumber getter");
+  is(richOption.ccType, null, "Check ccType getter");
+
+  ok(!richOption.innerText.includes("undefined"), "Check for presence of 'undefined'");
+  ok(!richOption.innerText.includes("null"), "Check for presence of 'null'");
+
+  is(richOption["_cc-exp"].innerText, "", "cc-exp text");
+  is(richOption["_cc-name"].innerText, "", "cc-name text");
+  is(richOption["_cc-number"].innerText, "****1111", "cc-number text");
+  is(richOption["_cc-type"].localName, "img", "cc-type localName");
+  is(richOption["_cc-type"].alt, "", "cc-type img alt");
+});
+
+</script>
+
+</body>
+</html>