browser/components/payments/test/mochitest/test_payment_dialog.html
author Matthew Noorenberghe <mozilla@noorenberghe.ca>
Thu, 20 Sep 2018 21:07:20 +0000
changeset 437550 1f44117fee2ea257eca27f6015b3437d2ad7cd70
parent 437547 7106b3f8d717899c5a29903a089ecd46f14b2253
child 438863 e2d81abcb65594b6033aa84487403d3776e7e84d
permissions -rw-r--r--
Bug 1463545 - Replace grid layout of <address-option> with a new two line design. r=sfoster Differential Revision: https://phabricator.services.mozilla.com/D5186

<!DOCTYPE HTML>
<html>
<!--
Test the payment-dialog custom element
-->
<head>
  <meta charset="utf-8">
  <title>Test the payment-dialog element</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="sinon-2.3.2.js"></script>
  <script src="payments_common.js"></script>
  <script src="../../res/vendor/custom-elements.min.js"></script>
  <script src="../../res/unprivileged-fallbacks.js"></script>
  <script src="autofillEditForms.js"></script>

  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
  <link rel="stylesheet" type="text/css" href="../../res/paymentRequest.css"/>
  <link rel="stylesheet" type="text/css" href="../../res/containers/rich-picker.css"/>
</head>
<body>
  <p id="display" style="height: 100vh; margin: 0;">
    <iframe id="templateFrame" src="../../res/paymentRequest.xhtml" width="0" height="0"
            style="float: left;"></iframe>
  </p>
<div id="content" style="display: none">

</div>
<pre id="test">
</pre>
<script type="module">
/** Test the payment-dialog element **/

/* global sinon */

import PaymentDialog from "../../res/containers/payment-dialog.js";

let el1;

add_task(async function setup_once() {
  let templateFrame = document.getElementById("templateFrame");
  await SimpleTest.promiseFocus(templateFrame.contentWindow);
  let displayEl = document.getElementById("display");
  importDialogDependencies(templateFrame, displayEl);

  el1 = new PaymentDialog();
  displayEl.appendChild(el1);

  sinon.spy(el1, "render");
  sinon.spy(el1, "stateChangeCallback");
});

async function setup() {
  let {request} = el1.requestStore.getState();
  await el1.requestStore.setState({
    changesPrevented: false,
    request: Object.assign({}, request, {completeStatus: ""}),
    orderDetailsShowing: false,
    page: {
      id: "payment-summary",
    },
  });

  el1.render.reset();
  el1.stateChangeCallback.reset();
}

add_task(async function test_initialState() {
  await setup();
  let initialState = el1.requestStore.getState();
  let elDetails = el1._orderDetailsOverlay;

  is(initialState.orderDetailsShowing, false, "orderDetailsShowing is initially false");
  ok(elDetails.hasAttribute("hidden"), "Check details are hidden");
  is(initialState.page.id, "payment-summary", "Check initial page");
});

add_task(async function test_viewAllButtonVisibility() {
  await setup();

  let button = el1._viewAllButton;
  ok(button.hidden, "Button is initially hidden when there are no items to show");
  ok(isHidden(button), "Button should be visibly hidden since bug 1469464");

  // Add a display item.
  let request = deepClone(el1.requestStore.getState().request);
  request.paymentDetails.displayItems = [
    {
      "label": "Triangle",
      "amount": {
        "currency": "CAD",
        "value": "3",
      },
    },
  ];
  await el1.requestStore.setState({ request });
  await asyncElementRendered();

  // Check if the "View all items" button is visible.
  ok(!button.hidden, "Button is visible");
});

add_task(async function test_viewAllButton() {
  await setup();

  let elDetails = el1._orderDetailsOverlay;
  let button = el1._viewAllButton;

  button.click();
  await asyncElementRendered();

  ok(el1.stateChangeCallback.calledOnce, "stateChangeCallback called once");
  ok(el1.render.calledOnce, "render called once");

  let state = el1.requestStore.getState();
  is(state.orderDetailsShowing, true, "orderDetailsShowing becomes true");
  ok(!elDetails.hasAttribute("hidden"), "Check details aren't hidden");
});

