diff options
Diffstat (limited to 'dom/payments/test')
42 files changed, 7191 insertions, 0 deletions
diff --git a/dom/payments/test/BasicCardErrorsChromeScript.js b/dom/payments/test/BasicCardErrorsChromeScript.js new file mode 100644 index 0000000000..f92e5eef5c --- /dev/null +++ b/dom/payments/test/BasicCardErrorsChromeScript.js @@ -0,0 +1,133 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +const defaultCard = { + cardholderName: "", + cardNumber: "4111111111111111", + expiryMonth: "", + expiryYear: "", + cardSecurityCode: "", + billingAddress: null, +}; + +function makeBillingAddress() { + const billingAddress = Cc[ + "@mozilla.org/dom/payments/payment-address;1" + ].createInstance(Ci.nsIPaymentAddress); + const addressLine = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray + ); + const address = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + address.data = "Easton Ave"; + addressLine.appendElement(address); + const addressArgs = [ + "USA", // country + addressLine, // address line + "CA", // region + "CA", // regionCode + "San Bruno", // city + "", // dependent locality + "94066", // postal code + "123456", // sorting code + "", // organization + "Bill A. Pacheco", // recipient + "+14344413879", // phone + ]; + billingAddress.init(...addressArgs); + return billingAddress; +} + +function makeBasicCardResponse(details) { + const basicCardResponseData = Cc[ + "@mozilla.org/dom/payments/basiccard-response-data;1" + ].createInstance(Ci.nsIBasicCardResponseData); + const { + cardholderName, + cardNumber, + expiryMonth, + expiryYear, + cardSecurityCode, + billingAddress, + } = details; + + const address = + billingAddress !== undefined ? billingAddress : makeBillingAddress(); + + basicCardResponseData.initData( + cardholderName, + cardNumber, + expiryMonth, + expiryYear, + cardSecurityCode, + address + ); + + return basicCardResponseData; +} + +const TestingUIService = { + showPayment(requestId, details = { ...defaultCard }) { + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "basic-card", // payment method + makeBasicCardResponse(details), + "Person name", + "Person email", + "Person phone" + ); + + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + // Handles response.retry({ paymentMethod }): + updatePayment(requestId) { + // Let's echo what was sent in by the error... + const request = paymentSrv.getPaymentRequestById(requestId); + this.showPayment(requestId, request.paymentDetails.paymentMethodErrors); + }, + // Handles response.complete() + completePayment(requestId) { + const request = paymentSrv.getPaymentRequestById(requestId); + const completeResponse = Cc[ + "@mozilla.org/dom/payments/payment-complete-action-response;1" + ].createInstance(Ci.nsIPaymentCompleteActionResponse); + completeResponse.init( + requestId, + Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED + ); + paymentSrv.respondPayment( + completeResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + get QueryInterface() { + return ChromeUtils.generateQI(["nsIPaymentUIService"]); + }, +}; + +paymentSrv.setTestingUIService( + TestingUIService.QueryInterface(Ci.nsIPaymentUIService) +); + +addMessageListener("teardown", () => { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/BasiccardChromeScript.js b/dom/payments/test/BasiccardChromeScript.js new file mode 100644 index 0000000000..6ce2ca024b --- /dev/null +++ b/dom/payments/test/BasiccardChromeScript.js @@ -0,0 +1,372 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +function emitTestFail(message) { + sendAsyncMessage("test-fail", `${DummyUIService.testName}: ${message}`); +} + +const billingAddress = Cc[ + "@mozilla.org/dom/payments/payment-address;1" +].createInstance(Ci.nsIPaymentAddress); +const addressLine = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray +); +const address = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString +); +address.data = "Easton Ave"; +addressLine.appendElement(address); +billingAddress.init( + "USA", // country + addressLine, // address line + "CA", // region + "CA", // region code + "San Bruno", // city + "", // dependent locality + "94066", // postal code + "123456", // sorting code + "", // organization + "Bill A. Pacheco", // recipient + "+14344413879" +); // phone + +const specialAddress = Cc[ + "@mozilla.org/dom/payments/payment-address;1" +].createInstance(Ci.nsIPaymentAddress); +const specialAddressLine = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray +); +const specialData = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString +); +specialData.data = ":$%@&*"; +specialAddressLine.appendElement(specialData); +specialAddress.init( + "USA", // country + specialAddressLine, // address line + "CA", // region + "CA", // region code + "San Bruno", // city + "", // dependent locality + "94066", // postal code + "123456", // sorting code + "", // organization + "Bill A. Pacheco", // recipient + "+14344413879" +); // phone + +const basiccardResponseData = Cc[ + "@mozilla.org/dom/payments/basiccard-response-data;1" +].createInstance(Ci.nsIBasicCardResponseData); + +const basiccardChangeDetails = Cc[ + "@mozilla.org/dom/payments/basiccard-change-details;1" +].createInstance(Ci.nsIBasicCardChangeDetails); + +const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" +].createInstance(Ci.nsIPaymentShowActionResponse); + +function abortPaymentResponse(requestId) { + let abortResponse = Cc[ + "@mozilla.org/dom/payments/payment-abort-action-response;1" + ].createInstance(Ci.nsIPaymentAbortActionResponse); + abortResponse.init(requestId, Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED); + paymentSrv.respondPayment( + abortResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function completePaymentResponse(requestId) { + let completeResponse = Cc[ + "@mozilla.org/dom/payments/payment-complete-action-response;1" + ].createInstance(Ci.nsIPaymentCompleteActionResponse); + completeResponse.init( + requestId, + Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED + ); + paymentSrv.respondPayment( + completeResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function showRequest(requestId) { + if (DummyUIService.showAction === "payment-method-change") { + basiccardChangeDetails.initData(billingAddress); + try { + paymentSrv.changePaymentMethod( + requestId, + "basic-card", + basiccardChangeDetails.QueryInterface(Ci.nsIMethodChangeDetails) + ); + } catch (error) { + emitTestFail( + `Unexpected error (${error.name}) when calling PaymentRequestService::changePaymentMethod` + ); + } + return; + } + if (DummyUIService.showAction === "detailBasicCardResponse") { + try { + basiccardResponseData.initData( + "Bill A. Pacheco", // cardholderName + "4916855166538720", // cardNumber + "01", // expiryMonth + "2024", // expiryYear + "180", // cardSecurityCode + billingAddress + ); // billingAddress + } catch (e) { + emitTestFail("Fail to initialize basic card response data."); + } + } + if (DummyUIService.showAction === "simpleBasicCardResponse") { + try { + basiccardResponseData.initData( + "", // cardholderName + "4916855166538720", // cardNumber + "", // expiryMonth + "", // expiryYear + "", // cardSecurityCode + null + ); // billingAddress + } catch (e) { + emitTestFail("Fail to initialize basic card response data."); + } + } + if (DummyUIService.showAction === "specialAddressResponse") { + try { + basiccardResponseData.initData( + "Bill A. Pacheco", // cardholderName + "4916855166538720", // cardNumber + "01", // expiryMonth + "2024", // expiryYear + "180", // cardSecurityCode + specialAddress + ); // billingAddress + } catch (e) { + emitTestFail("Fail to initialize basic card response data."); + } + } + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "basic-card", // payment method + basiccardResponseData, // payment method data + "Bill A. Pacheco", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +const DummyUIService = { + testName: "", + showAction: "", + showPayment: showRequest, + abortPayment: abortPaymentResponse, + completePayment: completePaymentResponse, + updatePayment: requestId => { + try { + basiccardResponseData.initData( + "Bill A. Pacheco", // cardholderName + "4916855166538720", // cardNumber + "01", // expiryMonth + "2024", // expiryYear + "180", // cardSecurityCode + billingAddress + ); // billingAddress + } catch (e) { + emitTestFail("Fail to initialize basic card response data."); + } + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "basic-card", // payment method + basiccardResponseData, // payment method data + "Bill A. Pacheco", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + closePayment: requestId => {}, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +paymentSrv.setTestingUIService( + DummyUIService.QueryInterface(Ci.nsIPaymentUIService) +); + +addMessageListener("set-detailed-ui-service", function (testName) { + DummyUIService.testName = testName; + DummyUIService.showAction = "detailBasicCardResponse"; + sendAsyncMessage("set-detailed-ui-service-complete"); +}); + +addMessageListener("set-simple-ui-service", function (testName) { + DummyUIService.testName = testName; + DummyUIService.showAction = "simpleBasicCardResponse"; + sendAsyncMessage("set-simple-ui-service-complete"); +}); + +addMessageListener("set-special-address-ui-service", function (testName) { + DummyUIService.testName = testName; + DummyUIService.showAction = "specialAddressResponse"; + sendAsyncMessage("set-special-address-ui-service-complete"); +}); + +addMessageListener("method-change-to-basic-card", function (testName) { + DummyUIService.testName = testName; + DummyUIService.showAction = "payment-method-change"; + sendAsyncMessage("method-change-to-basic-card-complete"); +}); + +addMessageListener("error-response-test", function (testName) { + // test empty cardNumber + try { + basiccardResponseData.initData("", "", "", "", "", null); + emitTestFail( + "BasicCardResponse should not be initialized with empty cardNumber." + ); + } catch (e) { + if (e.name != "NS_ERROR_FAILURE") { + emitTestFail( + "Empty cardNumber expected 'NS_ERROR_FAILURE', but got " + e.name + "." + ); + } + } + + // test invalid expiryMonth 123 + try { + basiccardResponseData.initData("", "4916855166538720", "123", "", "", null); + emitTestFail( + "BasicCardResponse should not be initialized with invalid expiryMonth '123'." + ); + } catch (e) { + if (e.name != "NS_ERROR_FAILURE") { + emitTestFail( + "expiryMonth 123 expected 'NS_ERROR_FAILURE', but got " + e.name + "." + ); + } + } + // test invalid expiryMonth 99 + try { + basiccardResponseData.initData("", "4916855166538720", "99", "", "", null); + emitTestFail( + "BasicCardResponse should not be initialized with invalid expiryMonth '99'." + ); + } catch (e) { + if (e.name != "NS_ERROR_FAILURE") { + emitTestFail( + "expiryMonth 99 xpected 'NS_ERROR_FAILURE', but got " + e.name + "." + ); + } + } + // test invalid expiryMonth ab + try { + basiccardResponseData.initData("", "4916855166538720", "ab", "", "", null); + emitTestFail( + "BasicCardResponse should not be initialized with invalid expiryMonth 'ab'." + ); + } catch (e) { + if (e.name != "NS_ERROR_FAILURE") { + emitTestFail( + "expiryMonth ab expected 'NS_ERROR_FAILURE', but got " + e.name + "." + ); + } + } + // test invalid expiryYear abcd + try { + basiccardResponseData.initData( + "", + "4916855166538720", + "", + "abcd", + "", + null + ); + emitTestFail( + "BasicCardResponse should not be initialized with invalid expiryYear 'abcd'." + ); + } catch (e) { + if (e.name != "NS_ERROR_FAILURE") { + emitTestFail( + "expiryYear abcd expected 'NS_ERROR_FAILURE', but got " + e.name + "." + ); + } + } + // test invalid expiryYear 11111 + try { + basiccardResponseData.initData( + "", + "4916855166538720", + "", + "11111", + "", + null + ); + emitTestFail( + "BasicCardResponse should not be initialized with invalid expiryYear '11111'." + ); + } catch (e) { + if (e.name != "NS_ERROR_FAILURE") { + emitTestFail( + "expiryYear 11111 expected 'NS_ERROR_FAILURE', but got " + e.name + "." + ); + } + } + + const responseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + try { + responseData.initData({}); + } catch (e) { + emitTestFail("Fail to initialize response data with empty object."); + } + + try { + showResponse.init( + "testid", + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "basic-card", // payment method + responseData, // payment method data + "Bill A. Pacheco", // payer name + "", // payer email + "" + ); // payer phone + emitTestFail( + "nsIPaymentShowActionResponse should not be initialized with basic-card method and nsIGeneralResponseData." + ); + } catch (e) { + if (e.name != "NS_ERROR_FAILURE") { + emitTestFail( + "ShowResponse init expected 'NS_ERROR_FAILURE', but got " + e.name + "." + ); + } + } + sendAsyncMessage("error-response-test-complete"); +}); + +addMessageListener("teardown", function () { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/Bug1478740ChromeScript.js b/dom/payments/test/Bug1478740ChromeScript.js new file mode 100644 index 0000000000..6e8633ff87 --- /dev/null +++ b/dom/payments/test/Bug1478740ChromeScript.js @@ -0,0 +1,90 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +function emitTestFail(message) { + sendAsyncMessage("test-fail", message); +} +function emitTestPass(message) { + sendAsyncMessage("test-pass", message); +} + +function rejectPayment(requestId) { + const responseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + responseData.initData({}); + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_REJECTED, + "", // payment method + responseData, // payment method data + "", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +const DummyUIService = { + testName: "", + requestId: "", + showPayment(requestId) { + this.requestId = requestId; + sendAsyncMessage("showing-payment", { data: "successful" }); + }, + abortPayment(requestId) { + this.requestId = requestId; + }, + completePayment(requestId) { + this.requestId = requestId; + }, + updatePayment(requestId) { + this.requestId = requestId; + }, + closePayment(requestId) { + this.requestId = requestId; + }, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +paymentSrv.setTestingUIService( + DummyUIService.QueryInterface(Ci.nsIPaymentUIService) +); + +addMessageListener("reject-payment", function () { + rejectPayment(DummyUIService.requestId); + sendAsyncMessage("reject-payment-complete"); +}); + +addMessageListener("start-test", function (testName) { + DummyUIService.testName = testName; + sendAsyncMessage("start-test-complete"); +}); + +addMessageListener("finish-test", function () { + DummyUIService.testName = ""; + sendAsyncMessage("finish-test-complete"); +}); + +addMessageListener("teardown", function () { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/Bug1490698ChromeScript.js b/dom/payments/test/Bug1490698ChromeScript.js new file mode 100644 index 0000000000..dbae51b117 --- /dev/null +++ b/dom/payments/test/Bug1490698ChromeScript.js @@ -0,0 +1,226 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +function emitTestFail(message) { + sendAsyncMessage("test-fail", message); +} +function emitTestPass(message) { + sendAsyncMessage("test-pass", message); +} + +const billingAddress = Cc[ + "@mozilla.org/dom/payments/payment-address;1" +].createInstance(Ci.nsIPaymentAddress); +const addressLine = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray +); +const address = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString +); +address.data = "Easton Ave"; +addressLine.appendElement(address); +billingAddress.init( + "USA", // country + addressLine, // address line + "CA", // region + "CA", // region code + "San Bruno", // city + "", // dependent locality + "94066", // postal code + "123456", // sorting code + "", // organization + "Bill A. Pacheco", // recipient + "+14344413879" +); // phone + +function acceptPayment(requestId) { + const basiccardResponseData = Cc[ + "@mozilla.org/dom/payments/basiccard-response-data;1" + ].createInstance(Ci.nsIBasicCardResponseData); + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + basiccardResponseData.initData( + "Bill A. Pacheco", // cardholderName + "4916855166538720", // cardNumber + "01", // expiryMonth + "2024", // expiryYear + "180", // cardSecurityCode + billingAddress + ); // billingAddress + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "basic-card", // payment method + basiccardResponseData, // payment method data + "Bill A. Pacheco", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function rejectPayment(requestId) { + const responseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + responseData.initData({}); + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_REJECTED, + "", // payment method + responseData, // payment method data + "", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +const DummyUIService = { + testName: "", + requestId: "", + showPayment(requestId) { + this.requestId = requestId; + acceptPayment(requestId); + }, + abortPaymen(requestId) { + this.requestId = requestId; + }, + completePayment(requestId) { + this.requestId = requestId; + let completeResponse = Cc[ + "@mozilla.org/dom/payments/payment-complete-action-response;1" + ].createInstance(Ci.nsIPaymentCompleteActionResponse); + completeResponse.init( + requestId, + Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED + ); + paymentSrv.respondPayment( + completeResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + updatePayment(requestId) { + this.requestId = requestId; + }, + closePayment(requestId) { + this.requestId = requestId; + }, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +paymentSrv.setTestingUIService( + DummyUIService.QueryInterface(Ci.nsIPaymentUIService) +); + +addMessageListener("start-test", function (testName) { + DummyUIService.testName = testName; + sendAsyncMessage("start-test-complete"); +}); + +addMessageListener("finish-test", function () { + DummyUIService.testName = ""; + sendAsyncMessage("finish-test-complete"); +}); + +addMessageListener("interact-with-payment", function () { + if (DummyUIService.requestId === "") { + emitTestFail(`${DummyUIService.testName}: Unexpected empty requestId`); + } + try { + acceptPayment(DummyUIService.requestId); + emitTestFail( + `${DummyUIService.testName}: Got unexpected success when accepting PaymentRequest.` + ); + } catch (err) { + if (err.name !== "NS_ERROR_FAILURE") { + emitTestFail( + `${DummyUIService.testName}: Got unexpected '${err.name}' when accepting PaymentRequest.` + ); + } else { + emitTestPass( + `${DummyUIService.testName}: Got expected 'NS_ERROR_FAILURE' when accepting PaymentRequest.` + ); + } + } + + try { + rejectPayment(DummyUIService.requestId); + emitTestFail( + `${DummyUIService.testName}: Got unexpected success when rejecting PaymentRequest.` + ); + } catch (err) { + if (err.name !== "NS_ERROR_FAILURE") { + emitTestFail( + `${DummyUIService.testName}: Got unexpected '${err.name}' when rejecting PaymentRequest.` + ); + } else { + emitTestPass( + `${DummyUIService.testName}: Got expected 'NS_ERROR_FAILURE' when rejecting PaymentRequest.` + ); + } + } + + try { + paymentSrv.changeShippingOption( + DummyUIService.requestId, + "error shippping option" + ); + emitTestFail( + `${DummyUIService.testName}: Got unexpected success when changing shippingOption.` + ); + } catch (err) { + if (err.name !== "NS_ERROR_FAILURE") { + emitTestFail( + `${DummyUIService.testName}: Got unexpected '${err.name}' when changin shippingOption.` + ); + } else { + emitTestPass( + `${DummyUIService.testName}: Got expected 'NS_ERROR_FAILURE' when changing shippingOption.` + ); + } + } + + try { + paymentSrv.changeShippingOption(DummyUIService.requestId, billingAddress); + emitTestFail( + `${DummyUIService.testName}: Got unexpected success when changing shippingAddress.` + ); + } catch (err) { + if (err.name !== "NS_ERROR_FAILURE") { + emitTestFail( + `${DummyUIService.testName}: Got unexpected '${err.name}' when changing shippingAddress.` + ); + } else { + emitTestPass( + `${DummyUIService.testName}: Got expected 'NS_ERROR_FAILURE' when changing shippingAddress.` + ); + } + } + sendAsyncMessage("interact-with-payment-complete"); +}); + +addMessageListener("teardown", function () { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/ClosePaymentChromeScript.js b/dom/payments/test/ClosePaymentChromeScript.js new file mode 100644 index 0000000000..60433f8f11 --- /dev/null +++ b/dom/payments/test/ClosePaymentChromeScript.js @@ -0,0 +1,160 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +function emitTestFail(message) { + sendAsyncMessage("test-fail", `${DummyUIService.testName}: ${message}`); +} +function emitTestPass(message) { + sendAsyncMessage("test-pass", `${DummyUIService.testName}: ${message}`); +} + +addMessageListener("close-check", function () { + const paymentEnum = paymentSrv.enumerate(); + if (paymentEnum.hasMoreElements()) { + emitTestFail("Non-empty PaymentRequest queue in PaymentRequestService."); + } else { + emitTestPass("Got empty PaymentRequest queue in PaymentRequestService."); + } + sendAsyncMessage("close-check-complete"); +}); + +var setPaymentNums = 0; + +addMessageListener("payment-num-set", function () { + setPaymentNums = 0; + const paymentEnum = paymentSrv.enumerate(); + while (paymentEnum.hasMoreElements()) { + setPaymentNums = setPaymentNums + 1; + paymentEnum.getNext(); + } + sendAsyncMessage("payment-num-set-complete"); +}); + +addMessageListener("payment-num-check", function (expectedNumPayments) { + const paymentEnum = paymentSrv.enumerate(); + let numPayments = 0; + while (paymentEnum.hasMoreElements()) { + numPayments = numPayments + 1; + paymentEnum.getNext(); + } + if (numPayments !== expectedNumPayments + setPaymentNums) { + emitTestFail( + "Expected '" + + expectedNumPayments + + "' PaymentRequests in PaymentRequestService" + + ", but got '" + + numPayments + + "'." + ); + } else { + emitTestPass( + "Got expected '" + + numPayments + + "' PaymentRequests in PaymentRequestService." + ); + } + // force cleanup PaymentRequests for clear environment to next testcase. + paymentSrv.cleanup(); + sendAsyncMessage("payment-num-check-complete"); +}); + +addMessageListener("test-setup", testName => { + DummyUIService.testName = testName; + sendAsyncMessage("test-setup-complete"); +}); + +addMessageListener("reject-payment", expectedError => { + try { + const responseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + responseData.initData({}); + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + DummyUIService.respondRequestId, + Ci.nsIPaymentActionResponse.PAYMENT_REJECTED, + "", // payment method + responseData, // payment method data + "", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + emitTestPass("Reject PaymentRequest successfully"); + } catch (error) { + if (expectedError) { + if (error.name === "NS_ERROR_FAILURE") { + emitTestPass( + "Got expected NS_ERROR_FAILURE when responding a closed PaymentRequest" + ); + sendAsyncMessage("reject-payment-complete"); + return; + } + } + emitTestFail( + "Unexpected error '" + + error.name + + "' when reponding a closed PaymentRequest" + ); + } + sendAsyncMessage("reject-payment-complete"); +}); + +addMessageListener("update-payment", () => { + try { + paymentSrv.changeShippingOption(DummyUIService.respondRequestId, ""); + emitTestPass("Change shippingOption succefully"); + } catch (error) { + emitTestFail( + "Unexpected error '" + error.name + "' when changing the shipping option" + ); + } + sendAsyncMessage("update-payment-complete"); +}); + +const DummyUIService = { + testName: "", + respondRequestId: "", + showPayment: requestId => { + DummyUIService.respondRequestId = requestId; + }, + abortPayment: requestId => { + DummyUIService.respondRequestId = requestId; + }, + completePayment: requestId => { + DummyUIService.respondRequestId = requestId; + }, + updatePayment: requestId => { + DummyUIService.respondRequestId = requestId; + }, + closePayment: requestId => { + this.respondRequestId = requestId; + }, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +paymentSrv.setTestingUIService( + DummyUIService.QueryInterface(Ci.nsIPaymentUIService) +); + +addMessageListener("teardown", function () { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/ConstructorChromeScript.js b/dom/payments/test/ConstructorChromeScript.js new file mode 100644 index 0000000000..17e59f6241 --- /dev/null +++ b/dom/payments/test/ConstructorChromeScript.js @@ -0,0 +1,490 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +function emitTestFail(message) { + sendAsyncMessage("test-fail", message); +} + +function checkSimplestRequest(payRequest) { + if (payRequest.topLevelPrincipal.origin != "https://example.com") { + emitTestFail( + "Top level principal's Origin should be 'https://example.com', but got '" + + payRequest.topLevelPrincipal.origin + + "'." + ); + } + + if (payRequest.paymentMethods.length != 1) { + emitTestFail("paymentMethods' length should be 1."); + } + + const methodData = payRequest.paymentMethods.queryElementAt( + 0, + Ci.nsIPaymentMethodData + ); + if (!methodData) { + emitTestFail("Fail to get payment methodData."); + } + const supportedMethod = methodData.supportedMethods; + if (supportedMethod != "basic-card") { + emitTestFail("supported method should be 'basic-card'."); + } + if (methodData.data) { + emitTestFail("methodData.data should not exist."); + } + + // checking the passed PaymentDetails parameter + const details = payRequest.paymentDetails; + if (details.totalItem.label != "Total") { + emitTestFail("total item's label should be 'Total'."); + } + if (details.totalItem.amount.currency != "USD") { + emitTestFail("total item's currency should be 'USD'."); + } + if (details.totalItem.amount.value != "1.00") { + emitTestFail("total item's value should be '1.00'."); + } + + if (details.displayItems.length !== 0) { + emitTestFail("details.displayItems should be an empty array."); + } + if (details.modifiers.length !== 0) { + emitTestFail("details.modifiers should be an empty array."); + } + if (details.shippingOptions.length !== 0) { + emitTestFail("details.shippingOptions should be an empty array."); + } + + // checking the default generated PaymentOptions parameter + const paymentOptions = payRequest.paymentOptions; + if (paymentOptions.requestPayerName) { + emitTestFail("requestPayerName option should be false."); + } + if (paymentOptions.requestPayerEmail) { + emitTestFail("requestPayerEmail option should be false."); + } + if (paymentOptions.requestPayerPhone) { + emitTestFail("requestPayerPhone option should be false."); + } + if (paymentOptions.requestShipping) { + emitTestFail("requestShipping option should be false."); + } + if (paymentOptions.shippingType != "shipping") { + emitTestFail("shippingType option should be 'shipping'."); + } +} + +// eslint-disable-next-line complexity +function checkComplexRequest(payRequest) { + if (payRequest.topLevelPrincipal.origin != "https://example.com") { + emitTestFail( + "Top level principal's origin should be 'https://example.com', but got '" + + payRequest.topLevelPrincipal.origin + + "'." + ); + } + + if (payRequest.paymentMethods.length != 1) { + emitTestFail("paymentMethods' length should be 1."); + } + + const methodData = payRequest.paymentMethods.queryElementAt( + 0, + Ci.nsIPaymentMethodData + ); + if (!methodData) { + emitTestFail("Fail to get payment methodData."); + } + let supportedMethod = methodData.supportedMethods; + if (supportedMethod != "basic-card") { + emitTestFail("supported method should be 'basic-card'."); + } + const data = methodData.data; + const supportedNetworks = data.supportedNetworks; + const expectedSupportedNetworks = [ + "unionpay", + "visa", + "mastercard", + "amex", + "discover", + "diners", + "jcb", + "mir", + ]; + if (supportedNetworks.length != expectedSupportedNetworks.length) { + emitTestFail( + "supportedNetworks.length should be " + + expectedSupportedNetworks.length + + ", but got " + + supportedNetworks.length + + "." + ); + } + for (let idx = 0; idx < supportedNetworks.length; idx++) { + if (supportedNetworks[idx] != expectedSupportedNetworks[idx]) { + emitTestFail( + "supportedNetworks[" + + idx + + "] should be '" + + expectedSupportedNetworks[idx] + + "', but got '" + + supportedNetworks[idx] + + "'." + ); + } + } + // checking the passed PaymentDetails parameter + const details = payRequest.paymentDetails; + if (details.id != "payment details") { + emitTestFail("details.id should be 'payment details'."); + } + if (details.totalItem.label != "Total") { + emitTestFail("total item's label should be 'Total'."); + } + if (details.totalItem.amount.currency != "USD") { + emitTestFail("total item's currency should be 'USD'."); + } + if (details.totalItem.amount.value != "100.00") { + emitTestFail("total item's value should be '100.00'."); + } + + const displayItems = details.displayItems; + if (!details.displayItems) { + emitTestFail("details.displayItems should not be undefined."); + } + if (displayItems.length != 2) { + emitTestFail("displayItems' length should be 2."); + } + let item = displayItems.queryElementAt(0, Ci.nsIPaymentItem); + if (item.label != "First item") { + emitTestFail("1st display item's label should be 'First item'."); + } + if (item.amount.currency != "USD") { + emitTestFail("1st display item's currency should be 'USD'."); + } + if (item.amount.value != "60.00") { + emitTestFail("1st display item's value should be '60.00'."); + } + item = displayItems.queryElementAt(1, Ci.nsIPaymentItem); + if (item.label != "Second item") { + emitTestFail("2nd display item's label should be 'Second item'."); + } + if (item.amount.currency != "USD") { + emitTestFail("2nd display item's currency should be 'USD'."); + } + if (item.amount.value != "40.00") { + emitTestFail("2nd display item's value should be '40.00'."); + } + + const modifiers = details.modifiers; + if (!modifiers) { + emitTestFail("details.displayItems should not be undefined."); + } + if (modifiers.length != 1) { + emitTestFail("modifiers' length should be 1."); + } + const modifier = modifiers.queryElementAt(0, Ci.nsIPaymentDetailsModifier); + const supportedMethods = modifier.supportedMethods; + if (supportedMethod != "basic-card") { + emitTestFail("modifier's supported method name should be 'basic-card'."); + } + if (modifier.total.label != "Discounted Total") { + emitTestFail("modifier's total label should be 'Discounted Total'."); + } + if (modifier.total.amount.currency != "USD") { + emitTestFail("modifier's total currency should be 'USD'."); + } + if (modifier.total.amount.value != "90.00") { + emitTestFail("modifier's total value should be '90.00'."); + } + + const additionalItems = modifier.additionalDisplayItems; + if (additionalItems.length != 1) { + emitTestFail("additionalDisplayItems' length should be 1."); + } + const additionalItem = additionalItems.queryElementAt(0, Ci.nsIPaymentItem); + if (additionalItem.label != "basic-card discount") { + emitTestFail("additional item's label should be 'basic-card discount'."); + } + if (additionalItem.amount.currency != "USD") { + emitTestFail("additional item's currency should be 'USD'."); + } + if (additionalItem.amount.value != "-10.00") { + emitTestFail("additional item's value should be '-10.00'."); + } + if (modifier.data.discountProgramParticipantId != "86328764873265") { + emitTestFail( + "modifier's data should be '86328764873265', but got '" + + modifier.data.discountProgramParticipantId + + "'." + ); + } + + const shippingOptions = details.shippingOptions; + if (!shippingOptions) { + emitTestFail("details.shippingOptions should not be undefined."); + } + if (shippingOptions.length != 2) { + emitTestFail("shippingOptions' length should be 2."); + } + let shippingOption = shippingOptions.queryElementAt( + 0, + Ci.nsIPaymentShippingOption + ); + if (shippingOption.id != "NormalShipping") { + emitTestFail("1st shippingOption's id should be 'NormalShipping'."); + } + if (shippingOption.label != "NormalShipping") { + emitTestFail("1st shippingOption's lable should be 'NormalShipping'."); + } + if (shippingOption.amount.currency != "USD") { + emitTestFail("1st shippingOption's amount currency should be 'USD'."); + } + if (shippingOption.amount.value != "10.00") { + emitTestFail("1st shippingOption's amount value should be '10.00'."); + } + if (!shippingOption.selected) { + emitTestFail("1st shippingOption should be selected."); + } + shippingOption = shippingOptions.queryElementAt( + 1, + Ci.nsIPaymentShippingOption + ); + if (shippingOption.id != "FastShipping") { + emitTestFail("2nd shippingOption's id should be 'FastShipping'."); + } + if (shippingOption.label != "FastShipping") { + emitTestFail("2nd shippingOption's lable should be 'FastShipping'."); + } + if (shippingOption.amount.currency != "USD") { + emitTestFail("2nd shippingOption's amount currency should be 'USD'."); + } + if (shippingOption.amount.value != "30.00") { + emitTestFail("2nd shippingOption's amount value should be '30.00'."); + } + if (shippingOption.selected) { + emitTestFail("2nd shippingOption should not be selected."); + } + + // checking the default generated PaymentOptions parameter + const paymentOptions = payRequest.paymentOptions; + if (!paymentOptions.requestPayerName) { + emitTestFail("requestPayerName option should be true."); + } + if (!paymentOptions.requestPayerEmail) { + emitTestFail("requestPayerEmail option should be true."); + } + if (!paymentOptions.requestPayerPhone) { + emitTestFail("requestPayerPhone option should be true."); + } + if (!paymentOptions.requestShipping) { + emitTestFail("requestShipping option should be true."); + } + if (paymentOptions.shippingType != "shipping") { + emitTestFail("shippingType option should be 'shipping'."); + } +} + +function checkNonBasicCardRequest(payRequest) { + if (payRequest.paymentMethods.length != 1) { + emitTestFail("paymentMethods' length should be 1."); + } + + const methodData = payRequest.paymentMethods.queryElementAt( + 0, + Ci.nsIPaymentMethodData + ); + if (!methodData) { + emitTestFail("Fail to get payment methodData."); + } + const supportedMethod = methodData.supportedMethods; + if (supportedMethod != "testing-payment-method") { + emitTestFail("supported method should be 'testing-payment-method'."); + } + + const paymentId = methodData.data.paymentId; + if (paymentId != "P3892940") { + emitTestFail( + "methodData.data.paymentId should be 'P3892940', but got " + + paymentId + + "." + ); + } + const paymentType = methodData.data.paymentType; + if (paymentType != "prepaid") { + emitTestFail( + "methodData.data.paymentType should be 'prepaid', but got " + + paymentType + + "." + ); + } + + // checking the passed PaymentDetails parameter + const details = payRequest.paymentDetails; + if (details.totalItem.label != "Total") { + emitTestFail("total item's label should be 'Total'."); + } + if (details.totalItem.amount.currency != "USD") { + emitTestFail("total item's currency should be 'USD'."); + } + if (details.totalItem.amount.value != "1.00") { + emitTestFail("total item's value should be '1.00'."); + } + + if (details.displayItems.length !== 0) { + emitTestFail("details.displayItems should be an zero length array."); + } + if (details.displayItems.length !== 0) { + emitTestFail("details.modifiers should be an zero length array."); + } + if (details.displayItems.length !== 0) { + emitTestFail("details.shippingOptions should be an zero length array."); + } + + // checking the default generated PaymentOptions parameter + const paymentOptions = payRequest.paymentOptions; + if (paymentOptions.requestPayerName) { + emitTestFail("requestPayerName option should be false."); + } + if (paymentOptions.requestPayerEmail) { + emitTestFail("requestPayerEmail option should be false."); + } + if (paymentOptions.requestPayerPhone) { + emitTestFail("requestPayerPhone option should be false."); + } + if (paymentOptions.requestShipping) { + emitTestFail("requestShipping option should be false."); + } + if (paymentOptions.shippingType != "shipping") { + emitTestFail("shippingType option should be 'shipping'."); + } +} + +function checkSimplestRequestHandler() { + const paymentEnum = paymentSrv.enumerate(); + if (!paymentEnum.hasMoreElements()) { + emitTestFail( + "PaymentRequestService should have at least one payment request." + ); + } + for (let payRequest of paymentEnum) { + if (!payRequest) { + emitTestFail("Fail to get existing payment request."); + break; + } + checkSimplestRequest(payRequest); + } + paymentSrv.cleanup(); + sendAsyncMessage("check-complete"); +} + +function checkComplexRequestHandler() { + const paymentEnum = paymentSrv.enumerate(); + if (!paymentEnum.hasMoreElements()) { + emitTestFail( + "PaymentRequestService should have at least one payment request." + ); + } + for (let payRequest of paymentEnum) { + if (!payRequest) { + emitTestFail("Fail to get existing payment request."); + break; + } + checkComplexRequest(payRequest); + } + paymentSrv.cleanup(); + sendAsyncMessage("check-complete"); +} + +function checkNonBasicCardRequestHandler() { + const paymentEnum = paymentSrv.enumerate(); + if (!paymentEnum.hasMoreElements()) { + emitTestFail( + "PaymentRequestService should have at least one payment request." + ); + } + for (let payRequest of paymentEnum) { + if (!payRequest) { + emitTestFail("Fail to get existing payment request."); + break; + } + checkNonBasicCardRequest(payRequest); + } + paymentSrv.cleanup(); + sendAsyncMessage("check-complete"); +} + +function checkMultipleRequestsHandler() { + const paymentEnum = paymentSrv.enumerate(); + if (!paymentEnum.hasMoreElements()) { + emitTestFail( + "PaymentRequestService should have at least one payment request." + ); + } + for (let payRequest of paymentEnum) { + if (!payRequest) { + emitTestFail("Fail to get existing payment request."); + break; + } + if (payRequest.paymentDetails.id == "payment details") { + checkComplexRequest(payRequest); + } else { + checkSimplestRequest(payRequest); + } + } + paymentSrv.cleanup(); + sendAsyncMessage("check-complete"); +} + +function checkCrossOriginTopLevelPrincipalHandler() { + const paymentEnum = paymentSrv.enumerate(); + if (!paymentEnum.hasMoreElements()) { + emitTestFail( + "PaymentRequestService should have at least one payment request." + ); + } + for (let payRequest of paymentEnum) { + if (!payRequest) { + emitTestFail("Fail to get existing payment request."); + break; + } + if (payRequest.topLevelPrincipal.origin != "https://example.com") { + emitTestFail( + "Top level principal's origin should be 'https://example.com', but got '" + + payRequest.topLevelPrincipal.origin + + "'." + ); + } + } + paymentSrv.cleanup(); + sendAsyncMessage("check-complete"); +} + +addMessageListener("check-simplest-request", checkSimplestRequestHandler); +addMessageListener("check-complex-request", checkComplexRequestHandler); +addMessageListener("check-multiple-requests", checkMultipleRequestsHandler); +addMessageListener( + "check-nonbasiccard-request", + checkNonBasicCardRequestHandler +); +addMessageListener( + "check-cross-origin-top-level-principal", + checkCrossOriginTopLevelPrincipalHandler +); + +addMessageListener("teardown", function () { + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/CurrencyAmountValidationChromeScript.js b/dom/payments/test/CurrencyAmountValidationChromeScript.js new file mode 100644 index 0000000000..a15e79be18 --- /dev/null +++ b/dom/payments/test/CurrencyAmountValidationChromeScript.js @@ -0,0 +1,67 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +const InvalidDetailsUIService = { + showPayment(requestId) { + paymentSrv.changeShippingOption(requestId, ""); + }, + abortPayment(requestId) { + const abortResponse = Cc[ + "@mozilla.org/dom/payments/payment-abort-action-response;1" + ].createInstance(Ci.nsIPaymentAbortActionResponse); + abortResponse.init(requestId, Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED); + paymentSrv.respondPayment( + abortResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + completePayment(requestId) {}, + updatePayment(requestId) {}, + closePayment(requestId) {}, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +function checkLowerCaseCurrency() { + const paymentEnum = paymentSrv.enumerate(); + if (!paymentEnum.hasMoreElements()) { + const msg = + "PaymentRequestService should have at least one payment request."; + sendAsyncMessage("test-fail", msg); + } + for (let payRequest of paymentEnum) { + if (!payRequest) { + sendAsyncMessage("test-fail", "Fail to get existing payment request."); + break; + } + const { currency } = payRequest.paymentDetails.totalItem.amount; + if (currency != "USD") { + const msg = + "Currency of PaymentItem total should be 'USD', but got ${currency}"; + sendAsyncMessage("check-complete"); + } + } + paymentSrv.cleanup(); + sendAsyncMessage("check-complete"); +} + +addMessageListener("check-lower-case-currency", checkLowerCaseCurrency); + +addMessageListener("set-update-with-invalid-details-ui-service", () => { + paymentSrv.setTestingUIService( + InvalidDetailsUIService.QueryInterface(Ci.nsIPaymentUIService) + ); +}); + +addMessageListener("teardown", () => sendAsyncMessage("teardown-complete")); diff --git a/dom/payments/test/DefaultData.js b/dom/payments/test/DefaultData.js new file mode 100644 index 0000000000..13723b5799 --- /dev/null +++ b/dom/payments/test/DefaultData.js @@ -0,0 +1,59 @@ +// testing data declation +const defaultMethods = [ + { + supportedMethods: "basic-card", + data: { + supportedNetworks: [ + "unionpay", + "visa", + "mastercard", + "amex", + "discover", + "diners", + "jcb", + "mir", + ], + }, + }, + { + supportedMethods: "testing-payment-method", + }, +]; + +const defaultDetails = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, + shippingOptions: [ + { + id: "NormalShipping", + label: "NormalShipping", + amount: { + currency: "USD", + value: "10.00", + }, + selected: false, + }, + { + id: "FastShipping", + label: "FastShipping", + amount: { + currency: "USD", + value: "5.00", + }, + selected: false, + }, + ], +}; + +const defaultOptions = { + requestPayerName: true, + requestPayerEmail: false, + requestPayerPhone: false, + requestShipping: true, + shippingType: "shipping", +}; diff --git a/dom/payments/test/GeneralChromeScript.js b/dom/payments/test/GeneralChromeScript.js new file mode 100644 index 0000000000..74fe4c299a --- /dev/null +++ b/dom/payments/test/GeneralChromeScript.js @@ -0,0 +1,20 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +addMessageListener("teardown", function () { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/PMIValidationChromeScript.js b/dom/payments/test/PMIValidationChromeScript.js new file mode 100644 index 0000000000..3deec2a89c --- /dev/null +++ b/dom/payments/test/PMIValidationChromeScript.js @@ -0,0 +1,82 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +const UIService = { + showPayment(requestId) { + paymentSrv.changeShippingOption(requestId, ""); + }, + abortPayment(requestId) { + let abortResponse = Cc[ + "@mozilla.org/dom/payments/payment-abort-action-response;1" + ].createInstance(Ci.nsIPaymentAbortActionResponse); + abortResponse.init(requestId, Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED); + paymentSrv.respondPayment( + abortResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + completePayment(requestId) { + const completeResponse = Cc[ + "@mozilla.org/dom/payments/payment-complete-action-response;1" + ].createInstance(Ci.nsIPaymentCompleteActionResponse); + completeResponse.init( + requestId, + Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED + ); + paymentSrv.respondPayment( + completeResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + updatePayment(requestId) { + const showResponseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + showResponseData.initData({ + paymentToken: "6880281f-0df3-4b8e-916f-66575e2457c1", + }); + + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "https://example.com", // payment method + showResponseData, // payment method data + "Bill A. Pacheco", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + closePayment(requestId) {}, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +function emitTestFail(message) { + sendAsyncMessage("test-fail", message); +} + +addMessageListener("set-ui-service", function () { + paymentSrv.setTestingUIService( + UIService.QueryInterface(Ci.nsIPaymentUIService) + ); +}); + +addMessageListener("teardown", function () { + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/PayerDetailsChromeScript.js b/dom/payments/test/PayerDetailsChromeScript.js new file mode 100644 index 0000000000..77024cc754 --- /dev/null +++ b/dom/payments/test/PayerDetailsChromeScript.js @@ -0,0 +1,83 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +const TestingUIService = { + showPayment(requestId, name = "", email = "", phone = "") { + const showResponseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + showResponseData.initData({}); + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "testing-payment-method", // payment method + showResponseData, // payment method data + name, + email, + phone + ); + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + // .retry({ payer }) and .updateWith({payerErrors}) both get routed here: + updatePayment(requestId) { + // Let's echo what was sent in by the error... + const request = paymentSrv.getPaymentRequestById(requestId); + const { name, email, phone } = request.paymentDetails.payerErrors; + const { error } = request.paymentDetails; + // Let's use the .error as the switch + switch (error) { + case "retry-fire-payerdetaichangeevent": { + paymentSrv.changePayerDetail(requestId, name, email, phone); + break; + } + case "update-with": { + this.showPayment(requestId, name, email, phone); + break; + } + default: + const msg = `Expect details.error value: '${error}'`; + sendAsyncMessage("test-fail", msg); + } + }, + completePayment(requestId) { + const request = paymentSrv.getPaymentRequestById(requestId); + const completeResponse = Cc[ + "@mozilla.org/dom/payments/payment-complete-action-response;1" + ].createInstance(Ci.nsIPaymentCompleteActionResponse); + completeResponse.init( + requestId, + Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED + ); + paymentSrv.respondPayment( + completeResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + get QueryInterface() { + return ChromeUtils.generateQI(["nsIPaymentUIService"]); + }, +}; + +paymentSrv.setTestingUIService( + TestingUIService.QueryInterface(Ci.nsIPaymentUIService) +); + +addMessageListener("teardown", () => { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/RequestShippingChromeScript.js b/dom/payments/test/RequestShippingChromeScript.js new file mode 100644 index 0000000000..d8dafb8a08 --- /dev/null +++ b/dom/payments/test/RequestShippingChromeScript.js @@ -0,0 +1,116 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +function emitTestFail(message) { + sendAsyncMessage("test-fail", message); +} + +const shippingAddress = Cc[ + "@mozilla.org/dom/payments/payment-address;1" +].createInstance(Ci.nsIPaymentAddress); +const addressLine = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray +); +const address = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString +); +address.data = "Easton Ave"; +addressLine.appendElement(address); +shippingAddress.init( + "", // country + addressLine, // address line + "", // region + "", // region code + "", // city + "", // dependent locality + "", // postal code + "", // sorting code + "", // organization + "", // recipient + "" +); // phone + +const NormalUIService = { + shippingOptionChanged: false, + showPayment(requestId) { + paymentSrv.changeShippingAddress(requestId, shippingAddress); + }, + abortPayment(requestId) {}, + completePayment(requestId) { + let completeResponse = Cc[ + "@mozilla.org/dom/payments/payment-complete-action-response;1" + ].createInstance(Ci.nsIPaymentCompleteActionResponse); + completeResponse.init( + requestId, + Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED + ); + paymentSrv.respondPayment( + completeResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + updatePayment(requestId) { + let showResponse = null; + let payRequest = paymentSrv.getPaymentRequestById(requestId); + + const shippingOptions = payRequest.paymentDetails.shippingOptions; + if (shippingOptions.length) { + emitTestFail("Wrong length for shippingOptions."); + } + + const showResponseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + + try { + showResponseData.initData({ + paymentToken: "6880281f-0df3-4b8e-916f-66575e2457c1", + }); + } catch (e) { + emitTestFail( + 'Fail to initialize response data with { paymentToken: "6880281f-0df3-4b8e-916f-66575e2457c1",}' + ); + } + + showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "testing-payment-method", // payment method + showResponseData, // payment method data + "", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + closePayment(requestId) {}, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +addMessageListener("set-normal-ui-service", function () { + paymentSrv.setTestingUIService( + NormalUIService.QueryInterface(Ci.nsIPaymentUIService) + ); +}); + +addMessageListener("teardown", function () { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/RetryPaymentChromeScript.js b/dom/payments/test/RetryPaymentChromeScript.js new file mode 100644 index 0000000000..d1486d676d --- /dev/null +++ b/dom/payments/test/RetryPaymentChromeScript.js @@ -0,0 +1,238 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +function emitTestFail(message) { + sendAsyncMessage("test-fail", message); +} +function emitTestPass(message) { + sendAsyncMessage("test-pass", message); +} + +const billingAddress = Cc[ + "@mozilla.org/dom/payments/payment-address;1" +].createInstance(Ci.nsIPaymentAddress); +const addressLine = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray +); +const address = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString +); +address.data = "Easton Ave"; +addressLine.appendElement(address); +billingAddress.init( + "USA", // country + addressLine, // address line + "CA", // region + "CA", // region code + "San Bruno", // city + "", // dependent locality + "94066", // postal code + "123456", // sorting code + "", // organization + "Bill A. Pacheco", // recipient + "+14344413879" +); // phone + +function acceptPayment(requestId, mode) { + const basiccardResponseData = Cc[ + "@mozilla.org/dom/payments/basiccard-response-data;1" + ].createInstance(Ci.nsIBasicCardResponseData); + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + basiccardResponseData.initData( + "Bill A. Pacheco", // cardholderName + "4916855166538720", // cardNumber + "01", // expiryMonth + "2024", // expiryYear + "180", // cardSecurityCode + billingAddress + ); // billingAddress + if (mode === "show") { + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "basic-card", // payment method + basiccardResponseData, // payment method data + "Bill A. Pacheco", // payer name + "", // payer email + "" + ); // payer phone + } + if (mode == "retry") { + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "basic-card", // payment method + basiccardResponseData, // payment method data + "Bill A. Pacheco", // payer name + "bpacheco@test.org", // payer email + "+123456789" + ); // payer phone + } + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function rejectPayment(requestId) { + const responseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + responseData.initData({}); + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_REJECTED, + "", // payment method + responseData, // payment method data + "", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function checkAddressErrors(testName, errors) { + if (!errors) { + emitTestFail( + `${testName}: Expect non-null shippingAddressErrors, but got null.` + ); + return; + } + for (const [key, msg] of Object.entries(errors)) { + const expected = `${key} error`; + if (msg !== expected) { + emitTestFail( + `${testName}: Expected '${expected}' on shippingAddressErrors.${key}, but got '${msg}'.` + ); + return; + } + } +} + +function checkPayerErrors(testName, errors) { + if (!errors) { + emitTestFail(`${testName}: Expect non-null payerErrors, but got null.`); + return; + } + for (const [key, msg] of Object.entries(errors)) { + const expected = `${key} error`; + if (msg !== expected) { + emitTestFail( + `${testName}: Expected '${expected}' on payerErrors.${key}, but got '${msg}'.` + ); + return; + } + } +} + +function checkPaymentMethodErrors(testName, errors) { + if (!errors) { + emitTestFail( + `${testName} :Expect non-null paymentMethodErrors, but got null.` + ); + return; + } + for (const [key, msg] of Object.entries(errors)) { + const expected = `method ${key} error`; + if (msg !== expected) { + emitTestFail( + `${testName}: Expected '${expected}' on paymentMethodErrors.${key}, but got '${msg}'.` + ); + return; + } + } +} + +const DummyUIService = { + testName: "", + rejectRetry: false, + showPayment(requestId) { + acceptPayment(requestId, "show"); + }, + abortPaymen(requestId) { + respondRequestId = requestId; + }, + completePayment(requestId) { + let completeResponse = Cc[ + "@mozilla.org/dom/payments/payment-complete-action-response;1" + ].createInstance(Ci.nsIPaymentCompleteActionResponse); + completeResponse.init( + requestId, + Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED + ); + paymentSrv.respondPayment( + completeResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + updatePayment(requestId) { + const payment = paymentSrv.getPaymentRequestById(requestId); + if (payment.paymentDetails.error !== "error") { + emitTestFail( + "Expect 'error' on details.error, but got '" + + payment.paymentDetails.error + + "'" + ); + } + checkAddressErrors( + this.testName, + payment.paymentDetails.shippingAddressErrors + ); + checkPayerErrors(this.testName, payment.paymentDetails.payerErrors); + checkPaymentMethodErrors( + this.testName, + payment.paymentDetails.paymentMethodErrors + ); + if (this.rejectRetry) { + rejectPayment(requestId); + } else { + acceptPayment(requestId, "retry"); + } + }, + closePayment: requestId => { + respondRequestId = requestId; + }, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +paymentSrv.setTestingUIService( + DummyUIService.QueryInterface(Ci.nsIPaymentUIService) +); + +addMessageListener("start-test", function (testName) { + DummyUIService.testName = testName; + sendAsyncMessage("start-test-complete"); +}); + +addMessageListener("finish-test", function () { + DummyUIService.testName = ""; + sendAsyncMessage("finish-test-complete"); +}); + +addMessageListener("reject-retry", function () { + DummyUIService.rejectRetry = true; + sendAsyncMessage("reject-retry-complete"); +}); + +addMessageListener("teardown", function () { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/ShippingOptionsChromeScript.js b/dom/payments/test/ShippingOptionsChromeScript.js new file mode 100644 index 0000000000..d3f23ab391 --- /dev/null +++ b/dom/payments/test/ShippingOptionsChromeScript.js @@ -0,0 +1,120 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +function emitTestFail(message) { + sendAsyncMessage("test-fail", message); +} +function emitTestPass(message) { + sendAsyncMessage("test-pass", message); +} + +let expectedRequestOption = null; +let expectedUpdatedOption = null; +let changeShippingOption = null; + +function showResponse(requestId) { + const showResponseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + showResponseData.initData({}); + const showActionResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showActionResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "testing-payment-method", // payment method + showResponseData, // payment method data + "Bill A. Pacheco", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showActionResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function showRequest(requestId) { + let request = paymentSrv.getPaymentRequestById(requestId); + const message = + "request.shippingOption should be " + + expectedRequestOption + + " when calling show(), but got " + + request.shippingOption + + "."; + if (request.shippingOption != expectedRequestOption) { + emitTestFail(message); + } else { + emitTestPass(message); + } + if (changeShippingOption) { + paymentSrv.changeShippingOption(requestId, changeShippingOption); + } else { + showResponse(requestId); + } +} + +function updateRequest(requestId) { + let request = paymentSrv.getPaymentRequestById(requestId); + const message = + "request.shippingOption should be " + + expectedUpdatedOption + + " when calling updateWith(), but got " + + request.shippingOption + + "."; + if (request.shippingOption != expectedUpdatedOption) { + emitTestFail(message); + } else { + emitTestPass(message); + } + showResponse(requestId); +} + +const TestingUIService = { + showPayment: showRequest, + abortPayment(requestId) {}, + completePayment(requestId) { + let request = paymentSrv.getPaymentRequestById(requestId); + let completeResponse = Cc[ + "@mozilla.org/dom/payments/payment-complete-action-response;1" + ].createInstance(Ci.nsIPaymentCompleteActionResponse); + completeResponse.init( + requestId, + Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED + ); + paymentSrv.respondPayment( + completeResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); + }, + updatePayment: updateRequest, + closePayment(requestId) {}, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +paymentSrv.setTestingUIService( + TestingUIService.QueryInterface(Ci.nsIPaymentUIService) +); + +addMessageListener("set-expected-results", function (results) { + expectedRequestOption = results.requestResult; + expectedUpdatedOption = results.responseResult; + changeShippingOption = results.changeOptionResult; +}); + +addMessageListener("teardown", function () { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/ShowPaymentChromeScript.js b/dom/payments/test/ShowPaymentChromeScript.js new file mode 100644 index 0000000000..c8848ab622 --- /dev/null +++ b/dom/payments/test/ShowPaymentChromeScript.js @@ -0,0 +1,393 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +function emitTestFail(message) { + sendAsyncMessage("test-fail", `${DummyUIService.testName}: ${message}`); +} +function emitTestPass(message) { + sendAsyncMessage("test-pass", `${DummyUIService.testName}: ${message}`); +} + +const shippingAddress = Cc[ + "@mozilla.org/dom/payments/payment-address;1" +].createInstance(Ci.nsIPaymentAddress); +const addressLine = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray +); +const address = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString +); +address.data = "Easton Ave"; +addressLine.appendElement(address); +shippingAddress.init( + "USA", // country + addressLine, // address line + "CA", // region + "CA", // region code + "San Bruno", // city + "Test locality", // dependent locality + "94066", // postal code + "123456", // sorting code + "Testing Org", // organization + "Bill A. Pacheco", // recipient + "+1-434-441-3879" +); // phone + +function acceptShow(requestId) { + const responseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + responseData.initData({ + paymentToken: "6880281f-0df3-4b8e-916f-66575e2457c1", + }); + let showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "testing-payment-method", // payment method + responseData, // payment method data + "Bill A. Pacheco", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function rejectShow(requestId) { + const responseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + responseData.initData({}); + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_REJECTED, + "", // payment method + responseData, // payment method data + "", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function updateShow(requestId) { + if (DummyUIService.expectedUpdateAction == "updateaddress") { + paymentSrv.changeShippingAddress(requestId, shippingAddress); + } else if ( + DummyUIService.expectedUpdateAction == "accept" || + DummyUIService.expectedUpdateAction == "error" + ) { + paymentSrv.changeShippingOption(requestId, "FastShipping"); + } else { + emitTestFail( + "Unknown expected update action: " + DummyUIService.expectedUpdateAction + ); + } +} + +function showRequest(requestId) { + const request = paymentSrv.getPaymentRequestById(requestId); + if (request.completeStatus == "initial") { + return; + } + if (DummyUIService.expectedShowAction == "accept") { + acceptShow(requestId); + } else if (DummyUIService.expectedShowAction == "reject") { + rejectShow(requestId); + } else if (DummyUIService.expectedShowAction == "update") { + updateShow(requestId); + } else { + emitTestFail( + "Unknown expected show action: " + DummyUIService.expectedShowAction + ); + } +} + +function abortRequest(requestId) { + let abortResponse = Cc[ + "@mozilla.org/dom/payments/payment-abort-action-response;1" + ].createInstance(Ci.nsIPaymentAbortActionResponse); + abortResponse.init(requestId, Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED); + paymentSrv.respondPayment(abortResponse); +} + +function completeRequest(requestId) { + let request = paymentSrv.getPaymentRequestById(requestId); + if (DummyUIService.expectedCompleteStatus) { + if (request.completeStatus == DummyUIService.expectedCompleteStatus) { + emitTestPass( + "request.completeStatus matches expectation of " + + DummyUIService.expectedCompleteStatus + ); + } else { + emitTestFail( + "request.completeStatus incorrect. Expected " + + DummyUIService.expectedCompleteStatus + + ", got " + + request.completeStatus + ); + } + } + let completeResponse = Cc[ + "@mozilla.org/dom/payments/payment-complete-action-response;1" + ].createInstance(Ci.nsIPaymentCompleteActionResponse); + completeResponse.init( + requestId, + Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED + ); + paymentSrv.respondPayment( + completeResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function updateRequest(requestId) { + let request = paymentSrv.getPaymentRequestById(requestId); + if (request.completeStatus !== "") { + emitTestFail( + "request.completeStatus should be empty, but got '" + + request.completeStatus + + "'." + ); + } + if (DummyUIService.expectedUpdateAction == "accept") { + if (request.paymentDetails.error != "") { + emitTestFail( + "updatedDetails should not have errors(" + + request.paymentDetails.error + + ")." + ); + } + const shippingOptions = request.paymentDetails.shippingOptions; + let shippingOption = shippingOptions.queryElementAt( + 0, + Ci.nsIPaymentShippingOption + ); + if (shippingOption.selected) { + emitTestFail(shippingOption.label + " should not be selected."); + } + shippingOption = shippingOptions.queryElementAt( + 1, + Ci.nsIPaymentShippingOption + ); + if (!shippingOption.selected) { + emitTestFail(shippingOption.label + " should be selected."); + } + acceptShow(requestId); + } else if (DummyUIService.expectedUpdateAction == "error") { + if (request.paymentDetails.error != "Update with Error") { + emitTestFail( + "details.error should be 'Update with Error', but got " + + request.paymentDetails.error + + "." + ); + } + rejectShow(requestId); + } else if (DummyUIService.expectedUpdateAction == "updateaddress") { + if (request.paymentDetails.error != "") { + emitTestFail( + "updatedDetails should not have errors(" + + request.paymentDetails.error + + ")." + ); + } + DummyUIService.expectedUpdateAction = "accept"; + paymentSrv.changeShippingOption(requestId, "FastShipping"); + } else { + emitTestFail( + "Unknown expected update aciton: " + DummyUIService.expectedUpdateAction + ); + } +} + +const DummyUIService = { + testName: "", + expectedCompleteStatus: null, + expectedShowAction: "accept", + expectedUpdateAction: "accept", + showPayment: showRequest, + abortPayment: abortRequest, + completePayment: completeRequest, + updatePayment: updateRequest, + closePayment(requestId) {}, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +paymentSrv.setTestingUIService( + DummyUIService.QueryInterface(Ci.nsIPaymentUIService) +); + +function testShowResponseInit() { + const showResponseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + try { + showResponseData.initData(null); + emitTestFail( + "nsIGeneralResponseData can not be initialized with null object." + ); + } catch (e) { + if (e.name != "NS_ERROR_FAILURE") { + emitTestFail( + "Expected 'NS_ERROR_FAILURE' when initializing nsIGeneralResponseData with null object, but got " + + e.name + + "." + ); + } + emitTestPass( + "Get expected result for initializing nsIGeneralResponseData with null object" + ); + } + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + try { + showResponse.init( + "test request id", + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "testing-payment-method", // payment method + showResponseData, // payment method data + "Bill A. Pacheco", // payer name + "", // payer email + "" + ); // payer phone + emitTestPass( + "Get expected result for initializing response with accepted and empty data." + ); + } catch (e) { + emitTestFail( + "Unexpected error " + + e.name + + " when initializing response with accepted and empty data." + ); + } + + try { + showResponse.init( + "test request id", + Ci.nsIPaymentActionResponse.PAYMENT_REJECTED, + "testing-payment-method", + null, + "Bill A. Pacheco", + "", + "" + ); + emitTestPass( + "Get expected result for initializing response with rejected and null data." + ); + } catch (e) { + emitTestFail( + "Unexpected error " + + e.name + + " when initializing response with rejected and null data." + ); + } + + try { + showResponse.init( + "test request id", + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "testing-payment-method", + null, + "Bill A. Pacheco", + "", + "" + ); + emitTestFail( + "nsIPaymentShowActionResponse can not be initialized with accpeted and null data." + ); + } catch (e) { + if (e.name != "NS_ERROR_ILLEGAL_VALUE") { + emitTestFail( + "Expected 'NS_ERROR_ILLEGAL_VALUE', but got " + e.name + "." + ); + } + emitTestPass( + "Get expected result for initializing response with accepted and null data." + ); + } + sendAsyncMessage("test-show-response-init-complete"); +} + +addMessageListener("set-simple-ui-service", function (testName) { + DummyUIService.testName = testName; + DummyUIService.expectedCompleteStatus = null; + DummyUIService.expectedShowAction = "accept"; + DummyUIService.expectedUpdateAction = "accept"; + sendAsyncMessage("set-simple-ui-service-complete"); +}); + +addMessageListener("set-normal-ui-service", function (testName) { + DummyUIService.testName = testName; + DummyUIService.expectedCompleteStatus = null; + DummyUIService.expectedShowAction = "update"; + DummyUIService.expectedUpdateAction = "updateaddress"; + sendAsyncMessage("set-normal-ui-service-complete"); +}); + +addMessageListener("set-reject-ui-service", function (testName) { + DummyUIService.testName = testName; + DummyUIService.expectedCompleteStatus = null; + DummyUIService.expectedShowAction = "reject"; + DummyUIService.expectedUpdateAction = "error"; + sendAsyncMessage("set-reject-ui-service-complete"); +}); + +addMessageListener("set-update-with-ui-service", function (testName) { + DummyUIService.testName = testName; + DummyUIService.expectedCompleteStatus = null; + DummyUIService.expectedShowAction = "update"; + DummyUIService.expectedUpdateAction = "accept"; + sendAsyncMessage("set-update-with-ui-service-complete"); +}); + +addMessageListener("set-update-with-error-ui-service", function (testName) { + DummyUIService.testName = testName; + DummyUIService.expectedCompleteStatus = null; + DummyUIService.expectedShowAction = "update"; + DummyUIService.expectedUpdateAction = "error"; + sendAsyncMessage("set-update-with-error-ui-service-complete"); +}); + +addMessageListener("test-show-response-init", testShowResponseInit); + +addMessageListener("set-complete-status-success", function () { + DummyUIService.expectedCompleteStatus = "success"; + sendAsyncMessage("set-complete-status-success-complete"); +}); + +addMessageListener("set-complete-status-fail", function () { + DummyUIService.expectedCompleteStatus = "fail"; + sendAsyncMessage("set-complete-status-fail-complete"); +}); + +addMessageListener("set-complete-status-unknown", function () { + DummyUIService.expectedCompleteStatus = "unknown"; + sendAsyncMessage("set-complete-status-unknown-complete"); +}); + +addMessageListener("teardown", function () { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/UpdateErrorsChromeScript.js b/dom/payments/test/UpdateErrorsChromeScript.js new file mode 100644 index 0000000000..ac288dbc15 --- /dev/null +++ b/dom/payments/test/UpdateErrorsChromeScript.js @@ -0,0 +1,214 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" +].getService(Ci.nsIPaymentRequestService); + +function emitTestFail(message) { + sendAsyncMessage("test-fail", message); +} +function emitTestPass(message) { + sendAsyncMessage("test-pass", message); +} + +const shippingAddress = Cc[ + "@mozilla.org/dom/payments/payment-address;1" +].createInstance(Ci.nsIPaymentAddress); +const addressLine = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray +); +const address = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString +); +address.data = "Easton Ave"; +addressLine.appendElement(address); +shippingAddress.init( + "USA", // country + addressLine, // address line + "CA", // region + "CA", // region code + "San Bruno", // city + "Test locality", // dependent locality + "94066", // postal code + "123456", // sorting code + "Testing Org", // organization + "Bill A. Pacheco", // recipient + "+1-434-441-3879" +); // phone + +function acceptShow(requestId) { + const responseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + responseData.initData({ + paymentToken: "6880281f-0df3-4b8e-916f-66575e2457c1", + }); + let showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED, + "testing-payment-method", // payment method + responseData, // payment method data + "Bill A. Pacheco", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function rejectShow(requestId) { + const responseData = Cc[ + "@mozilla.org/dom/payments/general-response-data;1" + ].createInstance(Ci.nsIGeneralResponseData); + responseData.initData({}); + const showResponse = Cc[ + "@mozilla.org/dom/payments/payment-show-action-response;1" + ].createInstance(Ci.nsIPaymentShowActionResponse); + showResponse.init( + requestId, + Ci.nsIPaymentActionResponse.PAYMENT_REJECTED, + "", // payment method + responseData, // payment method data + "", // payer name + "", // payer email + "" + ); // payer phone + paymentSrv.respondPayment( + showResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function updateShow(requestId) { + paymentSrv.changeShippingAddress(requestId, shippingAddress); +} + +function showRequest(requestId) { + updateShow(requestId); +} + +function abortRequest(requestId) { + let abortResponse = Cc[ + "@mozilla.org/dom/payments/payment-abort-action-response;1" + ].createInstance(Ci.nsIPaymentAbortActionResponse); + abortResponse.init(requestId, Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED); + paymentSrv.respondPayment(abortResponse); +} + +function completeRequest(requestId) { + let payRequest = paymentSrv.getPaymentRequestById(requestId); + let completeResponse = Cc[ + "@mozilla.org/dom/payments/payment-complete-action-response;1" + ].createInstance(Ci.nsIPaymentCompleteActionResponse); + completeResponse.init( + requestId, + Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED + ); + paymentSrv.respondPayment( + completeResponse.QueryInterface(Ci.nsIPaymentActionResponse) + ); +} + +function checkAddressErrors(errors) { + if (!errors) { + emitTestFail("Expect non-null shippingAddressErrors, but got null."); + } + if (errors.addressLine != "addressLine error") { + emitTestFail( + "Expect shippingAddressErrors.addressLine as 'addressLine error', but got" + + errors.addressLine + ); + } + if (errors.city != "city error") { + emitTestFail( + "Expect shippingAddressErrors.city as 'city error', but got" + errors.city + ); + } + if (errors.dependentLocality != "dependentLocality error") { + emitTestFail( + "Expect shippingAddressErrors.dependentLocality as 'dependentLocality error', but got" + + errors.dependentLocality + ); + } + if (errors.organization != "organization error") { + emitTestFail( + "Expect shippingAddressErrors.organization as 'organization error', but got" + + errors.organization + ); + } + if (errors.phone != "phone error") { + emitTestFail( + "Expect shippingAddressErrors.phone as 'phone error', but got" + + errors.phone + ); + } + if (errors.postalCode != "postalCode error") { + emitTestFail( + "Expect shippingAddressErrors.postalCode as 'postalCode error', but got" + + errors.postalCode + ); + } + if (errors.recipient != "recipient error") { + emitTestFail( + "Expect shippingAddressErrors.recipient as 'recipient error', but got" + + errors.recipient + ); + } + if (errors.region != "region error") { + emitTestFail( + "Expect shippingAddressErrors.region as 'region error', but got" + + errors.region + ); + } + if (errors.regionCode != "regionCode error") { + emitTestFail( + "Expect shippingAddressErrors.regionCode as 'regionCode error', but got" + + errors.region + ); + } + if (errors.sortingCode != "sortingCode error") { + emitTestFail( + "Expect shippingAddressErrors.sortingCode as 'sortingCode error', but got" + + errors.sortingCode + ); + } +} + +function updateRequest(requestId) { + let request = paymentSrv.getPaymentRequestById(requestId); + const addressErrors = request.paymentDetails.shippingAddressErrors; + const payerErrors = request.paymentDetails.payerErrors; + checkAddressErrors(addressErrors); + rejectShow(requestId); +} + +const DummyUIService = { + showPayment: showRequest, + abortPayment: abortRequest, + completePayment: completeRequest, + updatePayment: updateRequest, + closePayment(requestId) {}, + QueryInterface: ChromeUtils.generateQI(["nsIPaymentUIService"]), +}; + +paymentSrv.setTestingUIService( + DummyUIService.QueryInterface(Ci.nsIPaymentUIService) +); + +addMessageListener("teardown", function () { + paymentSrv.setTestingUIService(null); + sendAsyncMessage("teardown-complete"); +}); diff --git a/dom/payments/test/blank_page.html b/dom/payments/test/blank_page.html new file mode 100644 index 0000000000..7323b00a28 --- /dev/null +++ b/dom/payments/test/blank_page.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> + <head> + <title>Payment Request Testing</title> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> + </head> + <body> + <h1>blank page.html</h1> + <script type="text/javascript"> + if(window.parent) { + window.parent.postMessage("successful", '*'); + } + </script> + </body> +</html> diff --git a/dom/payments/test/browser.toml b/dom/payments/test/browser.toml new file mode 100644 index 0000000000..6ea321d4f9 --- /dev/null +++ b/dom/payments/test/browser.toml @@ -0,0 +1,9 @@ +[DEFAULT] +prefs = ["dom.payments.request.enabled=true"] +skip-if = ["true"] # we don't ship webpayments right now bug 1514425 +support-files = [ + "head.js", + "simple_payment_request.html", +] + +["browser_payment_in_different_tabs.js"] diff --git a/dom/payments/test/browser_payment_in_different_tabs.js b/dom/payments/test/browser_payment_in_different_tabs.js new file mode 100644 index 0000000000..c811d32dd2 --- /dev/null +++ b/dom/payments/test/browser_payment_in_different_tabs.js @@ -0,0 +1,37 @@ +"use strict"; + +// kTestRoot is from head.js +const kTestPage = kTestRoot + "simple_payment_request.html"; +const TABS_TO_OPEN = 5; +add_task(async () => { + Services.prefs.setBoolPref("dom.payments.request.enabled", true); + const tabs = []; + const options = { + gBrowser: Services.wm.getMostRecentWindow("navigator:browser").gBrowser, + url: kTestPage, + }; + for (let i = 0; i < TABS_TO_OPEN; i++) { + const tab = await BrowserTestUtils.openNewForegroundTab(options); + tabs.push(tab); + } + const paymentSrv = Cc[ + "@mozilla.org/dom/payments/payment-request-service;1" + ].getService(Ci.nsIPaymentRequestService); + const paymentEnum = paymentSrv.enumerate(); + ok( + paymentEnum.hasMoreElements(), + "PaymentRequestService should have at least one payment request." + ); + const payments = new Set(); + for (let payment of paymentEnum) { + ok(payment, "Fail to get existing payment request."); + checkSimplePayment(payment); + payments.add(payment); + } + is(payments.size, TABS_TO_OPEN, `Should be ${TABS_TO_OPEN} unique objects.`); + for (const tab of tabs) { + await TestUtils.waitForTick(); + BrowserTestUtils.removeTab(tab); + } + Services.prefs.setBoolPref("dom.payments.request.enabled", false); +}); diff --git a/dom/payments/test/bug1478740.html b/dom/payments/test/bug1478740.html new file mode 100644 index 0000000000..ddcc04bbb0 --- /dev/null +++ b/dom/payments/test/bug1478740.html @@ -0,0 +1,44 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Payment Request Testing</title> +<script> +const methods = [ + { + supportedMethods: "basic-card", + }, +]; +const details = { + id: "simple details", + total: { + label: "Donation", + amount: { currency: "USD", value: "55.00" }, + }, +}; +const updatedDetails = { + id: "simple details", + total: { + label: "Donation", + amount: { currency: "USD", value: "55.00" }, + }, + error: "", +}; + +window.onmessage = async ({ data: action }) => { + let msg = "successful"; + switch (action) { + case "Show Payment": + try { + let request = new PaymentRequest(methods, details); + let responsePromise = await request.show(); + } catch (err) { + msg = err.name; + } + window.parent.postMessage(msg, "*") + break; + default: + window.parent.postMessage(`fail - unknown postmessage action: ${action}`, "*"); + } +}; + +window.parent.postMessage("successful", "*"); +</script> diff --git a/dom/payments/test/echo_payment_request.html b/dom/payments/test/echo_payment_request.html new file mode 100644 index 0000000000..b1bf3da90c --- /dev/null +++ b/dom/payments/test/echo_payment_request.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html> + <head> + <title>Payment Request Testing</title> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> +</head> +<body> + <script> + window.onmessage = (e) => { + const paymentArgs = [[{supportedMethods: 'basic-card'}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}}]; + + if (e.data === 'new PaymentRequest') { + try { + new PaymentRequest(...paymentArgs); + if (window.parent) { + window.parent.postMessage("successful", '*'); + } + } catch(ex) { + if (window.parent) { + window.parent.postMessage(ex.name, '*'); + } + } + } else if (e.data === 'new PaymentRequest in a new iframe') { + var ifrr = document.createElement('iframe'); + ifrr.allow = "payment"; + ifrr.src = "https://example.com/tests/dom/payments/test/simple_payment_request.html"; + document.body.appendChild(ifrr); + } else { + if (window.parent) { + window.parent.postMessage(e.data, '*'); + } + } + } + </script> +</body> +</html> diff --git a/dom/payments/test/head.js b/dom/payments/test/head.js new file mode 100644 index 0000000000..3a377e09ae --- /dev/null +++ b/dom/payments/test/head.js @@ -0,0 +1,127 @@ +const kTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +function checkSimplePayment(aSimplePayment) { + // checking the passed PaymentMethods parameter + is( + aSimplePayment.paymentMethods.length, + 1, + "paymentMethods' length should be 1." + ); + + const methodData = aSimplePayment.paymentMethods.queryElementAt( + 0, + Ci.nsIPaymentMethodData + ); + ok(methodData, "Fail to get payment methodData."); + is( + methodData.supportedMethods, + "basic-card", + "supported method should be 'basic-card'." + ); + ok(!methodData.data, "methodData.data should not exist."); + + // checking the passed PaymentDetails parameter + const details = aSimplePayment.paymentDetails; + is(details.id, "simple details", "details.id should be 'simple details'."); + is( + details.totalItem.label, + "Donation", + "total item's label should be 'Donation'." + ); + is( + details.totalItem.amount.currency, + "USD", + "total item's currency should be 'USD'." + ); + is( + details.totalItem.amount.value, + "55.00", + "total item's value should be '55.00'." + ); + + is( + details.displayItems.length, + 0, + "details.displayItems should be a zero length array." + ); + is( + details.modifiers.length, + 0, + "details.modifiers should be a zero length array." + ); + is( + details.shippingOptions.length, + 0, + "details.shippingOptions should be a zero length array." + ); + + // checking the default generated PaymentOptions parameter + const paymentOptions = aSimplePayment.paymentOptions; + ok(!paymentOptions.requestPayerName, "payerName option should be false"); + ok(!paymentOptions.requestPayerEmail, "payerEmail option should be false"); + ok(!paymentOptions.requestPayerPhone, "payerPhone option should be false"); + ok(!paymentOptions.requestShipping, "requestShipping option should be false"); + is( + paymentOptions.shippingType, + "shipping", + "shippingType option should be 'shipping'" + ); +} + +function checkDupShippingOptionsPayment(aPayment) { + // checking the passed PaymentMethods parameter + is(aPayment.paymentMethods.length, 1, "paymentMethods' length should be 1."); + + const methodData = aPayment.paymentMethods.queryElementAt( + 0, + Ci.nsIPaymentMethodData + ); + ok(methodData, "Fail to get payment methodData."); + is( + methodData.supportedMethods, + "basic-card", + "methodData.supportedMethod name should be 'basic-card'." + ); + ok(!methodData.data, "methodData.data should not exist."); + + // checking the passed PaymentDetails parameter + const details = aPayment.paymentDetails; + is( + details.id, + "duplicate shipping options details", + "details.id should be 'duplicate shipping options details'." + ); + is( + details.totalItem.label, + "Donation", + "total item's label should be 'Donation'." + ); + is( + details.totalItem.amount.currency, + "USD", + "total item's currency should be 'USD'." + ); + is( + details.totalItem.amount.value, + "55.00", + "total item's value should be '55.00'." + ); + + const shippingOptions = details.shippingOptions; + is(shippingOptions.length, 0, "shippingOptions' length should be 0."); + + // checking the passed PaymentOptions parameter + const paymentOptions = aPayment.paymentOptions; + ok(paymentOptions.requestPayerName, "payerName option should be true"); + ok(paymentOptions.requestPayerEmail, "payerEmail option should be true"); + ok(paymentOptions.requestPayerPhone, "payerPhone option should be true"); + ok(paymentOptions.requestShipping, "requestShipping option should be true"); + is( + paymentOptions.shippingType, + "shipping", + "shippingType option should be 'shipping'" + ); +} diff --git a/dom/payments/test/mochitest.toml b/dom/payments/test/mochitest.toml new file mode 100644 index 0000000000..e6033dcf2b --- /dev/null +++ b/dom/payments/test/mochitest.toml @@ -0,0 +1,73 @@ +[DEFAULT] +prefs = ["dom.payments.request.enabled=true"] +# Android crashes on nearly all tests, bug 1525959 +skip-if = ["true"] # we don't ship webpayments right now bug 1514425 +scheme = "https" +support-files = [ + "blank_page.html", + "bug1478740.html", + "simple_payment_request.html", + "echo_payment_request.html", + "BasiccardChromeScript.js", + "Bug1478740ChromeScript.js", + "BasicCardErrorsChromeScript.js", + "Bug1490698ChromeScript.js", + "ClosePaymentChromeScript.js", + "ConstructorChromeScript.js", + "CurrencyAmountValidationChromeScript.js", + "DefaultData.js", + "GeneralChromeScript.js", + "PayerDetailsChromeScript.js", + "PMIValidationChromeScript.js", + "RequestShippingChromeScript.js", + "RetryPaymentChromeScript.js", + "ShippingOptionsChromeScript.js", + "ShowPaymentChromeScript.js", + "UpdateErrorsChromeScript.js", +] + +["test_abortPayment.html"] +run-if = ["nightly_build"] # Bug 1390018: Depends on the Nightly-only UI service +skip-if = ["debug"] # Bug 1507251 - Leak + +["test_basiccard.html"] + +["test_basiccarderrors.html"] + +["test_block_none10s.html"] +skip-if = ["true"] # Bug 1408250: Don't expose PaymentRequest Constructor in non-e10s + +["test_bug1478740.html"] + +["test_bug1490698.html"] + +["test_canMakePayment.html"] +run-if = ["nightly_build"] # Bug 1390737: Depends on the Nightly-only UI service +skip-if = ["debug"] # Bug 1507251 - Leak + +["test_closePayment.html"] + +["test_constructor.html"] +skip-if = [ + "os == 'linux'", + "os == 'mac'", + "os == 'win' && os_version == '10.0'", # Bug 1514425 +] + +["test_currency_amount_validation.html"] + +["test_payerDetails.html"] + +["test_payment-request-in-iframe.html"] + +["test_pmi_validation.html"] + +["test_requestShipping.html"] + +["test_retryPayment.html"] + +["test_shippingOptions.html"] + +["test_showPayment.html"] + +["test_update_errors.html"] diff --git a/dom/payments/test/simple_payment_request.html b/dom/payments/test/simple_payment_request.html new file mode 100644 index 0000000000..b532ba6101 --- /dev/null +++ b/dom/payments/test/simple_payment_request.html @@ -0,0 +1,81 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Payment Request Testing</title> +<script> +const methods = [ + { + supportedMethods: "basic-card", + }, +]; +const details = { + id: "simple details", + total: { + label: "Donation", + amount: { currency: "USD", value: "55.00" }, + }, +}; +const updatedDetails = { + id: "simple details", + total: { + label: "Donation", + amount: { currency: "USD", value: "55.00" }, + }, + error: "", +}; + +let request; +let shippingChangedEvent; + +let msg = "successful"; +try { + request = new PaymentRequest(methods, details); + request.onshippingoptionchange = (event) => { + shippingChangedEvent = event; + window.parent.postMessage("successful", "*"); + }; + request.onshippingaddresschange = (event) => { + shippingChangedEvent = event; + window.parent.postMessage("successful", "*"); + }; + +} catch (err) { + msg = err.name; +} +window.parent.postMessage(msg, "*"); + + +if (request) { + window.onmessage = async ({ data: action }) => { + msg = "successful"; + switch (action) { + case "show PaymentRequest": + const responsePromise = request.show(); + window.parent.postMessage(msg, "*"); + try { + await responsePromise; + } catch (err) { + if (err.name !== "AbortError") { + msg = err.name; + } + } + window.parent.postMessage(msg, "*") + break; + case "updateWith PaymentRequest": + if (shippingChangedEvent) { + try { + shippingChangedEvent.updateWith(updatedDetails); + } catch(err) { + if (err.name !== "InvalidStateError") { + msg = err.name; + } + } + window.parent.postMessage(msg, "*"); + shippingChangedEvent = undefined; + } + break; + default: + window.parent.postMessage(`fail - unknown postmessage action: ${action}`, "*"); + } + }; +} +</script> diff --git a/dom/payments/test/test_abortPayment.html b/dom/payments/test/test_abortPayment.html new file mode 100644 index 0000000000..64285914aa --- /dev/null +++ b/dom/payments/test/test_abortPayment.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1345367 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1345367</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + var gUrl = SimpleTest.getTestFileURL('GeneralChromeScript.js'); + var gScript = SpecialPowers.loadChromeScript(gUrl); + + const defaultMethods = [{ + supportedMethods: "basic-card", + }]; + const defaultDetails = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00" + } + }, + }; + + function testBeforeShow() { + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(defaultMethods, defaultDetails); + payRequest.abort().then((result) => { + ok(false, "Should throw 'InvalidStateError', but got resolved."); + resolve(); + }).catch((err) => { + is(err.name, "InvalidStateError", + "Expected 'InvalidStateError', but got '" + err.name + "'"); + resolve(); + }); + }); + } + + function testAfterShow() { + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(defaultMethods, defaultDetails); + const acceptPromise = payRequest.show(); + payRequest.abort().then((abortResult) => { + is(abortResult, undefined, "Should be resolved with undefined."); + resolve(); + }).catch( (err) => { + ok(false, "Expected no error, but got '" + err.name + "'."); + resolve(); + }).finally(handler.destruct); + }); + } + + function teardown() { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.destroy(); + SimpleTest.finish(); + }); + gScript.sendAsyncMessage("teardown"); + } + + function runTests() { + testBeforeShow() + .then(testAfterShow) + .then(teardown) + .catch( e => { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + }); + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1345367">Mozilla Bug 1345367</a> +</pre> +</body> +</html> diff --git a/dom/payments/test/test_basiccard.html b/dom/payments/test/test_basiccard.html new file mode 100644 index 0000000000..e8d30fbd06 --- /dev/null +++ b/dom/payments/test/test_basiccard.html @@ -0,0 +1,371 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1375345 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1375345</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + var gUrl = SimpleTest.getTestFileURL('BasiccardChromeScript.js'); + var gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + function testPassHandler(message) { + ok(true, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + gScript.addMessageListener("test-pass", testPassHandler); + + async function requestChromeAction(action, params) { + await new Promise(resolve => { + gScript.addMessageListener(`${action}-complete`, function completeListener() { + gScript.removeMessageListener(`${action}-complete`, completeListener); + resolve(); + }); + gScript.sendAsyncMessage(action, params); + }); + } + + const errorNetworksMethods = [{ + supportedMethods: "basic-card", + data: { + supportedNetworks: ["myNetwork"], + }, + }]; + + const nullDataMethods = [{ + supportedMethods: "basic-card", + }]; + + const emptyDataMethods = [{ + supportedMethods: "basic-card", + data: {}, + }]; + + const unconvertableDataMethods = [{ + supportedMethods: "basic-card", + data: "unconvertable data", + }]; + + const defaultMethods = [{ + supportedMethods: "basic-card", + data: { + supportedNetworks: ["unionpay", "visa", "mastercard", "amex", "discover", + "diners", "jcb", "mir", + ], + }, + }]; + const defaultDetails = { + id: "test payment", + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00" + } + }, + shippingOptions: [ + { + id: "NormalShipping", + label: "NormalShipping", + amount: { + currency: "USD", + value: "10.00" + }, + selected: true, + }, + { + id: "FastShipping", + label: "FastShipping", + amount: { + currency: "USD", + value: "30.00" + }, + selected: false, + }, + ], + }; + + const updateDetails = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00" + } + }, + shippingOptions: [ + { + id: "NormalShipping", + label: "NormalShipping", + amount: { + currency: "USD", + value: "10.00" + }, + selected: true, + }, + { + id: "FastShipping", + label: "FastShipping", + amount: { + currency: "USD", + value: "30.00" + }, + selected: false, + }, + ], + error: "", + }; + + const defaultOptions = { + requestPayerName: true, + requestPayerEmail: false, + requestPayerPhone: false, + requestShipping: true, + shippingType: "shipping" + }; + + async function testBasicCardRequestWithErrorNetworks() { + const testName = "testBasicCardRequestWithErrorNetworks"; + try { + const request = new PaymentRequest(errorNetworksMethods, defaultDetails, defaultOptions); + ok(false, `${testName}: Expected 'TypeError', but got success construction.`); + } catch (e) { + is(e.name, "TypeError", `${testName}: Expected TypeError, but got ${e.name}`); + } + } + + async function testBasicCardRequestWithUnconvertableData() { + const testName = "testBasicCardRequestWithUnconvertableData"; + try { + const request = new PaymentRequest(unconvertableDataMethods, defaultDetails, defaultOptions); + ok(false, `${testName}: Expected 'TypeError', but got success construction.`); + } catch (e) { + is(e.name, "TypeError", `${testName}: Expected TypeError, but got ${e.name}`); + } + } + + async function testBasicCardRequestWithNullData() { + const testName = "testBasicCardRequestWithNullData"; + try { + const request = new PaymentRequest(nullDataMethods, defaultDetails, defaultOptions); + ok(request, `${testName}: PaymentRequest should be constructed with null data BasicCardRequest.`); + } catch (e) { + ok(false, `${testName}: Unexpected error: ${e.name}`); + } + } + + async function testBasicCardRequestWithEmptyData() { + const testName = "testBasicCardRequestWithEmptyData"; + try { + const request = new PaymentRequest(emptyDataMethods, defaultDetails, defaultOptions); + ok(request, `${testName}: PaymentRequest should be constructed with empty data BasicCardRequest.`); + } catch (e) { + ok(false, `${testName}: Unexpected error: ${e.name}`); + } + } + + async function testCanMakePaymentWithBasicCardRequest() { + const testName = "testCanMakePaymentWithBasicCardRequest"; + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + try { + const result = await request.canMakePayment(); + ok(result, `${testName}: canMakePayment() should be resolved with true.`); + } catch (e) { + ok(false, `${testName}: Unexpected error: ${e.name}`); + } + } + + async function testBasicCardSimpleResponse() { + const testName = "testBasicCardSimpleResponse"; + await requestChromeAction("set-simple-ui-service", testName); + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + try { + const response = await request.show(); + ok(response.details, `${testName}: basiccard response should exists.`); + ok(!response.details.cardholderName, `${testName}: response.details.cardholderName should not exist.`); + is(response.details.cardNumber, "4916855166538720", + `${testName}: response.details.cardNumber should be '4916855166538720'.`); + ok(!response.details.expiryMonth, `${testName}: response.details.expiryMonth should not exist.`); + ok(!response.details.expiryYear, `${testName}: response.details.expiryYear should be '2024'.`); + ok(!response.details.cardSecurityCode, `${testName}: response.details.cardSecurityCode should not exist.`); + ok(!response.details.billingAddress, `${testName}: response.details.billingAddress should not exist.`); + await response.complete("success"); + } catch (e) { + ok(false, `${testName}: Unexpected error: ${e.name}`); + } + await handler.destruct(); + } + + async function testBasicCardDetailedResponse() { + const testName = "testBasicCardDetailedResponse"; + await requestChromeAction("set-detailed-ui-service", testName); + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + try { + const response = await request.show(); + ok(response.details, `${testName}: basiccard response should exists.`); + ok(response.details.cardholderName, `${testName}: response.details.cardholderName should not exist.`); + is(response.details.cardNumber, "4916855166538720", + `${testName}: response.details.cardNumber should be '4916855166538720'.`); + ok(response.details.expiryMonth, `${testName}: response.details.expiryMonth should not exist.`); + ok(response.details.expiryYear, `${testName}: response.details.expiryYear should be '2024'.`); + ok(response.details.cardSecurityCode, `${testName}: response.details.cardSecurityCode should not exist.`); + ok(response.details.billingAddress, `${testName}: response.details.billingAddress should not exist.`); + const billingAddress = response.details.billingAddress; + is(billingAddress.country, "USA", `${testName}: country should be 'USA'.`); + is(billingAddress.addressLine.length, 1, `${testName}: addressLine.length should be 1.`); + is(billingAddress.addressLine[0], "Easton Ave", `${testName}: addressLine[0] should be 'Easton Ave'.`); + is(billingAddress.region, "CA", `${testName}: region should be 'CA'.`); + is(billingAddress.regionCode, "CA", `${testName}: regionCode should be 'CA'.`); + is(billingAddress.city, "San Bruno", `${testName}: city should be 'San Bruno'.`); + is(billingAddress.dependentLocality, "", `${testName}: dependentLocality should be empty.`); + is(billingAddress.postalCode, "94066", `${testName}: postalCode should be '94066'.`); + is(billingAddress.sortingCode, "123456", `${testName}: sortingCode should be '123456'.`); + is(billingAddress.organization, "", `${testName}: organization should be empty.`); + is(billingAddress.recipient, "Bill A. Pacheco", `${testName}: recipient should be 'Bill A. Pacheco'.`); + is(billingAddress.phone, "+14344413879", `${testName}: phone should be '+14344413879'.`); + await response.complete("success"); + } catch (e) { + ok(false, `${testName}: Unexpected error: ${e.name}`); + } + await handler.destruct(); + } + + async function testSpecialAddressResponse() { + const testName = "testSpecialAddressResponse"; + await requestChromeAction("set-special-address-ui-service", testName); + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + try { + const response = await request.show(); + ok(response.details, `${testName}: BasiccardResponse should exist.`); + ok(response.details.billingAddress, + `${testName}: BasiccardResponse.billingAddress should exist.`); + is(response.details.billingAddress.addressLine[0], ":$%@&*", + `${testName}: AddressLine should be ':$%@&*'`); + await response.complete("success"); + } catch (e) { + ok(false, `${testName}: Unexpected error: ${e.name}`); + } + await handler.destruct(); + } + + async function testMethodChangeWithoutRequestBillingAddress() { + const testName = "testMethodChangeWithoutRequestBillingAddress"; + await requestChromeAction("method-change-to-basic-card", testName); + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + request.addEventListener("paymentmethodchange", async (event) => { + is(event.methodName, "basic-card", `${testName}: PaymentMethodChangeEvent.methodName should be 'basic-card'.`) + ok(event.methodDetails, `PaymentMethodChangeEvent.methodDetails should exist.`); + ok(!event.methodDetails.billingAddress, `PaymentMethodChangeEvent.methodDetails.billingAddres should not exist.`); + event.updateWith(updateDetails); + }); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + try { + const response = await request.show(); + await response.complete("success"); + } catch (error) { + ok(false, `${testName}: Unexpected error: ${error.name}`); + } + await handler.destruct(); + } + + async function testMethodChangeWithRequestBillingAddress() { + const testName = "testMethodChangeWithRequestBillingAddress"; + await requestChromeAction("method-change-to-basic-card", testName); + const options = { + requestPayerName: true, + requestBillingAddress: true, + requestShipping: true, + shippingType: "shipping", + }; + const request = new PaymentRequest(defaultMethods, defaultDetails, options); + request.addEventListener("paymentmethodchange", async (event) => { + is(event.methodName, "basic-card", `${testName}: PaymentMethodChangeEvent.methodName should be 'basic-card'.`) + ok(event.methodDetails, `PaymentMethodChangeEvent.methodDetails should exist.`); + const billingAddress = event.methodDetails.billingAddress; + is(billingAddress.country, "USA", `${testName}: country should be 'USA'.`); + is(billingAddress.addressLine.length, 1, `${testName}: addressLine.length should be 1.`); + is(billingAddress.addressLine[0], "Easton Ave", `${testName}: addressLine[0] should be 'Easton Ave'.`); + is(billingAddress.region, "CA", `${testName}: region should be 'CA'.`); + is(billingAddress.regionCode, "CA", `${testName}: regionCode should be 'CA'.`); + is(billingAddress.city, "San Bruno", `${testName}: city should be 'San Bruno'.`); + is(billingAddress.dependentLocality, "", `${testName}: dependentLocality should be empty.`); + is(billingAddress.postalCode, "94066", `${testName}: postalCode should be '94066'.`); + is(billingAddress.sortingCode, "123456", `${testName}: sortingCode should be '123456'.`); + is(billingAddress.organization, "", `${testName}: organization should be empty.`); + is(billingAddress.recipient, "Bill A. Pacheco", `${testName}: recipient should be 'Bill A. Pacheco'.`); + is(billingAddress.phone, "+14344413879", `${testName}: phone should be '+14344413879'.`); + event.updateWith(updateDetails); + }); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + try { + const response = await request.show(); + await response.complete("success"); + } catch (error) { + ok(false, `${testName}: Unexpected error: ${error.name}`); + } + await handler.destruct(); + } + + + async function testBasicCardErrorResponse() { + const testName = "testBasicCardErrorResponse"; + return requestChromeAction("error-response-test", testName); + } + + async function teardown() { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler) + gScript.destroy(); + SimpleTest.finish(); + }); + gScript.sendAsyncMessage("teardown"); + } + + async function runTests() { + try { + await testBasicCardRequestWithErrorNetworks(); + await testBasicCardRequestWithUnconvertableData(); + await testBasicCardRequestWithNullData(); + await testBasicCardRequestWithEmptyData(); + await testCanMakePaymentWithBasicCardRequest(); + await testBasicCardSimpleResponse(); + await testBasicCardDetailedResponse(); + await testSpecialAddressResponse(); + await testBasicCardErrorResponse(); + await testMethodChangeWithoutRequestBillingAddress(); + await testMethodChangeWithRequestBillingAddress() + await teardown(); + } catch (e) { + ok(false, `test_basiccard.html: Unexpected error: ${e.name}`); + SimpleTest.finish(); + }; + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1375345">Mozilla Bug 1375345</a> +</body> +</html> diff --git a/dom/payments/test/test_basiccarderrors.html b/dom/payments/test/test_basiccarderrors.html new file mode 100644 index 0000000000..f9ac76ae75 --- /dev/null +++ b/dom/payments/test/test_basiccarderrors.html @@ -0,0 +1,85 @@ +<!doctype html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1489968 +--> +<meta charset="utf-8"> +<title>Test for Bug 1489968</title> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="./DefaultData.js"></script> +<script> +SimpleTest.waitForExplicitFinish(); + +const gUrl = SimpleTest.getTestFileURL("BasicCardErrorsChromeScript.js"); +const gScript = SpecialPowers.loadChromeScript(gUrl); + +function sendOnce(message) { + return data => { + return new Promise(resolve => { + const doneMsg = `${message}-complete`; + gScript.addMessageListener(doneMsg, function listener() { + gScript.removeMessageListener(doneMsg, listener); + resolve(); + }); + gScript.sendAsyncMessage(message, data); + }); + }; +} +const sendTearDown = sendOnce("teardown"); + +async function teardown() { + await sendTearDown(); + gScript.destroy(); + SimpleTest.finish(); +} + +async function testBasicCardErrors() { + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput( + true + ); + const request = new PaymentRequest( + [{ supportedMethods: "basic-card" }], + defaultDetails + ); + const response = await request.show(); + // Smoke test the initial state + is(response.details.cardNumber, "4111111111111111", "Expected cardNumber to initially be 4111111111111111"); + // We send these up and have the chrome script echo them back to us. + const expected = { + cardholderName: "PASS", + cardNumber: "3566002020360505", + cardSecurityCode: "666", + expiryMonth: "02", + expiryYear: "2020", + }; + await response.retry({ paymentMethod: expected }); + // the values of the response would have been updated with the expected + for (const [member, expectedValue] of Object.entries(expected)) { + const actual = response.details[member]; + is( + actual, + expectedValue, + `Expected member ${member} to be "${expectedValue}, but got "${actual}"` + ); + } + await response.complete("success"); + handler.destruct(); +} + +async function runTests() { + try { + await testBasicCardErrors(); + } catch (err) { + ok(false, `Unexpected error: ${err} ${err.stack}.`); + } finally { + await teardown(); + } +} + +window.addEventListener("load", () => { + const prefs = [["dom.payments.request.enabled", true]]; + SpecialPowers.pushPrefEnv({ set: prefs }, runTests); +}); +</script> + +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1489968">Mozilla Bug 1489968</a> diff --git a/dom/payments/test/test_block_none10s.html b/dom/payments/test/test_block_none10s.html new file mode 100644 index 0000000000..b1d654f38c --- /dev/null +++ b/dom/payments/test/test_block_none10s.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test for Bug 1408250</title> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + + <script type="text/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + function testInNone10s() { + return new Promise((resolve,reject) => { + const supportedInstruments = [{ + supportedMethods: "basic-card", + }]; + const details = { + id: "simple details", + total: { + label: "Donation", + amount: { currency: "USD", value: "55.00" } + }, + }; + try { + const payRequest = new PaymentRequest(supportedInstruments, details); + ok(false, "Unexpected, new PaymentRequest() can not be used in non-e10s."); + } catch (err) { + ok(err.name, "ReferenceError", + "Expected ReferenceError when calling new PaymentRequest()"); + } + resolve(); + + }); + } + + function runTests() { + testInNone10s() + .then(SimpleTest.finish) + .catch( e => { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + }); + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + </script> + </head> + <body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1408250">Mozilla Bug 1408250</a> + </body> +</html> diff --git a/dom/payments/test/test_bug1478740.html b/dom/payments/test/test_bug1478740.html new file mode 100644 index 0000000000..e877face76 --- /dev/null +++ b/dom/payments/test/test_bug1478740.html @@ -0,0 +1,140 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1478740 +--> +<head> + <meta charset="utf-8"> + <title>Test for retry PaymentRequest</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="DefaultData.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + const gUrl = SimpleTest.getTestFileURL('Bug1478740ChromeScript.js'); + const gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + function testPassHandler(message) { + ok(true, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + gScript.addMessageListener("test-pass", testPassHandler); + + async function requestChromeAction(action, params) { + gScript.sendAsyncMessage(action, params); + await new Promise(resolve => { + gScript.addMessageListener(`${action}-complete`, function completeListener() { + gScript.removeMessageListener(`${action}-complete`, completeListener); + resolve(); + }); + }); + } + function unexpectedErrMsg(testName, errName, timing) { + return `${testName}: Unexpected error(${errName}) when ${timing} the PaymentRequest.`; + } + + async function testMultipleShows() { + const testName = "testMultipleShows"; + await requestChromeAction("start-test", testName); + let expectedResults = ["successful", + "successful", + "successful", + "AbortError", + "AbortError", + "AbortError"]; + let nextStatus = ["creating first page", + "creating second page", + "showing first payment", + "showing second payment", + "showing third payment", + "aborting first payment"]; + let currStatus = nextStatus.shift(); + let ifr1 = document.createElement('iframe'); + let ifr2 = document.createElement('iframe'); + + await new Promise(resolve => { + let listener = async function(event) { + let expected = expectedResults.shift(); + is(event.data, expected, + `${testName}: Expected '${expected}' when ${currStatus}, but got '${event.data}'`); + switch (currStatus) { + case "creating first page": + ifr2.src = "bug1478740.html"; + document.body.appendChild(ifr2); + break; + case "creating second page": + ifr1.contentWindow.postMessage("Show Payment", "*"); + break; + case "showing first payment": + ifr2.contentWindow.postMessage("Show Payment", "*"); + break; + case "showing second payment": + ifr2.contentWindow.postMessage("Show Payment", "*"); + break; + case "showing third payment": + await requestChromeAction("reject-payment"); + break; + case "aborting first payment": + window.removeEventListener("message", listener); + gScript.removeMessageListener("showing-payment", listener); + document.body.removeChild(ifr1); + document.body.removeChild(ifr2); + resolve(); + break; + default: + ok(false, `unknown status ${currStatus}`); + } + currStatus = nextStatus.shift(); + } + window.addEventListener("message", listener); + gScript.addMessageListener("showing-payment", listener); + ifr1.src = "bug1478740.html"; + document.body.appendChild(ifr1); + }); + await requestChromeAction("finish-test"); + } + + function teardown() { + return new Promise((resolve, reject) => { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler); + gScript.removeMessageListener("test-pass", testPassHandler); + gScript.destroy(); + SimpleTest.finish(); + resolve(); + }); + gScript.sendAsyncMessage("teardown"); + }); + } + + async function runTests() { + try { + await testMultipleShows(); + await teardown(); + } catch(e) { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + } + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ['dom.payments.request.user_interaction_required', false], + ] + }, runTests); + }); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1478740">Mozilla Bug 1478740</a> +</body> +</html> diff --git a/dom/payments/test/test_bug1490698.html b/dom/payments/test/test_bug1490698.html new file mode 100644 index 0000000000..e1126af770 --- /dev/null +++ b/dom/payments/test/test_bug1490698.html @@ -0,0 +1,119 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1490698 +--> +<head> + <meta charset="utf-8"> + <title>Test for retry PaymentRequest</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="DefaultData.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + const gUrl = SimpleTest.getTestFileURL('Bug1490698ChromeScript.js'); + const gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + function testPassHandler(message) { + ok(true, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + gScript.addMessageListener("test-pass", testPassHandler); + + async function requestChromeAction(action, params) { + gScript.sendAsyncMessage(action, params); + await new Promise(resolve => { + gScript.addMessageListener(`${action}-complete`, function completeListener() { + gScript.removeMessageListener(`${action}-complete`, completeListener); + resolve(); + }); + }); + } + function unexpectedErrMsg(testName, errName, timing) { + return `${testName}: Unexpected error(${errName}) when ${timing} the PaymentRequest.`; + } + + async function testInteractWithPaymentUnderWrongState() { + const testName = "testInteractWithPaymentUnderWrongState"; + await requestChromeAction("start-test", testName); + const payRequest = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + ok(payRequest, testName + ": failed to create PaymentRequest."); + if (!payRequest) { + await requestChromeAction("finish-test"); + return; + } + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + let payResponse; + try { + payResponse = await payRequest.show(); + info(`${testName}: Interact with payment when PaymentRequest is eClosed`); + await requestChromeAction("interact-with-payment"); + handler.destruct(); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.name, "showing")); + handler.destruct(); + await requestChromeAction("finish-test"); + return; + } + try { + await payResponse.complete("success"); + ok(true, `${testName}: complete() is successful after PaymentRequest's state is eClosed.`); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.name, "completing")); + await requestChromeAction("finish-test"); + return; + } + try { + info(`${testName}: Interact with payment when PaymentRequest is completed`); + await requestChromeAction("interact-with-payment"); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.name, "completing")); + await requestChromeAction("finish-test"); + return; + } + await requestChromeAction("finish-test"); + } + + function teardown() { + return new Promise((resolve, reject) => { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler); + gScript.removeMessageListener("test-pass", testPassHandler); + gScript.destroy(); + SimpleTest.finish(); + resolve(); + }); + gScript.sendAsyncMessage("teardown"); + }); + } + + async function runTests() { + try { + await testInteractWithPaymentUnderWrongState(); + await teardown(); + } catch(e) { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + } + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1490698">Mozilla Bug 1490698</a> +</body> +</html> diff --git a/dom/payments/test/test_canMakePayment.html b/dom/payments/test/test_canMakePayment.html new file mode 100644 index 0000000000..112fb8ce72 --- /dev/null +++ b/dom/payments/test/test_canMakePayment.html @@ -0,0 +1,164 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1345365 +--> +<head> + <meta charset="utf-8"> + <title>Test for PaymentRequest API canMakePayment()</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + var gUrl = SimpleTest.getTestFileURL('GeneralChromeScript.js'); + var gScript = SpecialPowers.loadChromeScript(gUrl); + + const defaultMethods = [{ + supportedMethods: "basic-card", + }]; + const defaultDetails = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00" + } + }, + }; + + const nonsupportedMethods = [{ + supportedMethods: "testing-payment-method", + }]; + + function testDefaultAction() { + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(nonsupportedMethods, defaultDetails); + payRequest.canMakePayment().then((result) => { + ok(!result, "Should be resolved with false, but got " + result + "."); + resolve(); + }).catch((err) => { + ok(false, "Expected no error, but got '" + err.name +"'."); + resolve(); + }); + }); + } + + function testSimple() { + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(defaultMethods, defaultDetails); + payRequest.canMakePayment().then((result) => { + ok(result, "Should be resolved with true, but got " + result + "."); + resolve(); + }).catch((err) => { + ok(false, "Expected no error, but got '" + err.name +"'."); + resolve(); + }); + }); + } + + function testAfterShow() { + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(defaultMethods, defaultDetails); + const acceptPromise = payRequest.show(); + payRequest.canMakePayment().then((result) => { + ok(false, "Should throw 'InvalidStateError', but got resolved."); + resolve(); + }).catch( (err) => { + is(err.name, "InvalidStateError", + "Expected 'InvalidStateError', but got '" + err.name + "'."); + payRequest.abort().then((abortResult) => { + is(abortResult, undefined, "abort() should be resolved with undefined."); + resolve(); + }).catch( (error) => { + ok(false, "Expected no error, but got '" + error.name + "'."); + resolve(); + }).finally(handler.destruct); + }); + }); + } + + function testAfterAbort() { + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(defaultMethods, defaultDetails); + const acceptPromise = payRequest.show(); + payRequest.abort().then((abortResult) => { + payRequest.canMakePayment().then((result) => { + ok(false, "Should throw 'InvalidStateError', but got resolved."); + resolve(); + }).catch( (err) => { + is(err.name, "InvalidStateError", + "Expected 'InvalidStateError', but got '" + err.name + "'."); + resolve(); + }); + }).catch( (err) => { + ok(false, "Expected no error, but got '" + err.name +"'."); + resolve(); + }).finally(handler.destruct); + }); + } + + async function testNotAllowed() { + let payRequest = new PaymentRequest(defaultMethods, defaultDetails); + for (let i = 0; i < 1000; i++) { + try { + await payRequest.canMakePayment(); + } catch(err) { + is(err.name, "NotAllowedError", + "Expected 'NotAllowError', but got '" + err.name + "'"); + break; + } + } + for (let i = 0; i < 1000; i++) { + try { + await new PaymentRequest(defaultMethods, defaultDetails).canMakePayment(); + } catch(err) { + is(err.name, "NotAllowedError", + "Expected 'NotAllowError', but got '" + err.name + "'"); + break; + } + } + } + + function teardown() { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.destroy(); + SimpleTest.finish(); + }); + gScript.sendAsyncMessage("teardown"); + } + + function runTests() { + testDefaultAction() + .then(testSimple) + .then(testAfterShow) + .then(testAfterAbort) + .then(testNotAllowed) + .then(teardown) + .catch(e => { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + }); + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1345365">Mozilla Bug 1345365</a> +</body> +</html> diff --git a/dom/payments/test/test_closePayment.html b/dom/payments/test/test_closePayment.html new file mode 100644 index 0000000000..8f2ad7cd00 --- /dev/null +++ b/dom/payments/test/test_closePayment.html @@ -0,0 +1,284 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1408234 +--> +<head> + <meta charset="utf-8"> + <title>Test for closing PaymentRequest</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="DefaultData.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + var gUrl = SimpleTest.getTestFileURL('ClosePaymentChromeScript.js'); + var gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + function testPassHandler(message) { + ok(true, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + gScript.addMessageListener("test-pass", testPassHandler); + + async function requestChromeAction(action, params) { + gScript.sendAsyncMessage(action, params); + await new Promise(resolve => { + gScript.addMessageListener(`${action}-complete`, function completeListener() { + gScript.removeMessageListener(`${action}-complete`, completeListener); + resolve(); + }); + }); + } + + async function testCloseByReloading() { + const testName = "testCloseByReloading"; + await requestChromeAction("test-setup", testName); + info(testName); + let nextStatus = ["creating", "reloading"]; + let currStatus = nextStatus.shift(); + let ifr = document.createElement('iframe'); + await requestChromeAction("payment-num-set"); + + await new Promise((resolve) => { + let listener = function(event) { + is(event.data, "successful", + `${testName}: Expected 'successful' when ${currStatus}, but got '${event.data}'.`); + if (currStatus === "creating") { + ifr.contentWindow.location.reload(); + } else if (currStatus === "reloading") { + window.removeEventListener("message", listener); + resolve(); + } + currStatus = nextStatus.shift(); + } + window.addEventListener("message", listener); + ifr.src = "simple_payment_request.html"; + document.body.appendChild(ifr); + }); + + await requestChromeAction("payment-num-check", 1); + document.body.removeChild(ifr); + + } + + async function testCloseByRedirecting() { + const testName = "testCloseByRedirecting"; + await requestChromeAction("test-setup", testName); + return new Promise((resolve) => { + let nextStatus = ["creating", "redirecting"]; + let currStatus = nextStatus.shift(); + let ifr = document.createElement('iframe'); + let listener = async function(event) { + is(event.data, "successful", + `${testName}: Expected 'successful' when ${currStatus}, but got '${event.data}'.`); + if (currStatus === "creating") { + ifr.src = "blank_page.html"; + } else if (currStatus === "redirecting"){ + window.removeEventListener("message", listener); + await requestChromeAction("close-check"); + document.body.removeChild(ifr); + resolve(); + } + currStatus = nextStatus.shift(); + }; + window.addEventListener("message", listener); + ifr.src = "simple_payment_request.html"; + document.body.appendChild(ifr); + }); + } + + async function testCloseByRedirectingAfterShow() { + const testName = "testCloseByRedirectingAfterShow"; + await requestChromeAction("test-setup", testName); + return new Promise((resolve) => { + let nextStatus = ["creating", "showing", "redirecting"]; + let currStatus = nextStatus.shift(); + let ifr = document.createElement('iframe'); + let handler = undefined; + let listener = async (event) => { + is(event.data, "successful", + `${testName}: Expected 'successful' when ${currStatus}, but got '${event.data}'.`); + if (currStatus === "creating") { + handler = SpecialPowers.getDOMWindowUtils(ifr.contentWindow).setHandlingUserInput(true); + ifr.contentWindow.postMessage("show PaymentRequest", "*"); + } else if (currStatus === "showing") { + handler.destruct(); + ifr.src = "blank_page.html"; + } else if (currStatus === "redirecting") { + window.removeEventListener("message", listener); + await requestChromeAction("close-check"); + await requestChromeAction("reject-payment", true); + document.body.removeChild(ifr); + resolve(); + } + currStatus = nextStatus.shift(); + } + window.addEventListener("message", listener); + ifr.src = "simple_payment_request.html"; + document.body.appendChild(ifr); + }); + } + + async function testCloseByRemovingIframe() { + const testName = "testCloseByRemovingIframe"; + await requestChromeAction("test-setup", testName); + return new Promise((resolve) => { + let nextStatus = ["creating"]; + let currStatus = nextStatus.shift(); + let ifr = document.createElement('iframe'); + let listener = async function(event) { + is(event.data, "successful", + `${testName}: Expected 'successful' when ${currStatus}, but got '${event.data}'.`); + document.body.removeChild(ifr); + window.removeEventListener("message", listener); + await requestChromeAction("close-check"); + resolve(); + }; + window.addEventListener("message", listener); + ifr.src = "simple_payment_request.html"; + document.body.appendChild(ifr); + }); + } + + async function testUpdateWithRespondedPayment() { + const testName = "testUpdateWithRespondedPayment"; + await requestChromeAction("test-setup", testName); + return new Promise(resolve => { + let nextStatus = ["creating", "showing", "closing", "updating", "finishing"]; + let currStatus = nextStatus.shift(); + let ifr = document.createElement('iframe'); + let handler = undefined; + let listener = async function(event) { + is(event.data, "successful", + `${testName}: Expected 'successful' when ${currStatus}, but got '${event.data}'.`); + switch (currStatus) { + case "creating": + handler = SpecialPowers.getDOMWindowUtils(ifr.contentWindow).setHandlingUserInput(true); + ifr.contentWindow.postMessage("show PaymentRequest", "*"); + break; + case "showing": + await requestChromeAction("update-payment"); + break; + case "closing": + await requestChromeAction("reject-payment", false); + break; + case "updating": + await requestChromeAction("close-check"); + ifr.contentWindow.postMessage("updateWith PaymentRequest", "*"); + break; + case "finishing": + handler.destruct(); + document.body.removeChild(ifr); + window.removeEventListener("message", listener); + resolve(); + break; + default: + ok(false, testName + ": Unknown status()" + currStatus); + break; + } + currStatus = nextStatus.shift(); + } + window.addEventListener("message", listener); + ifr.src = "simple_payment_request.html"; + document.body.appendChild(ifr); + }); + } + + function getLoadedPaymentRequest(iframe, url) { + return new Promise(resolve => { + iframe.addEventListener( + "load", + () => { + const { PaymentRequest } = iframe.contentWindow; + const request = new PaymentRequest(defaultMethods, defaultDetails); + resolve(request); + }, + { once: true } + ); + iframe.src = url; + }); + } + + async function testNonfullyActivePayment() { + const testName = "testNonfullyActivePayment"; + await requestChromeAction("test-setup", testName); + + const outer = document.createElement("iframe"); + outer.allow = "payment"; + document.body.appendChild(outer); + await getLoadedPaymentRequest(outer,"blank_page.html"); + + const inner = outer.contentDocument.createElement("iframe"); + inner.allow = "payment"; + outer.contentDocument.body.appendChild(inner); + + const request = await getLoadedPaymentRequest(inner,"blank_page.html"); + ok(request, `${testName}: PaymentRequest in inner iframe should exist.`); + + await new Promise(res => { + outer.addEventListener("load", res); + outer.src = "simple_payment_request.html"; + }); + + let handler = SpecialPowers.getDOMWindowUtils(inner.contentWindow).setHandlingUserInput(true); + try { + const showPromise = await request.show(); + ok(false, `${testName}: expected 'AbortError', but got resolved.`); + } catch (error) { + is(error.name, "AbortError", + `${testName}: expected 'AbortError'.`); + } + await handler.destruct(); + inner.remove(); + outer.remove(); + } + + async function teardown() { + return new Promise((resolve) => { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler); + gScript.removeMessageListener("test-pass", testPassHandler); + gScript.destroy(); + SimpleTest.finish(); + resolve(); + }); + gScript.sendAsyncMessage("teardown"); + }); + } + + async function runTests() { + try { + await testCloseByReloading(); + await testCloseByRedirecting(); + await testCloseByRedirectingAfterShow(); + await testCloseByRemovingIframe(); + await testUpdateWithRespondedPayment(); + await testNonfullyActivePayment(); + await teardown(); + } catch(e) { + ok(false, "test_closePayment.html: Unexpected error: " + e.name); + SimpleTest.finish(); + } + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1408234">Mozilla Bug 1408234</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1483470">Mozilla Bug 1483470</a> +</body> +</html> diff --git a/dom/payments/test/test_constructor.html b/dom/payments/test/test_constructor.html new file mode 100644 index 0000000000..4517a37028 --- /dev/null +++ b/dom/payments/test/test_constructor.html @@ -0,0 +1,351 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1345361 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1345361</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + var gUrl = SimpleTest.getTestFileURL('ConstructorChromeScript.js'); + var gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + + const simplestMethods = [{ + supportedMethods: "basic-card", + }]; + const simplestDetails = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00" + } + } + }; + + const complexMethods = [{ + supportedMethods: "basic-card", + data: { + supportedNetworks: ['unionpay', 'visa', 'mastercard', 'amex', 'discover', + 'diners', 'jcb', 'mir', + ], + }, + }]; + + const nonBasicCardMethods = [{ + supportedMethods: "testing-payment-method", + data: { + paymentId: "P3892940", + paymentType: "prepaid", + }, + }]; + + const complexDetails = { + id: "payment details", + total: { + label: "Total", + amount: { + currency: "USD", + value: "100.00" + } + }, + displayItems: [ + { + label: "First item", + amount: { + currency: "USD", + value: "60.00" + } + }, + { + label: "Second item", + amount: { + currency: "USD", + value: "40.00" + } + } + ], + modifiers: [ + { + supportedMethods: "basic-card", + total: { + label: "Discounted Total", + amount: { + currency: "USD", + value: "90.00" + } + }, + additionalDisplayItems: [ + { + label: "basic-card discount", + amount: { + currency: "USD", + value: "-10.00" + } + } + ], + data: { discountProgramParticipantId: "86328764873265", } + }, + ], + shippingOptions: [ + { + id: "NormalShipping", + label: "NormalShipping", + amount: { + currency: "USD", + value: "10.00" + }, + selected: true, + }, + { + id: "FastShipping", + label: "FastShipping", + amount: { + currency: "USD", + value: "30.00" + }, + selected: false, + }, + ], + }; + + const complexOptions = { + requestPayerName: true, + requestPayerEmail: true, + requestPayerPhone: true, + requestShipping: true, + shippingType: "shipping" + }; + + const duplicateShippingOptionsDetails = { + id: "duplicate shipping options details", + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00" + } + }, + shippingOptions: [ + { + id: "dupShipping", + label: "NormalShipping", + amount: { + currency: "USD", + value: "10.00" + }, + selected: true, + }, + { + id: "dupShipping", + label: "FastShipping", + amount: { + currency: "USD", + value: "30.00" + }, + selected: false, + }, + ], + }; + + + function testWithSimplestParameters() { + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(simplestMethods, simplestDetails); + ok(payRequest, "PaymentRequest should be created"); + gScript.addMessageListener("check-complete", function checkCompleteHandler() { + gScript.removeMessageListener("check-complete", checkCompleteHandler); + resolve(); + }); + gScript.sendAsyncMessage("check-simplest-request"); + }); + } + + function testWithComplexParameters() { + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(complexMethods, complexDetails, complexOptions); + ok(payRequest, "PaymentRequest should be created"); + gScript.addMessageListener("check-complete", function checkCompleteHandler() { + gScript.removeMessageListener("check-complete", checkCompleteHandler); + resolve(); + }); + gScript.sendAsyncMessage("check-complex-request"); + }); + } + + function testWithNonBasicCardMethods() { + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(nonBasicCardMethods, simplestDetails); + ok(payRequest, "PaymentRequest should be created"); + gScript.addMessageListener("check-complete", function checkCompleteHandler() { + gScript.removeMessageListener("check-complete", checkCompleteHandler); + resolve(); + }); + gScript.sendAsyncMessage("check-nonbasiccard-request"); + }); + } + + function testWithDuplicateShippingOptionsParameters() { + return new Promise((resolve, reject) => { + try { + const payRequest = new PaymentRequest(simplestMethods, + duplicateShippingOptionsDetails, + {requestShipping: true}); + ok(false, "Construction should fail with duplicate shippingOption Ids."); + resolve(); + } catch (e) { + is(e.name, "TypeError", "Expected 'TypeError' with duplicate shippingOption Ids."); + resolve(); + } + }); + } + + function testShippingOptionAttribute() { + return new Promise((resolve, reject) => { + const details = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, + shippingOptions: [ + { + id: "option1", + label: "option1", + amount: { + currency: "USD", + value: "1.00", + }, + selected: false, + }, + { + id: "option2", + label: "option2", + amount: { + currency: "USD", + value: "1.00", + }, + selected: false, + }, + ], + }; + const payRequest1 = new PaymentRequest(simplestMethods, + details, + {requestShipping: false}); + ok(payRequest1, "PaymentRequest should be created"); + is(payRequest1.shippingOption, null, + "request.shippingOption should be null in default, when options.requestShipping is false"); + details.shippingOptions[0].selected = true; + const payRequest2 = new PaymentRequest(simplestMethods, + details, + {requestShipping: false}); + ok(payRequest2, "PaymentRequest should be created"); + is(payRequest2.shippingOption, null, + "request.shippingOption should be null in default, when options.requestShipping is false"); + const payRequest3 = new PaymentRequest(simplestMethods, + details, + {requestShipping: true}); + ok(payRequest3, "PaymentRequest should be created"); + ok(payRequest3.shippingOption, + "request.shippingOption should not be null when both shoppingOtpion.selected and options.requestOptions are true"); + is(payRequest3.shippingOption, "option1", + "request.shippingOption should be 'option1'"); + details.shippingOptions[1].selected = true; + const payRequest4 = new PaymentRequest(simplestMethods, + details, + {requestShipping: true}); + ok(payRequest4, "PaymentRequest should be created"); + ok(payRequest4.shippingOption, + "request.shippingOption should not be null when both shoppingOtpion.selected and options.requestOptions are true"); + is(payRequest4.shippingOption, "option2", + "request.shippingOption should be 'option2' which is the last one selected."); + resolve(); + }); + } + + function testMultipleRequests() { + return new Promise((resolve, reject) => { + const payRequest1 = new PaymentRequest(complexMethods, complexDetails, complexOptions); + const payRequest2 = new PaymentRequest(simplestMethods, simplestDetails); + ok(payRequest1, "PaymentRequest with complex parameters should be created"); + ok(payRequest2, "PaymentRequest with simplest parameters should be created"); + gScript.addMessageListener("check-complete", function checkCompleteHandler() { + gScript.removeMessageListener("check-complete", checkCompleteHandler); + resolve(); + }); + gScript.sendAsyncMessage("check-multiple-requests"); + }); + } + + function testCrossOriginTopLevelPrincipal() { + return new Promise((resolve, reject) => { + var ifrr = document.createElement('iframe'); + + window.addEventListener("message", function(event) { + is(event.data, "successful", + "Expected 'successful', but got '" + event.data + "'"); + gScript.addMessageListener("check-complete", function checkCompleteHandler() { + gScript.removeMessageListener("check-complete", checkCompleteHandler); + resolve(); + }); + gScript.sendAsyncMessage("check-cross-origin-top-level-principal"); + }); + + ifrr.setAttribute('allow', 'payment'); + ifrr.src = "https://test1.example.com:443/tests/dom/payments/test/simple_payment_request.html"; + document.body.appendChild(ifrr); + }); + } + + function teardown() { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler) + gScript.destroy(); + SimpleTest.finish(); + }); + gScript.sendAsyncMessage("teardown"); + } + + function runTests() { + testWithSimplestParameters() + .then(testWithComplexParameters) + .then(testWithNonBasicCardMethods) + .then(testWithDuplicateShippingOptionsParameters) + .then(testMultipleRequests) + .then(testCrossOriginTopLevelPrincipal) + .then(testShippingOptionAttribute) + .then(teardown) + .catch( e => { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + }); + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1345361">Mozilla Bug 1345361</a> +</body> +</html> diff --git a/dom/payments/test/test_currency_amount_validation.html b/dom/payments/test/test_currency_amount_validation.html new file mode 100644 index 0000000000..bf8284f37a --- /dev/null +++ b/dom/payments/test/test_currency_amount_validation.html @@ -0,0 +1,353 @@ +<!DOCTYPE HTML> +<meta charset="utf-8"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1367669 +https://bugzilla.mozilla.org/show_bug.cgi?id=1388661 +--> +<title>Test for PaymentRequest API currency amount validation</title> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script> +"use strict"; +SimpleTest.waitForExplicitFinish(); + +const gUrl = SimpleTest.getTestFileURL( + "CurrencyAmountValidationChromeScript.js" +); +const gScript = SpecialPowers.loadChromeScript(gUrl); + +function testFailHandler(message) { + ok(false, message); +} +gScript.addMessageListener("test-fail", testFailHandler); + +const defaultMethods = [ + { + supportedMethods: "basic-card", + }, +]; +const defaultDetails = { + total: { + label: "total", + amount: { + currency: "usd", + value: "1.00", + }, + }, +}; + +const specialAmountDetails = { + total: { + label: "total", + amount: { + currency: "usd", + value: { + toString() { + throw "42"; + }, + }, + }, + }, +}; + +const wellFormedCurrencyCodes = [ + "BOB", + "EUR", + "usd", // currency codes are case-insensitive + "XdR", + "xTs", +]; + +const invalidCurrencyCodes = [ + "", + "€", + "$", + "SFr.", + "DM", + "KR₩", + "702", + "ßP", + "ınr", + "invalid", + "in", + "123", +]; + +const updatedInvalidCurrencyDetails = { + total: { + label: "Total", + amount: { + currency: "Invalid", + value: "1.00", + }, + }, +}; + +const updatedInvalidAmountDetails = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "-1.00", + }, + }, +}; + +const invalidAmounts = [ + "-", + "notdigits", + "ALSONOTDIGITS", + "10.", + ".99", + "-10.", + "-.99", + "10-", + "1-0", + "1.0.0", + "1/3", + "", + null, + " 1.0 ", + " 1.0 ", + "1.0 ", + "USD$1.0", + "$1.0", + { + toString() { + return " 1.0"; + }, + }, + undefined, +]; +const invalidTotalAmounts = invalidAmounts.concat([ + "-1", + "-1.0", + "-1.00", + "-1000.000", +]); + +function updateWithInvalidAmount() { + return new Promise((resolve, reject) => { + resolve(updatedInvalidAmountDetails); + }); +} + +async function testWithLowerCaseCurrency() { + const payRequest = new PaymentRequest(defaultMethods, defaultDetails); + return new Promise(resolve => { + gScript.addMessageListener( + "check-complete", + function checkCompleteHandler() { + gScript.removeMessageListener("check-complete", checkCompleteHandler); + resolve(); + } + ); + gScript.sendAsyncMessage("check-lower-case-currency"); + }); +} + +function testWithWellFormedCurrencyCodes() { + for (const currency of wellFormedCurrencyCodes) { + const details = { + total: { + label: "Well Formed Currency", + amount: { + currency, + value: "1.00", + }, + }, + }; + try { + const payRequest = new PaymentRequest(defaultMethods, details); + } catch (e) { + const msg = `Unexpected error while creating payment request with well-formed currency (${currency}) ${ + e.name + }`; + ok(false, msg); + } + } +} + +function testWithInvalidCurrencyCodes() { + for (const invalidCurrency of invalidCurrencyCodes) { + const invalidDetails = { + total: { + label: "Invalid Currency", + amount: { + currency: invalidCurrency, + value: "1.00", + }, + }, + }; + try { + const payRequest = new PaymentRequest(defaultMethods, invalidDetails); + ok( + false, + `Creating a Payment Request with invalid currency (${invalidCurrency}) must throw.` + ); + } catch (e) { + is( + e.name, + "RangeError", + `Expected rejected with 'RangeError', but got '${e.name}'.` + ); + } + } +} + +async function testUpdateWithInvalidCurrency() { + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput( + true + ); + gScript.sendAsyncMessage("set-update-with-invalid-details-ui-service"); + const payRequest = new PaymentRequest(defaultMethods, defaultDetails); + payRequest.addEventListener("shippingaddresschange", event => { + event.updateWith(Promise.resolve(updatedInvalidCurrencyDetails)); + }); + payRequest.addEventListener("shippingoptionchange", event => { + event.updateWith(updatedInvalidCurrencyDetails); + }); + try { + await payRequest.show(); + ok(false, "Should have rejected with 'RangeError'"); + } catch (err) { + is( + err.name, + "RangeError", + `Should be rejected with 'RangeError', but got '${err.name}'.` + ); + } + handler.destruct(); +} + +async function testUpdateWithInvalidAmount() { + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput( + true + ); + gScript.sendAsyncMessage("set-update-with-invalid-details-ui-service"); + const payRequest = new PaymentRequest(defaultMethods, defaultDetails); + payRequest.addEventListener("shippingaddresschange", event => { + event.updateWith(updateWithInvalidAmount()); + }); + payRequest.addEventListener("shippingoptionchange", event => { + event.updateWith(updateWithInvalidAmount()); + }); + try { + await payRequest.show(); + ok(false, "Should be rejected with 'TypeError'"); + } catch (err) { + is( + err.name, + "TypeError", + `Should be rejected with 'TypeError', but got ${err.name}.` + ); + } + handler.destruct(); +} + +function testSpecialAmount() { + try { + new PaymentRequest(defaultMethods, specialAmountDetails); + ok(false, "Should throw '42', but got resolved."); + } catch (e) { + is(e, "42", "Expected throw '42'. but got " + e); + } +} + +function testInvalidTotalAmounts() { + for (const invalidAmount of invalidTotalAmounts) { + try { + const invalidDetails = { + total: { + label: "", + amount: { + currency: "USD", + value: invalidAmount, + }, + }, + }; + new PaymentRequest(defaultMethods, invalidDetails); + ok(false, "Should throw 'TypeError', but got resolved."); + } catch (err) { + is(err.name, "TypeError", `Expected 'TypeError', but got '${err.name}'`); + } + } +} + +function testInvalidAmounts() { + for (const invalidAmount of invalidAmounts) { + try { + new PaymentRequest(defaultMethods, { + total: { + label: "", + amount: { + currency: "USD", + value: "1.00", + }, + }, + displayItems: [ + { + label: "", + amount: { + currency: "USD", + value: invalidAmount, + }, + }, + ], + }); + ok(false, "Should throw 'TypeError', but got resolved."); + } catch (err) { + is(err.name, "TypeError", `Expected 'TypeError', but got '${err.name}'.`); + } + } +} + +function teardown() { + return new Promise(resolve => { + gScript.addMessageListener( + "teardown-complete", + function teardownCompleteHandler() { + gScript.removeMessageListener( + "teardown-complete", + teardownCompleteHandler + ); + gScript.removeMessageListener("test-fail", testFailHandler); + gScript.destroy(); + SimpleTest.finish(); + resolve(); + } + ); + gScript.sendAsyncMessage("teardown"); + }); +} + +async function runTests() { + try { + testInvalidTotalAmounts(); + testSpecialAmount(); + testInvalidAmounts(); + testWithWellFormedCurrencyCodes(); + testWithInvalidCurrencyCodes(); + await testUpdateWithInvalidAmount(); + await testUpdateWithInvalidCurrency(); + await testWithLowerCaseCurrency(); + await teardown(); + } catch (e) { + console.error(e); + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + } +} + +window.addEventListener("load", () => { + SpecialPowers.pushPrefEnv( + { + set: [["dom.payments.request.enabled", true]], + }, + runTests + ); +}); +</script> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1367669">Mozilla Bug 1367669</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1388661">Mozilla Bug 1388661</a> diff --git a/dom/payments/test/test_payerDetails.html b/dom/payments/test/test_payerDetails.html new file mode 100644 index 0000000000..9a241803af --- /dev/null +++ b/dom/payments/test/test_payerDetails.html @@ -0,0 +1,107 @@ +<!DOCTYPE HTML> +<meta charset="utf-8"> +<title>Test for PaymentResponse.prototype.onpayerdetailchange</title> +<link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="./DefaultData.js"></script> +<script> + SimpleTest.waitForExplicitFinish(); + + const gUrl = SimpleTest.getTestFileURL("PayerDetailsChromeScript.js"); + const gScript = SpecialPowers.loadChromeScript(gUrl); + + function okTester(result) { + return message => ok(result, message); + } + const passListener = okTester(true); + const failListener = okTester(false); + + gScript.addMessageListener("test-fail", failListener); + gScript.addMessageListener("test-pass", passListener); + + function sendOnce(message) { + return data => { + return new Promise(resolve => { + const doneMsg = `${message}-complete`; + gScript.addMessageListener(doneMsg, function listener() { + gScript.removeMessageListener(doneMsg, listener); + resolve(); + }); + gScript.sendAsyncMessage(message, data); + }); + }; + } + const sendTearDown = sendOnce("teardown"); + + async function loopTest(iterations) { + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput( + true + ); + const options = { + requestPayerName: true, + requestPayerEmail: true, + requestPayerPhone: true, + } + const request = new PaymentRequest(defaultMethods, defaultDetails, options); + const response = await request.show(); + is(response.payerName, "", ".payerName must initially be ''"); + is(response.payerEmail, "", ".payerEmail must initially be ''"); + is(response.payerPhone, "", ".payerPhone must initially be ''"); + for (let i = 0; i < iterations; i++) { + const payer = { + name: `test name ${i}`, + phone: `test phone ${i}`, + email: `test email ${i}`, + } + + // Capture the event to firing + const eventPromise = new Promise(resolve => { + response.onpayerdetailchange = resolve; + }); + const retryPromise = response.retry({ + error: "retry-fire-payerdetaichangeevent", + payer + }); + const event = await eventPromise; + + // Check things got updated + is(response.payerName, payer.name, `.payerName must be "${payer.name}"`); + is(response.payerEmail, payer.email, `.payerEmail must be "${payer.email}"`); + is(response.payerPhone, payer.phone, `.payerPhone must be "${payer.phone}"`); + + // Finally, let's do an updateWith() + event.updateWith({ error: "update-with", payerErrors: payer, ...defaultDetails }); + + await retryPromise; + } + + await response.complete("success"); + handler.destruct(); + } + + async function teardown() { + await sendTearDown(); + gScript.removeMessageListener("test-fail", failListener); + gScript.removeMessageListener("test-pass", passListener); + gScript.destroy(); + SimpleTest.finish(); + } + + async function runTests() { + try { + await loopTest(5); // lets go around 5 times + } catch (err) { + ok(false, `Unexpected error: ${err}.`); + } finally { + await teardown(); + } + } + + window.addEventListener("load", () => { + const prefs = [["dom.payments.request.enabled", true]]; + SpecialPowers.pushPrefEnv({ set: prefs }, runTests); + }); +</script> + +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1472026">Mozilla Bug 1472026</a> diff --git a/dom/payments/test/test_payment-request-in-iframe.html b/dom/payments/test/test_payment-request-in-iframe.html new file mode 100644 index 0000000000..e72a6b693a --- /dev/null +++ b/dom/payments/test/test_payment-request-in-iframe.html @@ -0,0 +1,168 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1318988 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1318988</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + function testRequestInSameOrigin() { + return new Promise((resolve, reject) => { + var ifr = document.createElement('iframe'); + + let listener = function(event) { + is(event.data, "successful", + "Expected 'successful', but got '" + event.data + "'"); + resolve(); + }; + + window.addEventListener("message", listener); + + ifr.src = "simple_payment_request.html"; + document.body.appendChild(ifr); + + ifr.addEventListener('load', function() { + window.removeEventListener("message", listener); + }); + }); + } + + function testRequestInIFrame() { + return new Promise((resolve, reject) => { + var ifr = document.createElement('iframe'); + + let listener = function(event) { + is(event.data, "SecurityError", + "Expected 'SecurityError', but got '" + event.data + "'"); + resolve(); + }; + + window.addEventListener("message", listener); + + ifr.src = "https://test1.example.com:443/tests/dom/payments/test/simple_payment_request.html"; + document.body.appendChild(ifr); + + ifr.addEventListener('load', function() { + window.removeEventListener("message", listener); + }); + }); + } + + function testRequestInIFrameWithAttribute() { + return new Promise((resolve, reject) => { + var ifrr = document.createElement('iframe'); + + let listener = function(event) { + is(event.data, "successful", + "Expected 'successful', but got '" + event.data + "'"); + resolve(); + }; + + window.addEventListener("message", listener); + + ifrr.setAttribute('allow', 'payment'); + ifrr.src = "https://test1.example.com:443/tests/dom/payments/test/simple_payment_request.html"; + document.body.appendChild(ifrr); + + ifrr.addEventListener('load', function() { + window.removeEventListener("message", listener); + }); + }); + } + + function testRequestWithAttributeChanged() { + return new Promise((resolve, reject) => { + var ifrr = document.createElement('iframe'); + + let i = 0; + + ifrr.addEventListener('load', function() { + if (i === 0) { + ifrr.removeAttribute("allow"); + } + ifrr.contentWindow.postMessage('new PaymentRequest', '*'); + }); + + let listener = function(event) { + i++; + if (i === 1) { + is(event.data, "successful", + "Expected successful when running with allow=payment attribute."); + ifrr.contentWindow.location.href = ifrr.src; + } else { + is(event.data, "SecurityError", + "Expected SecurityError when running without allow=payment attribute."); + window.removeEventListener("message", listener); + resolve(); + } + } + window.addEventListener("message", listener); + + ifrr.setAttribute("allow", "payment"); + ifrr.src = "https://test1.example.com:443/tests/dom/payments/test/echo_payment_request.html"; + + document.body.appendChild(ifrr); + }); + } + + function testRequestInCrossOriginNestedIFrame() { + return new Promise((resolve, reject) => { + var ifrr = document.createElement('iframe'); + + let listener = function(event) { + if (ifrr.allow != 'payment') { + is(event.data, "SecurityError", + "Expected 'SecurityError' without allow=payment in nested iframe"); + ifrr.setAttribute('allow', "payment"); + ifrr.contentWindow.location.href = ifrr.src; + } else { + is(event.data, "successful", + "Expected 'successful' with allow='payment' in nested iframe"); + window.removeEventListener("message", listener); + resolve(); + } + }; + window.addEventListener("message", listener); + + ifrr.addEventListener("load", function() { + ifrr.contentWindow.postMessage('new PaymentRequest in a new iframe', '*'); + }) + + ifrr.src = "https://test1.example.com:443/tests/dom/payments/test/echo_payment_request.html"; + document.body.appendChild(ifrr); + }); + } + + function runTests() { + testRequestInSameOrigin() + .then(testRequestInIFrame) + .then(testRequestInIFrameWithAttribute) + .then(testRequestWithAttributeChanged) + .then(testRequestInCrossOriginNestedIFrame) + .then(SimpleTest.finish) + .catch( e => { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + }); + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1318988">Mozilla Bug 1318988</a> +</body> +</html> diff --git a/dom/payments/test/test_pmi_validation.html b/dom/payments/test/test_pmi_validation.html new file mode 100644 index 0000000000..00d5c0771c --- /dev/null +++ b/dom/payments/test/test_pmi_validation.html @@ -0,0 +1,245 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1389418 +--> +<head> + <meta charset="utf-8"> + <title>Test for PaymentRequest API payment method identifier validation</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + var gUrl = SimpleTest.getTestFileURL('PMIValidationChromeScript.js'); + var gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + + const defaultMethods = [{ + supportedMethods: "basic-card", + }]; + + const defaultDetails = { + total: { + label: "total", + amount: { + currency: "usd", + value: "1.00", + }, + }, + }; + + const validPMIs = [ + "https://wpt", + "https://wpt.fyi/", + "https://wpt.fyi/payment", + "https://wpt.fyi/payment-request", + "https://wpt.fyi/payment-request?", + "https://wpt.fyi/payment-request?this=is", + "https://wpt.fyi/payment-request?this=is&totally", + "https://wpt.fyi:443/payment-request?this=is&totally", + "https://wpt.fyi:443/payment-request?this=is&totally#fine", + "https://:@wpt.fyi:443/payment-request?this=is&totally#👍", + " \thttps://wpt\n ", + "https://xn--c1yn36f", + "https://點看", + "e", + "n6jzof05mk2g4lhxr-u-q-w1-c-i-pa-ty-bdvs9-ho-ae7-p-md8-s-wq3-h-qd-e-q-sa", + "a-b-q-n-s-pw0", + "m-u", + "s-l5", + "k9-f", + "m-l", + "u4-n-t", + "i488jh6-g18-fck-yb-v7-i", + "x-x-t-t-c34-o", + "basic-card", + ]; + + const invalidPMIs = [ + "https://:password@example.com", + "https://username@example.com", + "https://username:password@example.com/pay", + "http://username:password@example.com/pay", + "https://:@example.com:100000000/pay", + "https://foo.com:100000000/pay", + "basic-💳", + "not-https://wpt.fyi/payment-request", + "../realitive/url", + "/absolute/../path?", + "https://", + "¡basic-*-card!", + "Basic-Card", + "0", + "-", + "--", + "a--b", + "-a--b", + "a-b-", + "0-", + "0-a", + "a0--", + "A-", + "A-B", + "A-b", + "a-0", + "a-0b", + " a-b", + "\t\na-b", + "a-b ", + "a-b\n\t", + ]; + + function testWithValidPMIs() { + return new Promise((resolve, reject) => { + for (const validPMI of validPMIs) { + try { + const validMethods = [{supportedMethods: validPMI},]; + const payRequest = new PaymentRequest(validMethods, defaultDetails); + resolve(); + } catch (e) { + ok(false, "Unexpected error '" + e.name + "'."); + resolve(); + } + } + }); + } + + function testWithInvalidPMIs() { + return new Promise((resolve, reject) => { + for (const invalidPMI of invalidPMIs) { + try { + const invalidMethods = [{supportedMethods: invalidPMI},]; + const payRequest = new PaymentRequest(invalidMethods, defaultDetails); + ok(false, "Expected throw 'RangeError', but got resolved"); + resolve(); + } catch (e) { + is(e.name, "RangeError", "Expected 'RangeError'."); + resolve(); + } + } + }); + } + + function testUpdateWithValidPMI() { + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + + gScript.sendAsyncMessage("set-ui-service"); + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(defaultMethods, defaultDetails); + payRequest.addEventListener("shippingoptionchange", event => { + const validDetails = { + total: { + label: "total", + amount: { + currency: "USD", + value: "1.00", + }, + }, + modifiers: [{ + supportedMethods: "https://example.com", + total: { + label: "total", + amount: { + currency: "USD", + value: "1.00", + }, + } + },], + } + event.updateWith(validDetails); + }); + payRequest.show().then((response) => { + response.complete("success").then(() => { + resolve(); + }).catch((e) => { + ok(false, "Unexpected error '" + e.name + "'."); + resolve(); + }); + }).catch((e) => { + ok(false, "Unexpected error '" + e.name + "'."); + resolve(); + }).finally(handler.destruct); + }); + } + + function testUpdateWithInvalidPMI() { + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + + gScript.sendAsyncMessage("set-ui-service"); + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(defaultMethods, defaultDetails); + payRequest.addEventListener("shippingoptionchange", event => { + const invalidDetails = { + total: { + label: "total", + amount: { + currency: "USD", + value: "1.00", + }, + }, + modifiers: [{ + supportedMethods: "https://username:password@example.com", + total: { + label: "total", + amount: { + currency: "USD", + value: "1.00", + }, + }, + },], + } + event.updateWith(invalidDetails); + }); + payRequest.show().then((result) => { + ok(false, "Expected throw 'RangeError', but got resolved."); + resolve(); + }).catch((e) => { + is(e.name, "RangeError", "Expected 'RangeError'."); + resolve(); + }).finally(handler.destruct); + }); + } + + function teardown() { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler) + gScript.destroy(); + SimpleTest.finish(); + }); + gScript.sendAsyncMessage("teardown"); + } + + function runTests() { + testWithValidPMIs() + .then(testWithInvalidPMIs) + .then(testUpdateWithValidPMI) + .then(testUpdateWithInvalidPMI) + .then(teardown) + .catch( e => { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + }); + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1389418">Mozilla Bug 1389418</a> +</body> +</html> diff --git a/dom/payments/test/test_requestShipping.html b/dom/payments/test/test_requestShipping.html new file mode 100644 index 0000000000..b866588953 --- /dev/null +++ b/dom/payments/test/test_requestShipping.html @@ -0,0 +1,180 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1436903 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1436903</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + var gUrl = SimpleTest.getTestFileURL('RequestShippingChromeScript.js'); + var gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + + const defaultMethods = [{ + supportedMethods: "basic-card", + data: { + supportedNetworks: ['unionpay', 'visa', 'mastercard', 'amex', 'discover', + 'diners', 'jcb', 'mir', + ], + }, + }, { + supportedMethods: "testing-payment-method", + }]; + const defaultDetails = { + id: "test payment", + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00" + } + }, + shippingOptions: [ + { + id: "NormalShipping", + label: "NormalShipping", + amount: { + currency: "USD", + value: "10.00" + }, + selected: false, + }, + { + id: "FastShipping", + label: "FastShipping", + amount: { + currency: "USD", + value: "30.00" + }, + selected: false, + }, + ], + }; + + const defaultOptions = { + requestPayerName: true, + requestPayerEmail: false, + requestPayerPhone: false, + requestShipping: false, + shippingType: "shipping" + }; + + const updatedOptionDetails = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00" + } + }, + shippingOptions: [ + { + id: "NormalShipping", + label: "NormalShipping", + amount: { + currency: "USD", + value: "10.00" + }, + selected: false, + }, + { + id: "FastShipping", + label: "FastShipping", + amount: { + currency: "USD", + value: "30.00" + }, + selected: true, + }, + ], + }; + + const nonSupportedMethods = [{ + supportedMethods: "nonsupported-method", + }]; + + + function updateWithShippingAddress() { + return new Promise((resolve, reject) => { + resolve(defaultDetails); + }); + } + + function updateWithShippingOption() { + return new Promise((resolve, reject) => { + resolve(updatedOptionDetails); + }); + } + + function testShow() { + gScript.sendAsyncMessage("set-normal-ui-service"); + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + + payRequest.addEventListener("shippingaddresschange", event => { + event.updateWith(updateWithShippingAddress()); + }); + payRequest.addEventListener("shippingoptionchange", event => { + event.updateWith(updateWithShippingOption()); + }); + + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + payRequest.show().then(response => { + response.complete("success").then(() =>{ + resolve(); + }).catch(e => { + ok(false, "Unexpected error: " + e.name); + resolve(); + }); + }).catch( e => { + ok(false, "Unexpected error: " + e.name); + resolve(); + }).finally(handler.destruct); + }); + } + + function teardown() { + ok(true, "Mandatory assert"); + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler) + gScript.destroy(); + SimpleTest.finish(); + }); + gScript.sendAsyncMessage("teardown"); + } + + function runTests() { + testShow() + .then(teardown) + .catch( e => { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + }); + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1436903">Mozilla Bug 1436903</a> +</body> +</html> diff --git a/dom/payments/test/test_retryPayment.html b/dom/payments/test/test_retryPayment.html new file mode 100644 index 0000000000..7caed56b0c --- /dev/null +++ b/dom/payments/test/test_retryPayment.html @@ -0,0 +1,351 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1435161 +--> +<head> + <meta charset="utf-8"> + <title>Test for retry PaymentRequest</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="DefaultData.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + const gUrl = SimpleTest.getTestFileURL('RetryPaymentChromeScript.js'); + const gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + function testPassHandler(message) { + ok(true, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + gScript.addMessageListener("test-pass", testPassHandler); + + async function requestChromeAction(action, params) { + gScript.sendAsyncMessage(action, params); + await new Promise(resolve => { + gScript.addMessageListener(`${action}-complete`, function completeListener() { + gScript.removeMessageListener(`${action}-complete`, completeListener); + resolve(); + }); + }); + } + + const validationErrors = { + error: "error", + shippingAddress: { + addressLine: "addressLine error", + city: "city error", + country: "country error", + dependentLocality: "dependentLocality error", + organization: "organization error", + phone: "phone error", + postalCode: "postalCode error", + recipient: "recipient error", + region: "region error", + regionCode: "regionCode error", + sortingCode: "sortingCode error", + }, + payer: { + name: "name error", + email: "email error", + phone: "phone error", + }, + paymentMethod: { + account: "method account error", + password: "method password error", + }, + }; + + const options = { + requestPayerName: true, + requestPayerEmail: true, + requestPayerPhone: true, + requestShipping: true, + shippingType: "shipping" + }; + + function checkShowResponse(testName, payResponse) { + const { payerName, payerEmail, payerPhone } = payResponse.toJSON(); + is( + payerName, + "Bill A. Pacheco", + `${testName}: Expected 'Bill A. Pacheco' on payerName, but got '${payerName}' after show PaymentRequest` + ); + is( + payerEmail, + "", + `${testName}: Expected '' on payerEmail, but got '${payerEmail}' after show PaymentRequest` + ); + is( + payerPhone, + "", + `${testName}: Expected '' on payerPhone, but got '${payerPhone}' after show PaymentRequest` + ); + } + + function checkRetryResponse(testName, payResponse) { + const { payerName, payerEmail, payerPhone } = payResponse.toJSON(); + is( + payerName, + "Bill A. Pacheco", + `${testName}: Expected 'Bill A. Pacheco' on payerName, but got '${payerName}' after retry PaymentRequest` + ); + is( + payerEmail, + "bpacheco@test.org", + `${testName} : Expected 'bpacheco@test.org' on payerEmail, but got '${payerEmail}' after retry PaymentRequest` + ); + is( + payerPhone, + "+123456789", + `${testName} : Expected '+123456789' on payerPhone, but got '${payerPhone}' after retry PaymentRequest` + ); + } + + function unexpectedErrMsg(testName, errName, timing) { + return `${testName}: Unexpected error(${errName}) when ${timing} the PaymentRequest.`; + } + + function expectedErrMsg(testName, expectedErr, errName, timing) { + return `${testName}: Expected '${expectedErr}' when ${timing} PaymentResponse, but got '${errName}'.`; + } + + async function testRetryAfterComplete() { + const testName = "testRetryAfterComplete"; + await requestChromeAction("start-test", testName); + const payRequest = new PaymentRequest(defaultMethods, defaultDetails, options); + ok(payRequest, testName + ": failed to create PaymentRequest."); + if (!payRequest) { + await requestChromeAction("finish-test"); + return; + } + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + let payResponse; + try { + payResponse = await payRequest.show(); + await checkShowResponse(testName, payResponse); + handler.destruct(); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.Name, "showing")); + await requestChromeAction("finish-test"); + handler.destruct(); + return; + } + try { + await payResponse.complete("success"); + } catch(err) { + let errName = err.Name; + ok(false, unexpectedErrMsg(testName, err.Name, "completing")); + await requestChromeAction("finish-test"); + return; + } + try { + await payResponse.retry(validationErrors); + ok(false, `${testName}: Unexpected success when retry the PaymentResponse.`); + } catch(err) { + is(err.name, + "InvalidStateError", + expectedErrMsg(testName, "InvalidStateError", err.name, "retrying")); + } + await requestChromeAction("finish-test"); + } + + async function testRetryAfterRetry() { + const testName = "testRetryAfterRetry"; + await requestChromeAction("start-test", testName); + const payRequest = new PaymentRequest(defaultMethods, defaultDetails, options); + ok(payRequest, testName + ": failed to create PaymentRequest."); + if (!payRequest) { + await requestChromeAction("finish-test"); + return; + } + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + let payResponse; + try { + payResponse = await payRequest.show(); + await checkShowResponse(testName, payResponse); + handler.destruct(); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.name, "showing")); + await requestChromeAction("finish-test"); + handler.destruct(); + return; + } + let retryPromise; + try { + retryPromise = payResponse.retry(validationErrors); + await payResponse.retry(validationErrors); + ok(false, `${testName}: Unexpected success when retry the PaymentResponse.`); + await requestChromeAction("finish-test"); + return; + } catch(err) { + is(err.name, + "InvalidStateError", + expectedErrMsg(testName, "InvalidStateError", err.name, "retrying")); + } + try { + await retryPromise; + await payResponse.complete("success"); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.name, "completing")); + await requestChromeAction("finish-test"); + return; + } + await requestChromeAction("finish-test"); + } + + async function testRetryWithEmptyErrors() { + const testName = "testRetryWithEmptyErrors"; + await requestChromeAction("start-test", testName); + const payRequest = new PaymentRequest(defaultMethods, defaultDetails, options); + ok(payRequest, testName + ": failed to create PaymentRequest."); + if (!payRequest) { + requestChromeAction("finish-test"); + return; + } + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + let payResponse; + try { + payResponse = await payRequest.show(); + await checkShowResponse(testName, payResponse); + handler.destruct(); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.name, "showing")); + await requestChromeAction("finish-test"); + handler.destruct(); + return; + } + try { + await payResponse.retry(); + ok(false, `${testName}: Unexpected success when retry the PaymentResponse.`); + await requestChromeAction("finish-test"); + return; + } catch(err) { + is(err.name, + "AbortError", + expectedErrMsg(testName, "AbortError", err.name, "retrying")); + } + try { + await payResponse.complete("success"); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.name, "completing")); + await requestChromeAction("finish-test"); + return; + } + await requestChromeAction("finish-test"); + } + + async function testRetry() { + const testName = "testRetry"; + await requestChromeAction("start-test", testName); + const payRequest = new PaymentRequest(defaultMethods, defaultDetails, options); + ok(payRequest, testName + ": failed to create PaymentRequest."); + if (!payRequest) { + await requestChromeAction("finish-test"); + return; + } + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + let payResponse; + try { + payResponse = await payRequest.show(); + await checkShowResponse(testName, payResponse); + handler.destruct(); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.name, "showing")); + await requestChromeAction("finish-test"); + handler.destruct(); + return; + } + try { + await payResponse.retry(validationErrors); + await checkRetryResponse(testName, payResponse); + await payResponse.complete("success"); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.name, "retrying")); + await requestChromeAction("finish-test"); + return; + } + await requestChromeAction("finish-test"); + } + + async function testRetryAbortByUser() { + const testName = "testRetryAbortByUser"; + await requestChromeAction("reject-retry"); + const payRequest = new PaymentRequest(defaultMethods, defaultDetails, options); + ok(payRequest, testName + ": failed to create PaymentRequest."); + if (!payRequest) { + await requestChromeAction("finish-test"); + return; + } + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + let payResponse; + try { + payResponse = await payRequest.show(); + await checkShowResponse(testName, payResponse); + handler.destruct(); + } catch(err) { + ok(false, unexpectedErrMsg(testName, err.name, "showing")); + handler.destruct(); + await requestChromeAction("finish-test"); + return; + } + try { + await payResponse.retry(validationErrors); + ok(false, `${testName}: Unexpected success when retry the PaymentResponse.`); + await requestChromeAction("finish-test"); + return; + } catch(err) { + is(err.name, + "AbortError", + expectedErrMsg(testName, "AbortError", err.name, "retrying")); + } + await requestChromeAction("finish-test"); + } + + function teardown() { + return new Promise((resolve, reject) => { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler); + gScript.removeMessageListener("test-pass", testPassHandler); + gScript.destroy(); + SimpleTest.finish(); + resolve(); + }); + gScript.sendAsyncMessage("teardown"); + }); + } + + async function runTests() { + try { + await testRetryAfterComplete() + await testRetryAfterRetry() + await testRetryWithEmptyErrors() + await testRetry() + await testRetryAbortByUser() + await teardown() + } catch(e) { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + } + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1435161">Mozilla Bug 1435161</a> +</body> +</html> diff --git a/dom/payments/test/test_shippingOptions.html b/dom/payments/test/test_shippingOptions.html new file mode 100644 index 0000000000..887ec30de5 --- /dev/null +++ b/dom/payments/test/test_shippingOptions.html @@ -0,0 +1,208 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1440041 +https://bugzilla.mozilla.org/show_bug.cgi?id=1443914 +--> +<head> + <meta charset="utf-8"> + <title>Test for shippingOptions related bugs</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="./DefaultData.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + var gUrl = SimpleTest.getTestFileURL('ShippingOptionsChromeScript.js'); + var gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + function testPassHandler(message) { + ok(true, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + gScript.addMessageListener("test-pass", testPassHandler); + + let shippingOptions = [{ + id: "NormalShipping", + label: "NormalShipping", + amount: { + currency: "USD", + value: "10.00", + }, + selected: true, + },{ + id: "FastShipping", + label: "FastShipping", + amount: { + currency: "USD", + value: "5.00", + }, + selected: false, + }] + + // testing function main body + function testShippingOptionsTemplate(initDetails, + optionUpdateDetails, + expectedRequestOption, + expectedOptionChangeOption, + expectedResponseOption) { + const expectedResults = {requestResult: expectedRequestOption, + changeOptionResult: expectedOptionChangeOption, + responseResult: expectedResponseOption,}; + gScript.sendAsyncMessage("set-expected-results", expectedResults); + return new Promise((resolve, reject) => { + const request = new PaymentRequest(defaultMethods, initDetails, defaultOptions); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + is(request.shippingOption, expectedRequestOption, + "request.shippingOption should be " + expectedRequestOption + + " after created, but got " + request.shippingOption + "."); + if (optionUpdateDetails) { + request.addEventListener("shippingoptionchange", event => { + is(request.shippingOption, expectedOptionChangeOption, + "request.shippingOption should be " + expectedOptionChangeOption + + " in shippingoptionchange event, but got " + request.shippingOption + "."); + event.updateWith(optionUpdateDetails); + }); + } + request.show().then(response => { + is(response.shippingOption, expectedResponseOption, + "response.shippingOption should be " + expectedResponseOption + + ", but got " + response.shippingOption + "."); + response.complete("success").then(() => { + resolve(); + }).catch(error => { + ok(false, "Unexpected error: " + error.name); + resolve(); + }) + }, response => { + }).catch(error => { + ok(false, "Unexpected error: " + error.name); + resolve(); + }).finally(handler.destruct); + }); + } + + // test no selected shipping option in default + function testNoSelectedShippingOptions() { + return testShippingOptionsTemplate(defaultDetails, // initial details + null, // update details for optionchange + null, // expected request.shippintOption after create + null, // expected request.shippingOption after optionchange + null); // expected response.shippingOption + } + + // test select one shipping option in default + function testSelectedOneShippingOption() { + let details = Object.assign({}, defaultDetails); + details.shippingOptions = shippingOptions; + details.shippingOptions[0].selected = true; + details.shippingOptions[1].selected = false; + const expectedOption = details.shippingOptions[0].id; + return testShippingOptionsTemplate(details, // initial details + null, // update details for optionchange + expectedOption, // expected request.shippintOption after create + null, // expected request.shippingOption after optionchange + expectedOption); // expected response.shippingOption + } + + // test select multiple shipping options in default + function testMultiSelectedShippingOptions() { + let details = Object.assign({}, defaultDetails); + details.shippingOptions = shippingOptions; + details.shippingOptions[0].selected = true; + details.shippingOptions[1].selected = true; + const expectedOption = details.shippingOptions[1].id; + return testShippingOptionsTemplate(details, // initial details + null, // update details for optionchange + expectedOption, // expected request.shippintOption after create + null, // expected request.shippingOption after optionchange + expectedOption); // expected response.shippingOption + } + + // test no selected shipping option in default, but selected by user + function testSelectedByUser() { + let updateDetails = Object.assign({}, defaultDetails); + updateDetails.shippingOptions = shippingOptions; + updateDetails.shippingOptions[0].selected = true; + updateDetails.shippingOptions[1].selected = false; + const expectedOption = updateDetails.shippingOptions[0].id; + return testShippingOptionsTemplate(defaultDetails, // initial details + updateDetails, // update details for optionchange + null, // expected request.shippintOption after create + expectedOption, // expected request.shippingOption after optionchange + expectedOption); // expected response.shippingOption + } + + // test no selected shipping option in default, but selected by user then updated + // by merchant to the other. + function testUpdateSelectedByMerchant() { + let updateDetails = Object.assign({}, defaultDetails); + updateDetails.shippingOptions = shippingOptions; + updateDetails.shippingOptions[0].selected = false; + updateDetails.shippingOptions[1].selected = true; + const expectedOption = updateDetails.shippingOptions[0].id; + const expectedResponse = updateDetails.shippingOptions[1].id; + return testShippingOptionsTemplate(defaultDetails, // initial details + updateDetails, // update details for optionchange + null, // expected request.shippintOption after create + expectedOption, // expected request.shippingOption after optionchange + expectedResponse);// expected response.shippingOption + } + + // test update shipping options to null + function testUpdateShippingOptionsToNull() { + let updateDetails = Object.assign({}, defaultDetails); + delete updateDetails.shippingOptions; + const expectedOption = defaultDetails.shippingOptions[0].id; + return testShippingOptionsTemplate(defaultDetails, // initial details + updateDetails, // update details for optionchange + null, // expected request.shippintOption after create + expectedOption, // expected request.shippingOption after optionchange + null); // expected response.shippingOption + } + + function teardown() { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler); + gScript.removeMessageListener("test-pass", testPassHandler); + gScript.destroy(); + SimpleTest.finish(); + }); + gScript.sendAsyncMessage("teardown"); + } + + function runTests() { + testNoSelectedShippingOptions() + .then(testSelectedOneShippingOption) + .then(testMultiSelectedShippingOptions) + .then(testSelectedByUser) + .then(testUpdateSelectedByMerchant) + .then(testUpdateShippingOptionsToNull) + .then(teardown) + .catch( e => { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + }); + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1440041">Mozilla Bug 1440041</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1443914">Mozilla Bug 1443914</a> +</body> +</html> diff --git a/dom/payments/test/test_showPayment.html b/dom/payments/test/test_showPayment.html new file mode 100644 index 0000000000..2a4a0bb4f7 --- /dev/null +++ b/dom/payments/test/test_showPayment.html @@ -0,0 +1,504 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1345366 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1345366</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + var gUrl = SimpleTest.getTestFileURL('ShowPaymentChromeScript.js'); + var gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + function testPassHandler(message) { + ok(true, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + gScript.addMessageListener("test-pass", testPassHandler); + + async function requestChromeAction(action, params) { + await new Promise(resolve => { + gScript.addMessageListener(`${action}-complete`, function completeListener() { + gScript.removeMessageListener(`${action}-complete`, completeListener); + resolve(); + }); + gScript.sendAsyncMessage(action, params); + }); + } + + // testing data declaration + // default parameters for PaymentRequest construction + const defaultMethods = [{ + supportedMethods: "basic-card", + data: { + supportedNetworks: ['unionpay', 'visa', 'mastercard', 'amex', 'discover', + 'diners', 'jcb', 'mir', + ], + }, + }, { + supportedMethods: "testing-payment-method", + }]; + + const defaultTotal = { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + } + + const defaultDetails = { + id: "test payment", + total: defaultTotal, + shippingOptions: [ + { + id: "NormalShipping", + label: "NormalShipping", + amount: { + currency: "USD", + value: "10.00" + }, + selected: false, + }, + { + id: "FastShipping", + label: "FastShipping", + amount: { + currency: "USD", + value: "30.00" + }, + selected: false, + }, + ], + }; + + const defaultOptions = { + requestPayerName: true, + requestPayerEmail: false, + requestPayerPhone: false, + requestShipping: true, + shippingType: "shipping" + }; + + // testing data for PaymentRequestUpdateEvent.updateWith() + const updatedShippingOptionsDetails = { + total: defaultTotal, + shippingOptions: [ + { + id: "NormalShipping", + label: "NormalShipping", + amount: { + currency: "USD", + value: "10.00" + }, + selected: false, + }, + { + id: "FastShipping", + label: "FastShipping", + amount: { + currency: "USD", + value: "30.00" + }, + selected: true, + }, + ], + }; + + const updatedErrorDetails = { + total: defaultTotal, + error: "Update with Error", + }; + + // Promise function for PaymentRequestUpdateEvent.updateWith() + function updateWithPromise(detailsUpdate) { + return new Promise((resolve, reject) => { + if (detailsUpdate) { + resolve(detailsUpdate); + } else { + reject(); + } + }); + } + + // testing data for PaymentRequest.show() with Non-supported methods + const nonSupportedMethods = [{ + supportedMethods: "nonsupported-method", + }]; + + + // checking functions + function checkAddress(testName, address, fromEvent) { + is(address.country, + "USA", + `${testName}: address.country should be 'USA'.`); + is(address.region, + "CA", + `${testName}: address.region should be 'CA'.`); + is(address.city, + "San Bruno", + `${testName}: address.city should be 'San Bruno'.`); + is(address.dependentLocality, + "Test locality", + `${testName}: address.dependentLocality should be 'Test locality'.`); + is(address.postalCode, + "94066", + `${testName}: address.postalCode should be '94066'.`); + is(address.sortingCode, + "123456", + `${testName}: address.sortingCode should be '123456'.`); + if (fromEvent) { + is(address.addressLine.length, + 0, + `${testName}: address.addressLine.length should be 0 from event.`); + is(address.organization, + "", + `${testName}: address.organization should be empty from event.`); + is(address.recipient, + "", + `${testName}: address.recipient should be empty from event.`); + is(address.phone, + "", + `${testName}: address.phone should be empty from event.`); + } else { + is(address.addressLine.length, + 1, + `${testName}: address.addressLine.length should be 1 from promise.`); + is(address.addressLine[0], + "Easton Ave", + `${testName}: address.addressLine[0] should be 'Easton Ave' from promise.`); + is(address.organization, + "Testing Org", + `${testName}: address.organization should be 'Testing Org' from promise.`); + is(address.recipient, + "Bill A. Pacheco", + `${testName}: address.recipient should be 'Bill A. Pacheco' from promise.`); + is(address.phone, + "+1-434-441-3879", + `${testName}: address.phone should be '+1-434-441-3879' from promise.`); + } + } + + function checkResponse(testName, response) { + is(response.requestId, + "test payment", + `${testName}: response.requestId should be 'test payment'.`); + is(response.methodName, + "testing-payment-method", + `${testName}: response.methodName should be 'testing-payment-method'.`); + is(response.details.paymentToken, + "6880281f-0df3-4b8e-916f-66575e2457c1", + `${testName}: response.details.paymentToken should be '6880281f-0df3-4b8e-916f-66575e2457c1'.`); + checkAddress(testName, response.shippingAddress, false/*fromEvent*/); + is(response.shippingOption, + "FastShipping", + `${testName}: response.shippingOption should be 'FastShipping'.`); + is(response.payerName, + "Bill A. Pacheco", + `${testName}: response.payerName should be 'Bill A. Pacheco'.`); + ok(!response.payerEmail, + `${testName}: response.payerEmail should be empty`); + ok(!response.payerPhone, + `${testName}: response.payerPhone should be empty`); + } + + // testing functions + async function testShowNormalFlow() { + const testName = "testShowNormalFlow"; + await requestChromeAction("set-normal-ui-service", testName); + + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + request.addEventListener("shippingaddresschange", event => { + checkAddress(testName, request.shippingAddress, true/*fromEvent*/); + event.updateWith(updateWithPromise(defaultDetails)); + }); + request.addEventListener("shippingoptionchange", event => { + event.updateWith(updateWithPromise(updatedShippingOptionsDetails)); + }); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + try { + let response = await request.show(); + checkResponse(testName, response, false); + await response.complete(); + } catch (error) { + ok(false, `${testName} Unexpected error: ${e.name}`); + } + await handler.destruct(); + } + + // testing show with nonsupported methods + async function testCannotMakePaymentShow() { + const testName = "testCannotMakePaymentShow"; + await requestChromeAction("set-simple-ui-service", testName); + + const request = new PaymentRequest(nonSupportedMethods, defaultDetails); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + let result = await request.canMakePayment(); + ok(!result, `${testName}: canMakePayment() should return false.`); + try { + await request.show(); + ok(false, `${testName}: should be rejected with 'NotSupportedError' but got resolved.`); + } catch (error) { + is(error.name, "NotSupportedError", `${testName}: should be rejected with 'NotSupportedError'.`); + } + await handler.destruct(); + } + + // testing show rejected by user + async function testRejectShow() { + const testName = "testRejectShow"; + await requestChromeAction("set-reject-ui-service", testName); + + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + try { + await request.show(); + ok(false, `${testName}: Should be rejected with 'AbortError' but got resolved.`); + } catch(error) { + is(error.name, "AbortError", `${testName}: Should be rejected with 'AbortError'.`); + } + await handler.destruct(); + } + + // testing PaymentResponse.complete() with specified result + async function testCompleteStatus(testName, result) { + await requestChromeAction("set-simple-ui-service", testName); + if (result) { + await requestChromeAction(`set-complete-status-${result}`); + } else { + await requestChromeAction(`set-complete-status-unknown`); + } + + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + try { + let response = await request.show(); + await response.complete(result); + } catch (error) { + ok(false, `${testName}: Unexpected error ${error.name}.`); + } + await handler.destruct(); + } + + async function testCompleteFail() { + const testName = "testCompleteFail"; + return testCompleteStatus(testName, "fail"); + } + + async function testCompleteSuccess() { + const testName = "testCompleteSuccess"; + return testCompleteStatus(testName, "success"); + } + + async function testCompleteUnknown() { + const testName = "testCompleteUnknown" + return testCompleteStatus(testName, "unknown"); + } + + async function testCompleteEmpty() { + const testName = "testCompleteEmpty"; + return testCompleteStatus(testName); + } + + // testing PaymentRequestUpdateEvent.updateWith with specified details and error + async function testUpdateWith(testName, detailsUpdate, expectedError) { + if (expectedError) { + await requestChromeAction("set-update-with-error-ui-service", testName); + } else { + await requestChromeAction("set-update-with-ui-service", testName); + } + + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + request.addEventListener("shippingaddresschange", event => { + event.updateWith(updateWithPromise(detailsUpdate)); + }); + request.addEventListener("shippingoptionchange", event => { + event.updateWith(updateWithPromise(detailsUpdate)); + }); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + try { + const response = await request.show(); + if (expectedError) { + ok(false, `${testName}: Should be rejected with ${expectedError} but got resolved.`); + } else { + await response.complete("success"); + } + } catch(error) { + if (expectedError) { + is(error.name, expectedError, `${testName}: Should be rejected with ${expectedError}.`); + } else { + ok(false, `${testName}: Unexpected error ${error.name}.`); + } + } + await handler.destruct(); + } + + async function testUpdateWithReject() { + const testName = "testUpdateWithReject"; + return testUpdateWith(testName, null, "AbortError"); + } + + async function testUpdateWithValidDetails() { + const testName = "testUpdateWithValidDetails"; + return testUpdateWith(testName, updatedShippingOptionsDetails, null); + } + + async function testUpdateWithInvalidDetails() { + const testName = "testUpdateWithInvalidDetails"; + return testUpdateWith(testName, {total: "invalid details"}, "TypeError"); + } + + async function testUpdateWithError() { + const testName = "testUpdateWithError"; + return testUpdateWith(testName, updatedErrorDetails, "AbortError"); + } + + // testing show with detailsUpdate promise + async function testShowWithDetailsPromise(testName, detailsUpdate, expectedError) { + if (expectedError) { + await requestChromeAction("set-reject-ui-service", testName); + } else { + await requestChromeAction("set-simple-ui-service", testName); + } + + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + ok(!request.shippingOption, `${testName}: request.shippingOption should be null.`); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + try { + let response = await request.show(updateWithPromise(detailsUpdate)); + if (expectedError) { + ok(false, `${testName}: Should be rejected with ${expectedError} but got resolved.`); + } else { + ok(response.shippingOption, + `${testName}: response.shippingOption should not be null.`); + } + await response.complete(); + } catch(error) { + if (expectedError) { + is(error.name, expectedError, `${testName}: Should be rejected with ${expectedError}.`); + } else { + ok(false, `${testName}: Unexpected error ${error.name}.`); + } + } + await handler.destruct(); + } + async function testShowWithValidPromise() { + const testName = "testShowWithValidPromise"; + return testShowWithDetailsPromise(testName, updatedShippingOptionsDetails, null); + } + + async function testShowWithRejectedPromise() { + const testName = "testShowWithRejectedPromise"; + return testShowWithDetailsPromise(testName, null, "AbortError"); + } + + async function testShowWithInvalidPromise() { + const testName = "testShowWithInvalidPromise"; + return testShowWithDetailsPromise(testName, {total: "invalid details"}, "TypeError"); + } + + async function testShowWithErrorPromise() { + const testName = "testShowWithErrorPromise"; + return testShowWithDetailsPromise(testName, updatedErrorDetails, "AbortError"); + } + + async function testShowWithPromiseResolvedByRejectedPromise() { + const testName = "testShowWithPromiseResolvedByRejectedPromise"; + await requestChromeAction("set-reject-ui-service", testName); + + const request = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + let rejectPromise = Promise.reject(new TypeError()); + let detailsUpdatePromise = Promise.resolve(rejectPromise); + try { + await request.show(detailsUpdatePromise); + ok(false, `${testName}: should be rejected with AbortError but got resolved.`); + } catch(error) { + is(error.name, "AbortError", `${testName}: should be rejected with AbortError.`); + } + await handler.destruct(); + } + + // testing show response initialization in chrome process + async function testShowResponseInit() { + const testName = "testShowResponseInit"; + await requestChromeAction("test-show-response-init", testName); + } + + // testing show that is not triggered by user. + async function testShowNotTriggeredByUser() { + const testName = "testShowNotTriggeredByUser"; + await requestChromeAction("set-simple-ui-service", testName); + + const request = new PaymentRequest(defaultMethods, defaultDetails); + try { + await request.show(); + ok(false, `${testName}: should be rejected with SecurityError, but got resolved.`); + } catch (error) { + is(error.name, "SecurityError", `${testName}: should be rejected with SecurityError.`); + } + } + + // teardown function + async function teardown() { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler); + gScript.removeMessageListener("test-pass", testPassHandler); + gScript.destroy(); + SimpleTest.finish(); + }); + gScript.sendAsyncMessage("teardown"); + } + + // test main body + async function runTests() { + try { + await testCannotMakePaymentShow(); + await testRejectShow(); + await testShowNormalFlow(); + await testCompleteSuccess(); + await testCompleteFail(); + await testCompleteUnknown(); + await testCompleteEmpty(); + await testUpdateWithReject(); + await testUpdateWithValidDetails(); + await testUpdateWithInvalidDetails(); + await testUpdateWithError(); + await testShowWithValidPromise(); + await testShowWithInvalidPromise(); + await testShowWithRejectedPromise(); + await testShowWithErrorPromise(); + await testShowWithPromiseResolvedByRejectedPromise(); + await testShowResponseInit(); + await testShowNotTriggeredByUser(); + await teardown(); + } catch (error) { + ok(false, `test_showPayment: Unexpected error: ${error.name}`); + SimpleTest.finish(); + } + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1345366">Mozilla Bug 1345366</a> +</body> +</html> diff --git a/dom/payments/test/test_update_errors.html b/dom/payments/test/test_update_errors.html new file mode 100644 index 0000000000..a473cf2706 --- /dev/null +++ b/dom/payments/test/test_update_errors.html @@ -0,0 +1,121 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1435157 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1435157</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="DefaultData.js"></script> + <script type="application/javascript"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + var gUrl = SimpleTest.getTestFileURL('UpdateErrorsChromeScript.js'); + var gScript = SpecialPowers.loadChromeScript(gUrl); + + function testFailHandler(message) { + ok(false, message); + } + function testPassHandler(message) { + ok(true, message); + } + gScript.addMessageListener("test-fail", testFailHandler); + gScript.addMessageListener("test-pass", testPassHandler); + + const addressErrors = { + addressLine: "addressLine error", + city: "city error", + country: "country error", + dependentLocality: "dependentLocality error", + organization: "organization error", + phone: "phone error", + postalCode: "postalCode error", + recipient: "recipient error", + region: "region error", + regionCode: "regionCode error", + sortingCode: "sortingCode error", + }; + + const payErrors = { + email: "email error", + name: "name error", + phone: "phone error", + }; + + let updateDetails = { + total:{ + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, + erros: "shipping address error", + shippingAddressErrors: addressErrors, + payerErrors: payErrors, + } + + // testing functions + function testUpdateErrors() { + return new Promise((resolve, reject) => { + const payRequest = new PaymentRequest(defaultMethods, defaultDetails, defaultOptions); + payRequest.addEventListener("shippingaddresschange", event => { + event.updateWith(updateDetails); + }); + payRequest.addEventListener("shippingoptionchange", event => { + event.updateWith(updatedDetails); + }); + const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true); + payRequest.show().then(response => { + ok(false, "Expected AbortError, but got pass"); + resolve(); + }, error => { + is(error.name, "AbortError", "Expect AbortError, but got " + error.name); + resolve(); + }).catch( e => { + ok(false, "Unexpected error: " + e.name); + resolve(); + }).finally(handler.destruct); + }); + } + + // teardown function + function teardown() { + gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() { + gScript.removeMessageListener("teardown-complete", teardownCompleteHandler); + gScript.removeMessageListener("test-fail", testFailHandler); + gScript.removeMessageListener("test-pass", testPassHandler); + gScript.destroy(); + SimpleTest.finish(); + }); + gScript.sendAsyncMessage("teardown"); + } + + // test main body + function runTests() { + testUpdateErrors() + .then(teardown) + .catch( e => { + ok(false, "Unexpected error: " + e.name); + SimpleTest.finish(); + }); + } + + window.addEventListener('load', function() { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.payments.request.enabled', true], + ] + }, runTests); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1435157">Mozilla Bug 1435157</a> +</body> +</html> |