summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent')
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-call-immediate-manual.https.html206
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-duplicate-shipping-options-manual.https.html106
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html196
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-method-abort-update-manual.https.html286
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-state-checks-manual.https.html125
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html8
6 files changed, 925 insertions, 2 deletions
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-call-immediate-manual.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-call-immediate-manual.https.html
new file mode 100644
index 0000000000..1365ecefeb
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-call-immediate-manual.https.html
@@ -0,0 +1,206 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://www.w3.org/TR/payment-request/#updatewith()-method">
+<link rel="help" href="https://github.com/w3c/payment-request/pull/591">
+<title>
+ PaymentRequestUpdateEvent.updateWith() needs to be called immediately
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const validMethod = Object.freeze({ supportedMethods: "basic-card" });
+const validMethods = Object.freeze([validMethod, applePay]);
+const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+const validTotal = Object.freeze({
+ label: "label",
+ amount: validAmount,
+});
+const validShippingOptionA = Object.freeze({
+ id: "a-shipping-option",
+ label: "A shipping option",
+ amount: validAmount,
+ selected: true,
+});
+const validShippingOptionB = Object.freeze({
+ id: "b-shipping-option",
+ label: "B shipping option",
+ amount: validAmount,
+});
+const validDetails = Object.freeze({
+ total: validTotal,
+ shippingOptions: [validShippingOptionA, validShippingOptionB],
+});
+const validOptions = Object.freeze({
+ requestShipping: true,
+});
+
+function testImmediateUpdate({ textContent: testName }) {
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ validOptions
+ );
+ const eventPromise = new Promise((resolve, reject) => {
+ request.addEventListener(
+ "shippingaddresschange",
+ ev => {
+ // Forces updateWith() to be run in the next event loop tick so that
+ // [[waitForUpdate]] is already true when it runs.
+ t.step_timeout(() => {
+ try {
+ ev.updateWith(validDetails);
+ resolve(); // This is bad.
+ } catch (err) {
+ reject(err); // this is good.
+ }
+ });
+ },
+ { once: true }
+ );
+ });
+ const acceptPromise = request.show();
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ eventPromise,
+ "The event loop already spun, so [[waitForUpdate]] is now true"
+ );
+ const response = await acceptPromise;
+ await response.complete();
+ }, testName.trim());
+}
+
+function testSubsequentUpdateWithCalls({ textContent: testName }) {
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ validOptions
+ );
+ const eventPromise = new Promise((resolve, reject) => {
+ request.addEventListener("shippingaddresschange", async ev => {
+ const p = Promise.resolve(validDetails);
+ ev.updateWith(p);
+ await p;
+ try {
+ ev.updateWith(validDetails);
+ resolve(); // this is bad, we should never get to here.
+ } catch (err) {
+ reject(err); // this is good!
+ }
+ });
+ });
+ const responsePromise = request.show();
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ eventPromise,
+ "Expected eventPromise to have rejected, because updateWith() was a called twice"
+ );
+ const response = await responsePromise;
+ await response.complete();
+ }, testName.trim());
+}
+
+function testRecycleEvents({ textContent: testName }) {
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ validOptions
+ );
+
+ // Register both listeners.
+ const addressChangedPromise = new Promise(resolve => {
+ request.addEventListener("shippingaddresschange", resolve, {
+ once: true,
+ });
+ });
+
+ const optionChangedPromise = new Promise(resolve => {
+ request.addEventListener("shippingoptionchange", resolve, {
+ once: true,
+ });
+ });
+
+ const responsePromise = request.show();
+
+ // Let's wait for the address to change.
+ const addressChangeEvent = await addressChangedPromise;
+
+ // Sets [[waitingForUpdate]] to true.
+ addressChangeEvent.updateWith(validDetails);
+
+ // Let's wait for the shippingOption.
+ const optionChangeEvent = await optionChangedPromise;
+
+ // Here, we try to be sneaky, and reuse the addressChangeEvent to perform the update.
+ // However, addressChangeEvent [[waitingForUpdate]] is true, so it throws.
+ assert_throws_dom(
+ "InvalidStateError",
+ () => {
+ addressChangeEvent.updateWith(validDetails);
+ },
+ "addressChangeEvent [[waitingForUpdate]] is true, so it must throw"
+ );
+
+ // But optionChangeEvent is still usable tho, so...
+ optionChangeEvent.updateWith(validDetails);
+
+ assert_throws_dom(
+ "InvalidStateError",
+ () => {
+ optionChangeEvent.updateWith(validDetails);
+ },
+ "optionChangeEvent [[waitingForUpdate]] is true, so it must throw"
+ );
+
+ const response = await responsePromise;
+ await response.complete();
+ }, testName.trim());
+}
+</script>
+<h2>updateWith() method</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is shown, select a different shipping address once. Then pay.
+</p>
+<ol>
+ <li id="test-0">
+ <button onclick="testImmediateUpdate(this);">
+ updateWith() must be called immediately, otherwise must throw an InvalidStateError.
+ </button>
+ </li>
+ <li id="test-1">
+ <button onclick="testSubsequentUpdateWithCalls(this);">
+ Once the event has performed an update, subsequent calls to updateWith() must throw InvalidStateError.
+ </button>
+ </li>
+ <li id="test-2">
+ <button onclick="testRecycleEvents(this);">
+ Recycling events must not be possible.
+ </button> When the payment sheet is shown, select a different shipping address once, then change shipping option once. Then pay.
+ </li>
+ <li>
+ <button onclick="done();">Done!</button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-duplicate-shipping-options-manual.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-duplicate-shipping-options-manual.https.html
new file mode 100644
index 0000000000..a4a7afd7f6
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-duplicate-shipping-options-manual.https.html
@@ -0,0 +1,106 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#updatewith()-method">
+<title>
+ updateWith() method - duplicate shippingOption ids
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const validMethod = Object.freeze({ supportedMethods: "basic-card" });
+const validMethods = [validMethod, applePay];
+const validAmount = Object.freeze({
+ currency: "USD",
+ value: "5.00",
+});
+const validShippingOption = Object.freeze({
+ id: "option1",
+ label: "Option 1",
+ amount: validAmount,
+ selected: true,
+});
+const validShippingOptions = Object.freeze([validShippingOption]);
+const validDetails = Object.freeze({
+ total: {
+ label: "Total due",
+ amount: validAmount,
+ },
+ shippingOptions: validShippingOptions,
+});
+const validOptions = Object.freeze({
+ requestShipping: true,
+});
+
+test(() => {
+ try {
+ const request = new PaymentRequest(validMethods, validDetails);
+ } catch (err) {
+ done();
+ throw err;
+ }
+}, "Must construct a PaymentRequest (smoke test)");
+
+function testFireEvents(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ validOptions
+ );
+ request.addEventListener("shippingaddresschange", event => {
+ // Same option, so duplicate ids
+ const otherShippingOption = Object.assign({}, validShippingOption, {
+ id: "other",
+ });
+ const shippingOptions = [
+ validShippingOption,
+ otherShippingOption,
+ validShippingOption,
+ ];
+ const newDetails = Object.assign({}, validDetails, { shippingOptions });
+ event.updateWith(newDetails);
+ });
+ const acceptPromise = request.show();
+ await promise_rejects_js(
+ t,
+ TypeError,
+ acceptPromise,
+ "Duplicate shippingOption ids must abort with TypeError"
+ );
+ }, button.textContent.trim());
+ done();
+}
+</script>
+<h2>updateWith() method - duplicate shippingOptions ids</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is shown, select a different shipping address.
+ If you have to manually abort the test from the payment sheet, then the
+ test has failed.
+</p>
+<ol>
+ <li>
+ <button onclick="testFireEvents(this)">
+ If there are duplicate shippingOption ids, then abort payment request.
+ </button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html
new file mode 100644
index 0000000000..c1ed1b5f68
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html
@@ -0,0 +1,196 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#updatewith-method">
+<title>
+ Incremental updates via updateWith()
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({
+ explicit_done: true,
+ explicit_timeout: true,
+});
+
+const methods = [{
+ supportedMethods: "basic-card",
+}, {
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+}];
+
+const options = {
+ requestShipping: true,
+};
+
+const initialDetails = {
+ total: {
+ label: "Initial total",
+ amount: {
+ currency: "USD",
+ value: "1.0",
+ },
+ },
+ shippingOptions: [
+ {
+ id: "neutral",
+ label: "NEUTRAL SHIPPING OPTION",
+ selected: true,
+ amount: {
+ currency: "USD",
+ value: "0.00",
+ },
+ },
+ ],
+};
+
+function testFireEvent(button, updateDetails) {
+ button.disabled = true;
+ const request = new PaymentRequest(methods, initialDetails, options);
+ const handlerPromise = new Promise(resolve => {
+ request.onshippingaddresschange = event => {
+ event.updateWith(updateDetails);
+ resolve(event);
+ };
+ });
+ promise_test(async t => {
+ const response = await request.show();
+ const event = await handlerPromise;
+ await response.complete("success");
+ }, button.textContent.trim());
+}
+
+</script>
+<h2>
+ Incremental updates
+</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ Unless stated otherwise, each test will update some part of the displayed payment sheet in
+ a manner indicated below. When prompted, please change or enter a new
+ shipping address, look for the tested change, and complete the payment.
+</p>
+<p>
+ If the payment request locks up or otherwise aborts, the test has failed.
+</p>
+<ol>
+ <li>
+ <button onclick="testFireEvent(this, {});">
+ Passing an empty dictionary does not cause the sheet to change.
+ No values in the sheet must change.
+ </button>
+ </li>
+</ol>
+
+<section>
+ <h3>Incremental updates via PaymentDetailsUpdate.total</h3>
+ <ol>
+ <li>
+ <button onclick="
+ const total = {
+ label: 'PASS',
+ amount: {
+ currency: 'XXX',
+ value: '20',
+ },
+ };
+ const updatedDetails = { total };
+ testFireEvent(this, updatedDetails);">
+ After changing shipping address, the total becomes XXX20, with the label "PASS".
+ </button>
+ </li>
+ </ol>
+</section>
+
+<section>
+ <h3>Incremental updates via PaymentDetailsBase.displayItems</h3>
+ <ol>
+ <li>
+ <button onclick="
+ const item = {
+ label: 'PASS',
+ amount: { currency: 'ABC', value: '55.00' },
+ };
+ const updatedDetails = {
+ displayItems: [ item ]
+ };
+ testFireEvent(this, updatedDetails);">
+ After changing shipping address, a new display item is shown
+ with a with label PASS, and value of ABC55.00.
+ </button>
+ </li>
+ </ol>
+</section>
+
+<section>
+ <h3>Incremental updates via PaymentDetailsBase.shippingOptions</h3>
+ <ol>
+ <li>
+ <button onclick="
+ const shippingOptions = [
+ {
+ id: 'pass',
+ label: 'PASS',
+ amount: { currency: 'USD', value: '1.00' },
+ selected: true,
+ },
+ {
+ id: 'fail',
+ label: 'FAIL IF THIS IS SELECTED',
+ amount: { currency: 'USD', value: '25.00' }
+ },
+ ];
+ const updatedDetails = {
+ shippingOptions
+ };
+ testFireEvent(this, updatedDetails);">
+ After changing shipping address, two new shipping options appear.
+ The shipping option labelled "PASS" with a value of USD1.0 is selected.
+ </button>
+ </li>
+ </ol>
+</section>
+
+<section>
+ <h3>Incremental updates via PaymentDetailsBase.modifiers</h3>
+ <ol>
+ <li>
+ <button onclick="
+ const additionalItem = {
+ label: 'PASS-DISPLAY-ITEM',
+ amount: { currency: 'USD', value: '3.00' },
+ };
+ const modifiers = [{
+ additionalDisplayItems: [ additionalItem ],
+ supportedMethods: 'basic-card',
+ total: {
+ label: 'PASS-TOTAL',
+ amount: { currency: 'USD', value: '123.00' },
+ },
+ }];
+ const updatedDetails = { modifiers };
+ testFireEvent(this, updatedDetails);">
+ After changing shipping address, a new display item is shown
+ with a with label PASS-DISPLAY-ITEM, and value of ABC55.00 and the total is
+ labelled PASS-TOTAL with a value of USD123.0
+ </button>
+ </li>
+ <li>
+ <button onclick="done()">DONE - see results</button>
+ </li>
+ </ol>
+</section>
+
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-method-abort-update-manual.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-method-abort-update-manual.https.html
new file mode 100644
index 0000000000..e24452c2a9
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-method-abort-update-manual.https.html
@@ -0,0 +1,286 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dfn-abort-the-update">
+<title>
+ updateWith() method - "abort the update"
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+
+// PaymentMethod
+const validMethod = Object.freeze({
+ supportedMethods: "valid-but-wont-ever-match",
+});
+
+const validMethodBasicCard = Object.freeze({
+ supportedMethods: "basic-card",
+});
+
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+
+// Methods
+const validMethods = Object.freeze([validMethodBasicCard, validMethod, applePay]);
+
+// Amounts
+const validAmount = Object.freeze({
+ currency: "USD",
+ value: "1.00",
+});
+
+const invalidAmount = Object.freeze({
+ currency: "¡INVALID!",
+ value: "A1.0",
+});
+
+const negativeAmount = Object.freeze({
+ currency: "USD",
+ value: "-1.00",
+});
+
+// Totals
+const validTotal = Object.freeze({
+ label: "Valid Total",
+ amount: validAmount,
+});
+
+const invalidTotal = Object.freeze({
+ label: "Invalid Total",
+ amount: invalidAmount,
+});
+
+const invalidNegativeTotal = Object.freeze({
+ label: "Invalid negative total",
+ amount: negativeAmount,
+});
+
+// PaymentDetailsInit
+const validDetails = Object.freeze({
+ total: validTotal,
+});
+
+const invalidDetailsNegativeTotal = Object.freeze({
+ total: invalidNegativeTotal,
+});
+
+// PaymentOptions
+const validOptions = Object.freeze({
+ requestShipping: true,
+});
+
+// PaymentItem
+const validPaymentItem = Object.freeze({
+ amount: validAmount,
+ label: "Valid payment item",
+});
+
+const invalidPaymentItem = Object.freeze({
+ amount: invalidAmount,
+ label: "Invalid payment item",
+});
+
+// PaymentItem
+const validPaymentItems = Object.freeze([validPaymentItem]);
+const invalidPaymentItems = Object.freeze([invalidPaymentItem]);
+
+// PaymentShippingOption
+const invalidShippingOption = Object.freeze({
+ id: "abc",
+ label: "Invalid shipping option",
+ amount: invalidAmount,
+ selected: true,
+});
+
+// PaymentShippingOptions
+const validShippingOption = Object.freeze({
+ id: "abc",
+ label: "valid shipping option",
+ amount: validAmount,
+});
+
+const validShippingOptions = Object.freeze([validShippingOption]);
+const invalidShippingOptions = Object.freeze([invalidShippingOption]);
+
+// PaymentDetailsModifier
+const validModifier = Object.freeze({
+ additionalDisplayItems: validPaymentItems,
+ supportedMethods: "valid-but-wont-ever-match",
+ total: validTotal,
+});
+
+const modifierWithInvalidDisplayItems = Object.freeze({
+ additionalDisplayItems: invalidPaymentItems,
+ supportedMethods: "basic-card",
+ total: validTotal,
+});
+
+const modifierWithValidDisplayItems = Object.freeze({
+ additionalDisplayItems: validPaymentItems,
+ supportedMethods: "basic-card",
+ total: validTotal,
+});
+
+const modifierWithInvalidTotal = Object.freeze({
+ additionalDisplayItems: validPaymentItems,
+ supportedMethods: "basic-card",
+ total: invalidTotal,
+});
+
+const recursiveData = {};
+recursiveData.foo = recursiveData;
+Object.freeze(recursiveData);
+
+const modifierWithRecursiveData = Object.freeze({
+ supportedMethods: "basic-card",
+ total: validTotal,
+ data: recursiveData,
+});
+
+function testBadUpdate(button, badDetails, expectedError, errorCode) {
+ button.disabled = true;
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ validOptions
+ );
+ request.onshippingaddresschange = event => {
+ event.updateWith(badDetails);
+ };
+ // First we check the bad update.
+ const acceptPromise = request.show();
+ let test_func;
+ if (typeof expectedError == "function") {
+ test_func = promise_rejects_js;
+ } else {
+ test_func = promise_rejects_dom;
+ }
+ await test_func(
+ t,
+ expectedError,
+ acceptPromise,
+ "badDetails must cause acceptPromise to reject with expectedError"
+ );
+ // The request [[state]] is now "closed", so let's check for InvalidStateError
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ request.show(),
+ "show() must reject with InvalidStateError"
+ );
+ }, button.innerText.trim());
+}
+</script>
+<h2>updateWith() method - "abort the update"</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is shown, change the shipping address.
+</p>
+<ol>
+ <li>
+ <button onclick="
+ const rejectedPromise = Promise.reject(new SyntaxError('test'));
+ testBadUpdate(this, rejectedPromise, 'AbortError');
+ ">
+ Rejection of detailsPromise must abort the update with an "AbortError" DOMException.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const invalidDetails = { total: `this will cause a TypeError!` };
+ testBadUpdate(this, invalidDetails, TypeError);
+ ">
+ Total in the update is a string, so converting to IDL must abort the update with a TypeError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const invalidDetails = { total: recursiveData };
+ testBadUpdate(this, invalidDetails, TypeError);
+ ">
+ Total is recursive, so converting to IDL must abort the update with a TypeError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ testBadUpdate(this, invalidDetailsNegativeTotal, TypeError);
+ ">
+ Updating with a negative total results in a TypeError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const badDetails = Object.assign({}, validDetails, { displayItems: invalidPaymentItems });
+ testBadUpdate(this, badDetails, RangeError);
+ ">
+ Updating with a displayItem with an invalid currency results in RangeError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const duplicateShippingOptions = [validShippingOption, validShippingOption];
+ const badDetails = Object.assign({}, validDetails, { shippingOptions: duplicateShippingOptions });
+ testBadUpdate(this, badDetails, TypeError);
+ ">
+ Updating with duplicate shippingOptions (same IDs) results in a TypeError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const badDetails = Object.assign({}, validDetails, { shippingOptions: invalidShippingOptions });
+ testBadUpdate(this, badDetails, RangeError);
+ ">
+ Updating with a shippingOption with an invalid currency value results in a RangError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ // validModifier is there as to avoid false positives - it should just get ignored
+ const badModifiers = { modifiers: [ modifierWithInvalidTotal, validModifier ] };
+ const badDetails = Object.assign({}, validDetails, badModifiers);
+ testBadUpdate(this, badDetails, RangeError);
+ ">
+ Must throw a RangeError when a modifier's total item has an invalid currency.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ // validModifier is there as to avoid false positives - it should just get ignored
+ const badModifiers = { modifiers: [ modifierWithInvalidDisplayItems, validModifier ] };
+ const badDetails = Object.assign({}, validDetails, badModifiers);
+ testBadUpdate(this, badDetails, RangeError);
+ ">
+ Must throw a RangeError when a modifier display item has an invalid currency.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ // validModifier is there as to avoid false positives - it should just get ignored
+ const badModifiers = { modifiers: [ modifierWithRecursiveData, validModifier ] };
+ const badDetails = Object.assign({}, validDetails, badModifiers);
+ testBadUpdate(this, badDetails, TypeError);
+ ">
+ Must throw as Modifier has a recursive dictionary.
+ </button>
+ </li>
+ <li>
+ <button onclick="done();">Done!</button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-state-checks-manual.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-state-checks-manual.https.html
new file mode 100644
index 0000000000..fb16de5699
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-state-checks-manual.https.html
@@ -0,0 +1,125 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://www.w3.org/TR/payment-request/#updatewith()-method">
+<title>updateWith() method - state machine checks</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const validMethod = Object.freeze({ supportedMethods: "basic-card" });
+const validMethods = Object.freeze([validMethod, applePay]);
+const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+const validTotal = Object.freeze({
+ label: "label",
+ amount: validAmount,
+});
+const validShippingOption = Object.freeze({
+ id: "a-shipping-option",
+ label: "A shipping option",
+ amount: validAmount,
+ selected: true,
+});
+const validDetails = Object.freeze({
+ total: validTotal,
+ shippingOptions: [validShippingOption],
+});
+const validOptions = Object.freeze({
+ requestShipping: true,
+});
+
+function getPaymentPromises() {
+ const request = new PaymentRequest(validMethods, validDetails, validOptions);
+ const eventPromise = new Promise(resolve => {
+ request.addEventListener("shippingaddresschange", resolve);
+ });
+ const responsePromise = request.show();
+ return { eventPromise, responsePromise };
+}
+
+function testRequestIsClosed(button) {
+ button.disabled = "true";
+ promise_test(async t => {
+ const { eventPromise, responsePromise } = getPaymentPromises();
+ const event = await eventPromise;
+ // We are going to abort the responsePromise, so we can ignore error.
+ responsePromise.catch(err => err);
+ // Set request.[[state]] to closed
+ await event.target.abort();
+ assert_throws_dom(
+ "InvalidStateError",
+ () => {
+ event.updateWith(validDetails);
+ },
+ "request.[[state]] is not interactive, must throw an InvalidStateError."
+ );
+ responsePromise.catch(err => err);
+ }, button.textContent.trim());
+}
+
+function testRequestIsUpdating(button) {
+ button.disabled = "true";
+ promise_test(async t => {
+ const { eventPromise, responsePromise } = getPaymentPromises();
+ const event = await eventPromise;
+ // We are going to put a promise into a pending state
+ // check that a second call to updateWith() throws,
+ // then resolve the pending promise below.
+ let resolver;
+ const pendingPromise = new Promise(resolve => {
+ resolver = resolve;
+ });
+ // Set request.[[updating]] to true
+ event.updateWith(pendingPromise);
+ assert_throws_dom(
+ "InvalidStateError",
+ () => {
+ event.updateWith(validDetails);
+ },
+ "request.[[updating]] to true, must throw an InvalidStateError."
+ );
+ // We got the error we wanted, so let's resolve with valid details.
+ resolver(validDetails);
+ await pendingPromise;
+ await event.target.abort();
+ responsePromise.catch(err => err);
+ }, button.textContent.trim());
+}
+
+</script>
+<h2>updateWith() method - state machine checks</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is shown, select a different shipping address once. Then pay.
+</p>
+<ol>
+ <li id="test-0">
+ <button onclick="testRequestIsClosed(this);">
+ When updateWith() is called, if request.[[state]] is not "interactive", then throw an " InvalidStateError" DOMException.
+ </button>
+ </li>
+ <li id="test-1">
+ <button onclick="testRequestIsUpdating(this);">
+ When updateWith() is called, If request.[[updating]] is true, then throw an "InvalidStateError" DOMException.
+ </button>
+ </li>
+ <li>
+ <button onclick="done();">Done!</button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html
index 9a60fe7a4c..fffd3b3ec5 100644
--- a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html
@@ -29,7 +29,9 @@ test(() => {
// Github issue: https://github.com/w3c/browser-payment-api/issues/546
test(() => {
const untrustedEvents = [
- new PaymentRequestUpdateEvent("just a test")
+ new PaymentRequestUpdateEvent("just a test"),
+ new PaymentRequestUpdateEvent("shippingaddresschange"),
+ new PaymentRequestUpdateEvent("shippingoptionchange"),
].forEach(ev => {
assert_throws_dom(
"InvalidStateError",
@@ -45,7 +47,9 @@ test(() => {
test(() => {
const request = new PaymentRequest(defaultMethods, defaultDetails);
const untrustedEvents = [
- new PaymentRequestUpdateEvent("just a test")
+ new PaymentRequestUpdateEvent("just a test"),
+ new PaymentRequestUpdateEvent("shippingaddresschange"),
+ new PaymentRequestUpdateEvent("shippingoptionchange"),
].map(ev => {
request.dispatchEvent(ev); // set .target and dispatch flag
// unstrusted event.