add_task(async function test_changesPrevented() {
  await setup();
  let state = el1.requestStore.getState();
  is(state.changesPrevented, false, "changesPrevented is initially false");
  let disabledOverlay = document.getElementById("disabled-overlay");
  ok(disabledOverlay.hidden, "Overlay should initially be hidden");
  await el1.requestStore.setState({changesPrevented: true});
  await asyncElementRendered();
  ok(!disabledOverlay.hidden, "Overlay should prevent changes");
});

add_task(async function test_initial_completeStatus() {
  await setup();
  let {request, page} = el1.requestStore.getState();
  is(request.completeStatus, "", "completeStatus is initially empty");

  let payButton = document.getElementById("pay");
  is(payButton, document.querySelector(`#${page.id} button.primary`),
     "Primary button is the pay button in the initial state");
  is(payButton.textContent, "Pay", "Check default label");
  ok(payButton.disabled, "Button is disabled by default");
});

add_task(async function test_generic_errors() {
  await setup();
  const SHIPPING_GENERIC_ERROR = "Can't ship to that address";
  el1._errorText.dataset.shippingGenericError = SHIPPING_GENERIC_ERROR;
  el1.requestStore.setState({
    savedAddresses: {
      "48bnds6854t": {
        "address-level1": "MI",
        "address-level2": "Some City",
        "country": "US",
        "guid": "48bnds6854t",
        "name": "Mr. Foo",
        "postal-code": "90210",
        "street-address": "123 Sesame Street,\nApt 40",
        "tel": "+1 519 555-5555",
      },
      "68gjdh354j": {
        "address-level1": "CA",
        "address-level2": "Mountain View",
        "country": "US",
        "guid": "68gjdh354j",
        "name": "Mrs. Bar",
        "postal-code": "94041",
        "street-address": "P.O. Box 123",
        "tel": "+1 650 555-5555",
      },
    },
    selectedShippingAddress: "48bnds6854t",
  });
  await asyncElementRendered();

  let picker = el1._shippingAddressPicker;
  ok(picker.selectedOption, "Address picker should have a selected option");
  is(el1._errorText.textContent, SHIPPING_GENERIC_ERROR,
     "Generic error message should be shown when no shipping options or error are provided");
});

add_task(async function test_processing_completeStatus() {
  // "processing": has overlay. Check button visibility
  await setup();
  let {request} = el1.requestStore.getState();
  // this a transition state, set when waiting for a response from the merchant page
  el1.requestStore.setState({
    changesPrevented: true,
    request: Object.assign({}, request, {completeStatus: "processing"}),
  });
  await asyncElementRendered();

  let primaryButtons = document.querySelectorAll("footer button.primary");
  ok(Array.from(primaryButtons).every(el => isHidden(el) || el.disabled),
     "all primary footer buttons are hidden or disabled");
});

add_task(async function test_success_unknown_completeStatus() {
  // in the "success" and "unknown" completion states the dialog would normally be closed
  // so just ensure it is left in a good state
  for (let completeStatus of ["success", "unknown"]) {
    await setup();
    let {request} = el1.requestStore.getState();
    el1.requestStore.setState({
      request: Object.assign({}, request, {completeStatus}),
    });
    await asyncElementRendered();

    let {page} = el1.requestStore.getState();

    // this status doesnt change page
    let payButton = document.getElementById("pay");
    is(payButton, document.querySelector(`#${page.id} button.primary`),
       `Primary button is the pay button in the ${completeStatus} state`);

    if (completeStatus == "success") {
      is(payButton.textContent, "Done", "Check button label");
    }
    if (completeStatus == "unknown") {
      is(payButton.textContent, "Unknown", "Check button label");
    }
    ok(payButton.disabled, "Button is disabled by default");
  }
});

add_task(async function test_timeout_fail_completeStatus() {
  // in these states the dialog stays open and presents a single
  // button for acknowledgement
  for (let completeStatus of ["fail", "timeout"]) {
    await setup();
    let {request} = el1.requestStore.getState();
    el1.requestStore.setState({
      request: Object.assign({}, request, {completeStatus}),
      page: {
        id: `completion-${completeStatus}-error`,
      },
    });
    await asyncElementRendered();

    let {page} = el1.requestStore.getState();
    let pageElem = document.querySelector(`#${page.id}`);
    let payButton = document.getElementById("pay");
    let primaryButton = pageElem.querySelector("button.primary");

    ok(pageElem && !isHidden(pageElem, `page element for ${page.id} exists and is visible`));
    ok(!isHidden(primaryButton), "Primary button is visible");
    ok(payButton != primaryButton,
       `Primary button is the not pay button in the ${completeStatus} state`);
    ok(isHidden(payButton), "Pay button is not visible");
    is(primaryButton.textContent, "Close", "Check button label");

    let rect = primaryButton.getBoundingClientRect();
    let visibleElement =
      document.elementFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
    ok(primaryButton === visibleElement, "Primary button is on top of the overlay");
  }
});

add_task(async function test_scrollPaymentRequestPage() {
  await setup();
  info("making the payment-dialog container small to require scrolling");
  el1.parentElement.style.height = "100px";
  let summaryPageBody = document.querySelector("#payment-summary .page-body");
  is(summaryPageBody.scrollTop, 0, "Page body not scrolled initially");
  let securityCodeInput = summaryPageBody.querySelector("payment-method-picker input");
  securityCodeInput.focus();
  await new Promise(resolve => SimpleTest.executeSoon(resolve));
  ok(summaryPageBody.scrollTop > 0, "Page body scrolled after focusing the CVV field");
  el1.parentElement.style.height = "";
});

add_task(async function test_acceptedCards() {
  let initialState = el1.requestStore.getState();
  let paymentMethods = [{
    supportedMethods: "basic-card",
    data: {
      supportedNetworks: ["visa", "mastercard"],
    },
  }];
  el1.requestStore.setState({
    request: Object.assign({}, initialState.request, {
      paymentMethods,
    }),
  });
  await asyncElementRendered();

  let acceptedCards = el1._acceptedCardsList;
  ok(acceptedCards && !isHidden(acceptedCards), "Accepted cards list is present and visible");

  paymentMethods = [{
    supportedMethods: "basic-card",
  }];
  el1.requestStore.setState({
    request: Object.assign({}, initialState.request, {
      paymentMethods,
    }),
  });
  await asyncElementRendered();

  acceptedCards = el1._acceptedCardsList;
  ok(acceptedCards && isHidden(acceptedCards), "Accepted cards list is present but hidden");
});

add_task(async function test_picker_labels() {
  await setup();
  let picker = el1._shippingOptionPicker;

  const SHIPPING_OPTIONS_LABEL = "Shipping options";
  const DELIVERY_OPTIONS_LABEL = "Delivery options";
  const PICKUP_OPTIONS_LABEL = "Pickup options";
  picker.dataset.shippingOptionsLabel = SHIPPING_OPTIONS_LABEL;
  picker.dataset.deliveryOptionsLabel = DELIVERY_OPTIONS_LABEL;
  picker.dataset.pickupOptionsLabel = PICKUP_OPTIONS_LABEL;

  for (let [shippingType, label] of [
    ["shipping", SHIPPING_OPTIONS_LABEL],
    ["delivery", DELIVERY_OPTIONS_LABEL],
    ["pickup", PICKUP_OPTIONS_LABEL],
  ]) {
    let request = deepClone(el1.requestStore.getState().request);
    request.paymentOptions.requestShipping = true;
    request.paymentOptions.shippingType = shippingType;
    await el1.requestStore.setState({ request });
    await asyncElementRendered();
    is(picker.labelElement.textContent, label,
       `Label should be appropriate for ${shippingType}`);
  }
});

add_task(async function test_disconnect() {
  await setup();

  el1.remove();
  await el1.requestStore.setState({orderDetailsShowing: true});
  await asyncElementRendered();
  ok(el1.stateChangeCallback.notCalled, "stateChangeCallback not called");
  ok(el1.render.notCalled, "render not called");

  let elDetails = el1._orderDetailsOverlay;
  ok(elDetails.hasAttribute("hidden"), "details overlay remains hidden");
});
</script>

</body>
</html>