diff options
Diffstat (limited to 'testing/web-platform/tests/payment-request')
32 files changed, 4151 insertions, 6 deletions
diff --git a/testing/web-platform/tests/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html b/testing/web-platform/tests/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html new file mode 100644 index 0000000000..fc1ce3523e --- /dev/null +++ b/testing/web-platform/tests/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html @@ -0,0 +1,109 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://www.w3.org/TR/payment-request/#ContactAddress-interface"> +<title> + PaymentResponse.prototype.shippingAddress +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../payment-response/helpers.js"></script> +<script> +const options = { requestShipping: true }; +function runManualTest(button, expected = {}) { + button.disabled = true; + promise_test(async () => { + const { response } = await getPaymentRequestResponse(options); + await response.complete(); + assert_idl_attribute(response, "shippingAddress"); + const { shippingAddress: addr } = response; + assert_true( + addr instanceof ContactAddress, + "Expect instance of ContactAddress" + ); + // An [ISO3166] alpha-2 code. The canonical form is upper case. + const { country } = addr; + assert_equals(country.length, 2, "Expected length is 2"); + assert_true(/^[A-Z]{2}$/.test(country), "Canonical form is upper case"); + assert_true( + addr.addressLine instanceof Array, + "Expected addressLine to be an array" + ); + assert_throws_js( + TypeError, + () => { + addr.addressLine.push("this must throw"); + }, + "Array must be frozen" + ); + for (let [attr, expectedValue] of Object.entries(expected)) { + assert_idl_attribute(addr, attr); + const msg = `Expected ContactAddress.${attr} to equal ${expectedValue}.`; + //.toString() flattens array addressLine, + //.toLowerCase() because case can't be enforced for some attributes + const actualValue = addr[attr].toString().toLowerCase(); + expectedValue = expectedValue.toString().toLowerCase(); + assert_equals(actualValue, expectedValue, msg); + } + // Check toJSON result + for (let [prop, jsonValue] of Object.entries(addr.toJSON())) { + const actualValue = jsonValue.toString().toLowerCase(); + const expectedValue = expected[prop].toString().toLowerCase(); + const msg = `Expected JSON ${prop} to be ${expectedValue}`; + assert_equals(actualValue, expectedValue, msg); + } + }, button.textContent.trim()); + done(); +} +</script> +<h2>ContactAddress interface</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 prompted, please enter addresses as follows... +</p> +<ol> + <li> + <button onclick=" + const expectedAddress = { + country: 'AU', + regionCode: 'QLD', + addressLine: '55 test st', + city: 'Chapel Hill', + dependentLocality: '', + postalCode: '6095', + region: 'QLD', + sortingCode: '', + organization: 'w3c', + recipient: 'web platform test', + phone: '+61733780000', + }; + runManualTest(this, expectedAddress);"> + If the requestShipping member is true, then shippingAddress's ContactAddress must match the expected values. + </button> + Please use: + <dl> + <dt>Recipient:</dt> + <dd>web platform test</dd> + <dt>Address line:</dt> + <dd>55 test st</dd> + <dt>Country</dt> + <dd>Australia</dd> + <dt>City</dt> + <dd>Chapel Hill</dd> + <dd>State/Region</dd> + <dd>Queensland</dd> + <dt>postal code </dt> + <dd>6095</dd> + <dt>organization</dt> + <dd>w3c</dd> + <dt>Phone number</dt> + <dd>+61 7 3378 0000</dd> + </dl> + </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-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. diff --git a/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-shippingAddress-member-manual.https.html b/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-shippingAddress-member-manual.https.html new file mode 100644 index 0000000000..94e6fa5105 --- /dev/null +++ b/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-shippingAddress-member-manual.https.html @@ -0,0 +1,103 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentvalidationerrors-shippingaddress"> +<title> + PaymentValidationErrors' `shippingAddress` member (AddressErrors) +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../payment-response/helpers.js"></script> +<script> +function retryShowsShippingAddressMember(button, error) { + button.disabled = true; + promise_test(async t => { + const options = { + requestShipping: true, + } + const { response } = await getPaymentRequestResponse(options); + await response.retry({ shippingAddress: error }); + await response.complete("success"); + }, button.textContent.trim()); +} +</script> +<h2> + Manual Test for PaymentValidationErrors' `shippingAddress` member - Please run in order! +</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 presented with the payment sheet, use any card and select to "Pay". + You will be asked to retry the payment and an error should be shown somewhere + in the UI. The expected error string is described in each individual test. + If you see the error, hit "Pay" again. If you don't see the error, + abort the payment request by hitting "esc" - which means that particular test + has failed. +</p> +<ol> + <li> + <button onclick="retryShowsShippingAddressMember(this, { addressLine: 'ADDRESSLINE ERROR' });"> + The payment sheet shows "ADDRESSLINE ERROR" for the shipping address' addressLine. + </button> + </li> + <li> + <button onclick="retryShowsShippingAddressMember(this, { city: 'CITY ERROR' });"> + The payment sheet shows "CITY ERROR" for the shipping address' city. + </button> + </li> + <li> + <button onclick="retryShowsShippingAddressMember(this, { country: 'COUNTRY ERROR' });"> + The payment sheet shows "COUNTRY ERROR" for the shipping address' country. + </button> + </li> + <li> + <button onclick="retryShowsShippingAddressMember(this, { dependentLocality: 'DEPENDENTLOCALITY ERROR' });"> + The payment sheet shows "DEPENDENTLOCALITY ERROR" for the shipping address' dependentLocality. + </button> + </li> + <li> + <button onclick="retryShowsShippingAddressMember(this, { organization: 'ORGANIZATION ERROR' });"> + The payment sheet shows "ORGANIZATION ERROR" for the shipping address' organization. + </button> + </li> + <li> + <button onclick="retryShowsShippingAddressMember(this, { phone: 'PHONE ERROR' });"> + The payment sheet shows "PHONE ERROR" for the shipping address' phone. + </button> + </li> + <li> + <button onclick="retryShowsShippingAddressMember(this, { postalCode: 'POSTALCODE ERROR' });"> + The payment sheet shows "POSTALCODE ERROR" for the shipping address' postal code. + </button> + </li> + <li> + <button onclick="retryShowsShippingAddressMember(this, { recipient: 'RECIPIENT ERROR' });"> + The payment sheet shows "RECIPIENT ERROR" for the shipping address' recipient. + </button> + </li> + <li> + <button onclick="retryShowsShippingAddressMember(this, { region: 'REGION ERROR' });"> + The payment sheet shows "REGION ERROR" for the shipping address' region. + </button> + </li> + <li> + <button onclick="retryShowsShippingAddressMember(this, { regionCode: 'REGIONCODE ERROR' });"> + The payment sheet shows "REGIONCODE ERROR" for the shipping address' region code. + </button> + </li> + <li> + <button onclick="retryShowsShippingAddressMember(this, { sortingCode: 'SORTINGCODE ERROR' });"> + The payment sheet shows "SORTINGCODE ERROR" for the shipping address' sorting code. + </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">owners</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/algorithms-manual.https.html b/testing/web-platform/tests/payment-request/algorithms-manual.https.html new file mode 100644 index 0000000000..b90c312aba --- /dev/null +++ b/testing/web-platform/tests/payment-request/algorithms-manual.https.html @@ -0,0 +1,176 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#algorithms"> +<title> + Payment Request algorithms +</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 shippingOptions = { + shippingOptions: [ + { + id: "fail", + label: "Option 1", + amount: { + currency: "USD", + value: "5.00", + }, + selected: true, + }, + { + id: "pass", + label: "Option 2", + amount: { + currency: "USD", + value: "5.00", + }, + }, + ], +}; + +const detailsNoShippingOptions = { + total: { + label: "Total due", + amount: { + currency: "USD", + value: "1.0", + }, + }, +}; + +const detailsWithShippingOptions = Object.assign( + { + total: { + label: "Total due", + amount: { + currency: "USD", + value: "1.0", + }, + }, + }, + shippingOptions +); + +const options = { + requestShipping: true, +}; + +function testFireEvent(button, details, eventName, expectRequestProps) { + button.disabled = true; + promise_test(async t => { + new PaymentRequest(methods, detailsNoShippingOptions, options); + const request = new PaymentRequest(methods, details, options); + const handlerPromise = new Promise(resolve => { + request[`on${eventName}`] = event => { + // "prevent immediate propagation" flag is set. + // This listener below won't fire! + event.updateWith(details); + resolve(event); + }; + }); + // This listener should never fire because the + // the event handler caused "prevent immediate propagation" to be set. + request.addEventListener( + eventName, + t.unreached_func("Second event listener should never fire") + ); + const response = await request.show(); + const event = await handlerPromise; + assert_true( + event instanceof window.PaymentRequestUpdateEvent, + "Expected instances of PaymentRequestUpdateEvent" + ); + await response.complete("success"); + }, button.textContent.trim()); +} + +async function runAbortTest(button) { + button.disabled = true; + const { textContent: testName } = button; + promise_test(async t => { + const request = new PaymentRequest(methods, detailsNoShippingOptions); + // Await the user to abort + await promise_rejects_dom(t, "AbortError", request.show()); + // [[state]] is now closed + await promise_rejects_dom(t, "InvalidStateError", request.show()); + }, testName.trim()); +} +</script> +<h2> + Tests for "algorithms" section +</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> +<section> + <h3 id="abort-algo"> + User aborts the payment request algorithm + </h3> + <link rel="help" href="https://w3c.github.io/payment-request/#user-aborts-the-payment-request-algorithm"> + <p> + When presented with the payment sheet, abort the payment request (e.g., by hitting the esc key or pressing a UA provided button). + </p> + <ol> + <li> + <button onclick="runAbortTest(this);"> + If the user aborts, the UA must run the user aborts the payment request algorithm. + </button> + </li> + </ol> +</section> + +<section> + <h3 id="shipping-address-changed-algo">Shipping address changed algorithm</h3> + <link rel="help" href="https://www.w3.org/TR/payment-request/#shipping-address-changed-algorithm"> + <p> + When prompted, please change or enter a new shipping address and then select Pay. + </p> + <ol> + <li> + <button onclick="testFireEvent(this, detailsWithShippingOptions, 'shippingaddresschange', {});"> + The shipping address changed algorithm runs when the user provides a new shipping address. + </button> + </li> + </ol> +</section> + +<section> + <h3 id="shipping-option-changed-algo">Shipping option changed algorithm</h3> + <link rel="help" href="https://w3c.github.io/payment-request/#shipping-option-changed-algorithm"> + <p> + Finally, when prompted, please select "shipping option 2" and then select Pay. + </p> + <ol> + <li> + <button onclick="testFireEvent(this, detailsWithShippingOptions, 'shippingoptionchange', {}, 'pass'); done();"> + The shipping option changed algorithm runs when the user chooses a new shipping option. + </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/billing-address-changed-manual.https.html b/testing/web-platform/tests/payment-request/billing-address-changed-manual.https.html new file mode 100644 index 0000000000..d03f761518 --- /dev/null +++ b/testing/web-platform/tests/payment-request/billing-address-changed-manual.https.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> <meta charset="utf-8" /> +<title>Test for requesting billing address</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 details = { + total: { + label: "label", + amount: { currency: "USD", value: "5.00" }, + }, + }; + test(() => { + assert_true( + "onpaymentmethodchange" in PaymentRequest.prototype, + "The paymentmethodchange is not supported" + ); + }, "onpaymentmethodchange is in prototype"); + + function dontRequestBillingAddress() { + promise_test(async t => { + const request = new PaymentRequest(methods, details, {}); + const showPromise = request.show(); + + // Let's check the method data from event. + const { methodDetails } = await new Promise(resolve => + request.addEventListener("paymentmethodchange", resolve) + ); + + assert_true("billingAddress" in methodDetails); + assert_equals( + methodDetails.billingAddress, + null, + "Expected methodDetails.billingAddress to be null" + ); + await request.abort(); + }); + } + + function requestBillingAddress() { + promise_test(async t => { + const request = new PaymentRequest(methods, details, { + requestBillingAddress: true, + }); + const showPromise = request.show(); + + // Let's check the method data from event. + const { methodDetails } = await new Promise(resolve => + request.addEventListener("paymentmethodchange", resolve) + ); + + assert_true("billingAddress" in methodDetails); + + const { billingAddress } = methodDetails; + assert_true( + billingAddress instanceof ContactAddress, + "Expected instance of ContactAddress" + ); + await request.abort(); + }); + } +</script> + +<h2>Request billing address</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 presented, select a payment method (e.g., a credit + card). +</p> +<ol> + <li> + <button onclick="dontRequestBillingAddress()"> + When no billing address is requested, + `PaymentMethodChangeEvent.methodDetails.billingAddress` is null. + </button> + </li> + <li> + <button onclick="requestBillingAddress()"> + When billing address is + requested,`PaymentMethodChangeEvent.methodDetails.billingAddress` is a + `ContactAddress`. + </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/change-shipping-option-manual.https.html b/testing/web-platform/tests/payment-request/change-shipping-option-manual.https.html new file mode 100644 index 0000000000..438001804a --- /dev/null +++ b/testing/web-platform/tests/payment-request/change-shipping-option-manual.https.html @@ -0,0 +1,104 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequest shippingOption attribute</title> +<link rel="help" href="https://w3c.github.io/payment-request/#shippingoption-attribute"> +<link rel="help" href="https://w3c.github.io/payment-request/#onshippingoptionchange-attribute"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +setup({ explicit_done: true, explicit_timeout: true }); +const validMethod = Object.freeze({ supportedMethods: "basic-card" }); +const applePayMethod = { + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + }, +}; +const validMethods = Object.freeze([validMethod, applePayMethod]); +const validAmount = Object.freeze({ currency: "USD", value: "5.00" }); +const validTotal = Object.freeze({ + label: "label", + amount: validAmount, +}); +const validDetails = Object.freeze({ total: validTotal }); + +const validShippingOption1 = Object.freeze({ + id: "valid-1", + label: "PICK ME!", + amount: validAmount, + selected: false, +}); + +const validShippingOption2 = Object.freeze({ + id: "initially-selected", + label: "Valid shipping option 2", + amount: validAmount, + selected: true, +}); + +const requestShipping = Object.freeze({ + requestShipping: true, +}); + +function testShippingOptionChanged() { + promise_test(async t => { + const detailsWithShippingOptions = Object.assign({}, validDetails, { + shippingOptions: [validShippingOption1, validShippingOption2], + }); + const request = new PaymentRequest( + validMethods, + detailsWithShippingOptions, + requestShipping + ); + assert_equals( + request.shippingOption, + "initially-selected", + "Must be 'initially-selected', as the selected member is true" + ); + const listenerPromise = new Promise(resolve => { + request.addEventListener("shippingoptionchange", () => { + resolve(request.shippingOption); + }); + }); + const handlerPromise = new Promise(resolve => { + request.onshippingoptionchange = () => { + resolve(request.shippingOption); + }; + }); + request.show().catch(err => err); + + const results = await Promise.all([listenerPromise, handlerPromise]); + assert_true( + results.every(result => result === "valid-1"), + "Expected valid-1 as the shippingOption" + ); + await request.abort(); + }); + done(); +} +</script> + +<h2>PaymentRequest shippingOption attribute</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 presented, select "PICK ME!" as the shipping option. +</p> +<ol> + <li> + <button onclick="testShippingOptionChanged()"> + When the shipping option is manually changed, request.shippingOption represents the user's choice. + </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/change-shipping-option-select-last-manual.https.html b/testing/web-platform/tests/payment-request/change-shipping-option-select-last-manual.https.html new file mode 100644 index 0000000000..4ad31d6531 --- /dev/null +++ b/testing/web-platform/tests/payment-request/change-shipping-option-select-last-manual.https.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test for PaymentDetailsBase's shippingOptions member</title> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentdetailsbase-shippingoptions"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +setup({ explicit_done: true, explicit_timeout: true }); +const validMethods = Object.freeze([ + { supportedMethods: "basic-card" }, + { + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + }, + }, +]); +const validAmount = Object.freeze({ currency: "USD", value: "5.00" }); +const validTotal = Object.freeze({ + label: "label", + amount: validAmount, +}); +const validDetails = Object.freeze({ total: validTotal }); + +const validShippingOption1 = Object.freeze({ + id: "fail-if-selected-1", + label: "FAIL if selected 1", + amount: validAmount, + selected: true, +}); + +const validShippingOption2 = Object.freeze({ + id: "fail-if-selected-2", + label: "FAIL if selected 2", + amount: validAmount, + selected: false, +}); + +const validShippingOption3 = Object.freeze({ + id: "pass-if-selected", + label: "THIS MUST BE AUTOMATICALLY SELECTED", + amount: validAmount, + selected: true, +}); + +function testShippingOptionChanged(button) { + button.disabled = true; + promise_test(async t => { + const detailsWithShippingOptions = { + ...validDetails, + shippingOptions: [ + validShippingOption1, + validShippingOption2, + validShippingOption3, + ], + }; + const request = new PaymentRequest( + validMethods, + detailsWithShippingOptions, + { requestShipping: true } + ); + assert_equals( + request.shippingOption, + "pass-if-selected", + "Must be 'pass-if-selected', as the selected member is true" + ); + request.onshippingoptionchange = () => { + assert_unreached("onshippingoptionchange fired unexpectedly"); + }; + const response = await request.show(); + assert_equals(response.shippingOption, "pass-if-selected"); + response.complete(); + }, button.textContent.trim()); + done(); +} +</script> + +<h2>PaymentRequest shippingOption attribute</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 presented, hit pay. +</p> +<ol> + <li> + <button onclick="testShippingOptionChanged(this)"> + When default shipping option is pre-selected, must not fire onshippingoptionchange + and PaymentResponse must reflect the pre-selected option. + </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/dynamically-change-shipping-options-manual.https.html b/testing/web-platform/tests/payment-request/dynamically-change-shipping-options-manual.https.html new file mode 100644 index 0000000000..0e6670a1b8 --- /dev/null +++ b/testing/web-platform/tests/payment-request/dynamically-change-shipping-options-manual.https.html @@ -0,0 +1,142 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Test for PaymentRequest shippingOption dynamic updating</title> +<link + rel="help" + href="https://w3c.github.io/payment-request/#shippingoption-attribute" +/> +<link + rel="help" + href="https://w3c.github.io/payment-request/#onshippingoptionchange-attribute" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + setup({ explicit_done: true, explicit_timeout: true }); + const validMethod = Object.freeze({ supportedMethods: "basic-card" }); + const applePayMethod = { + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + }, + }; + const validMethods = Object.freeze([validMethod, applePayMethod]); + const validAmount = Object.freeze({ currency: "USD", value: "5.00" }); + const validTotal = Object.freeze({ + label: "label", + amount: validAmount, + }); + const validDetails = Object.freeze({ total: validTotal }); + + const initialValidShippingOption = Object.freeze({ + id: "default-method", + label: "Default shipping method", + amount: validAmount, + selected: true, + }); + + const validDynamicShippingOption = Object.freeze({ + id: "dynamically-added-id", + label: "Dynamically added shipping option", + amount: validAmount, + selected: false, + }); + + const requestShipping = Object.freeze({ + requestShipping: true, + }); + + function testShippingOptionChanged() { + promise_test(async (t) => { + const detailsWithShippingOptions = { + ...validDetails, + shippingOptions: [initialValidShippingOption], + }; + const request = new PaymentRequest( + validMethods, + detailsWithShippingOptions, + requestShipping + ); + const shippingAddressChangeListener = new Promise((resolve) => { + request.addEventListener( + "shippingaddresschange", + (ev) => { + // resolve(request.shippingOption); + ev.updateWith({ + shippingOptions: [ + initialValidShippingOption, + validDynamicShippingOption, + ], + }); + resolve(); + }, + { once: true } + ); + }); + const handlerPromise = new Promise((resolve) => { + request.onshippingoptionchange = () => { + resolve(request.shippingOption); + }; + }); + request.show().catch((err) => err); + + const results = await Promise.all([ + shippingAddressChangeListener, + handlerPromise, + ]); + assert_true( + results[1] === "dynamically-added-id", + "Expected dynamically-added-id as the shippingOption" + ); + await request.abort(); + }); + } +</script> + +<h2>PaymentRequest shippingOption attribute</h2> +<p> + Click on each button in sequence from top to bottom without refreshing the + page. Each button (except the 'Done' button) will bring up the Payment Request + UI window. +</p> +<ol> + <li> + When the payment sheet is presented, view options for Shipping Method. There + should only be one: "Default shipping method" + </li> + <li> + Change your Shipping Address - either update your existing one by changing + something (name, address, etc), or select a different Shipping Address, or + add a new Shipping Address and select it. + </li> + <li> + Go back to Shipping Method, and there is now an option called "Dynamically + added shipping option". Select it + </li> + <li> + Click on the 'Done' button + </li> +</ol> +<ul> + <li> + <button onclick="testShippingOptionChanged()"> + When the address is changed, shipping methods can be updated + </button> + </li> + <li> + <button onclick="done()">Done</button> + </li> +</ul> +<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/historical.https.html b/testing/web-platform/tests/payment-request/historical.https.html index aa183a58cd..3e881d0122 100644 --- a/testing/web-platform/tests/payment-request/historical.https.html +++ b/testing/web-platform/tests/payment-request/historical.https.html @@ -10,7 +10,7 @@ ["paymentRequestID", "PaymentResponse"], // https://github.com/w3c/browser-payment-api/pull/258 - ["careOf", "PaymentAddress"], + ["careOf", "ContactAddress"], // https://github.com/w3c/browser-payment-api/pull/219 ["totalAmount", "PaymentResponse"], @@ -20,7 +20,7 @@ ["paymentRequestId", "PaymentResponse"], // https://github.com/w3c/payment-request/pull/765 - ["languageCode", "PaymentAddress"], + ["languageCode", "ContactAddress"], //https://github.com/whatwg/html/pull/5915 ["allowPaymentRequest", "HTMLIFrameElement"], diff --git a/testing/web-platform/tests/payment-request/payment-request-constructor-thcrash.https.html b/testing/web-platform/tests/payment-request/payment-request-constructor-thcrash.https.html new file mode 100644 index 0000000000..b600307085 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-constructor-thcrash.https.html @@ -0,0 +1,254 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>Crash tests PaymentRequest Constructor</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#constructor"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +"use strict"; +const ABUSIVE_AMOUNT = 100000; + +const applePay = { + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } +}; + +const basicCard = Object.freeze({ + supportedMethods: "basic-card", +}); + +const defaultAmount = Object.freeze({ + currency: "USD", + value: "1.00", +}); + +const evilAmount = Object.freeze({ + currency: "USD", + value: "1".repeat(ABUSIVE_AMOUNT), +}); + +const defaultMethods = Object.freeze([basicCard, applePay]); + +const defaultTotal = Object.freeze({ + label: "label", + amount: defaultAmount, +}); + +const evilTotal = Object.freeze({ + label: "a".repeat(ABUSIVE_AMOUNT), + amount: evilAmount, +}); + +const defaultDetails = Object.freeze({ + total: defaultTotal, + get id() { + return Math.random(); + }, +}); + +const defaultPaymentItem = Object.freeze({ + label: "label", + amount: defaultAmount, +}); + +const defaultShippingOption = { + get id() { + return "shipping option " + Math.random(); + }, + amount: defaultAmount, + label: "shipping option label", +}; + +// First argument is sequence<PaymentMethodData> methodData +test(() => { + let evilMethods = [Object.assign({}, basicCard)]; + // smoke test + try { + new PaymentRequest(evilMethods, defaultDetails); + } catch (err) { + assert_unreached("failed smoke test: " + err.stack); + } + // Now, let's add an abusive amount of methods. + while (evilMethods.length < ABUSIVE_AMOUNT) { + evilMethods.push({supportedMethods: "evil-method" + evilMethods.length}); + } + try { + new PaymentRequest(evilMethods, defaultDetails); + } catch (err) { + assert_equals(err.name, "TypeError", "must be a TypeError"); + } +}, "Don't crash if there is an abusive number of payment methods in the methodData sequence"); + +// PaymentMethodData.supportedMethods +test(() => { + const supportedMethods = "basic-card"; + // Smoke test + try { + new PaymentRequest([{ supportedMethods }], defaultDetails); + } catch (err) { + assert_unreached("failed smoke test: " + err.stack); + } + // Now, we make supportedMethods super large + const evilMethodData = [ + { + supportedMethods: supportedMethods.repeat(ABUSIVE_AMOUNT), + }, + ]; + try { + new PaymentRequest(evilMethodData, defaultDetails); + } catch (err) { + assert_equals(err.name, "TypeError", "must be a TypeError"); + } +}, "Don't crash if PaymentMethodData.supportedMethods is an abusive length"); + +// PaymentDetailsInit.id +test(() => { + const id = "abc"; + // Smoke Test + try { + new PaymentRequest( + defaultMethods, + Object.assign({}, defaultDetails, { id }) + ); + } catch (err) { + assert_unreached("failed smoke test: " + err.stack); + } + // Now, we make the id super large; + const evilDetails = Object.assign({}, defaultDetails, { + id: id.repeat(ABUSIVE_AMOUNT), + }); + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_equals(err.name, "TypeError", "must be a TypeError"); + } +}, "Don't crash if the request id has an abusive length"); + +// PaymentDetailsInit.total.label +test(() => { + const evilDetails = Object.assign({}, defaultDetails); + // Smoke Test + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_unreached("failed smoke test: " + err.stack); + } + // Now, we make the label super large; + evilDetails.total = { + label: "l".repeat(ABUSIVE_AMOUNT), + amount: defaultAmount, + }; + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_equals(err.name, "TypeError", "must be a TypeError"); + } +}, "Don't crash if PaymentDetailsInit.total.label is an abusive length"); + +test(() => { + const evilDetails = Object.assign({}, defaultDetails); + // Smoke Test + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_unreached("failed smoke test: " + err.stack); + } + // Now, we can use evilAmount + evilDetails.total = evilAmount; + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_equals(err.name, "TypeError", "must be a TypeError"); + } +}, "Don't crash if total.amount.value is an abusive length"); + +for (const [prop, defaultValue] of [ + ["displayItems", defaultPaymentItem], + ["shippingOptions", defaultShippingOption], +]) { + test(() => { + const evilDetails = Object.assign({}, defaultDetails); + evilDetails[prop] = [defaultValue]; + // Smoke Test + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_unreached("failed smoke test: " + err.stack); + } + while (evilDetails[prop].length < ABUSIVE_AMOUNT) { + evilDetails[prop] = evilDetails[prop].concat(evilDetails[prop]); + } + // Now, construct with evil items! + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_equals(err.name, "TypeError", "must be a TypeError"); + } + }, `Don't crash if details.${prop} has an abusive number of items`); +} + +test(() => { + const evilDetails = Object.assign({}, defaultDetails); + const evilShippingOption = Object.assign({}, defaultShippingOption); + evilDetails.shippingOptions = [evilShippingOption]; + // Smoke Test + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_unreached("failed smoke test: " + err.stack); + } + // Now, we make the label super large; + evilShippingOption.label = "l".repeat(ABUSIVE_AMOUNT); + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_equals(err.name, "TypeError", "must be a TypeError"); + } +}, "Don't crash if PaymentShippingOptions.label is an abusive length"); + +test(() => { + const evilDetails = Object.assign({}, defaultDetails); + const evilShippingOption = Object.assign({}, defaultShippingOption); + evilDetails.shippingOptions = [evilShippingOption]; + // Smoke Test + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_unreached("failed smoke test: " + err.stack); + } + // Now, we make use of evilAmount; + evilShippingOption.amount = evilAmount; + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_equals(err.name, "TypeError", "must be a TypeError"); + } +}, "Don't crash if the PaymentShippingOptions.amount.value is an abusive length"); + +test(() => { + const evilDetails = Object.assign({}, defaultDetails); + const evilDisplayItem = Object.assign({}, defaultPaymentItem); + evilDetails.displayItems = [evilDisplayItem]; + // Smoke Test + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_unreached("failed smoke test: " + err.stack); + } + // Now, we make the label super large; + evilDisplayItem.label = "l".repeat(ABUSIVE_AMOUNT); + try { + new PaymentRequest(defaultMethods, evilDetails); + } catch (err) { + assert_equals(err.name, "TypeError", "must be a TypeError"); + } +}, "Don't crash if PaymentItem.label is an abusive length"); +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html b/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html index c1ecc22583..9b0ad06454 100644 --- a/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html +++ b/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html @@ -240,7 +240,7 @@ test(() => { test(() => { smokeTest(); - for (const prop in ["displayItems", "modifiers"]) { + for (const prop in ["displayItems", "shippingOptions", "modifiers"]) { try { const details = Object.assign({}, defaultDetails, { [prop]: [] }); new PaymentRequest(defaultMethods, details); @@ -361,6 +361,186 @@ test(() => { } }, "it handles high precision currency values without throwing"); +// Process shipping options: + +const defaultShippingOption = Object.freeze({ + id: "default", + label: "", + amount: defaultAmount, + selected: false, +}); +const defaultShippingOptions = Object.freeze([ + Object.assign({}, defaultShippingOption), +]); + +test(() => { + smokeTest(); + for (const amount of invalidAmounts) { + const invalidAmount = Object.assign({}, defaultAmount, { + value: amount, + }); + const invalidShippingOption = Object.assign({}, defaultShippingOption, { + amount: invalidAmount, + }); + const details = Object.assign({}, defaultDetails, { + shippingOptions: [invalidShippingOption], + }); + assert_throws_js( + TypeError, + () => { + new PaymentRequest(defaultMethods, details, { requestShipping: true }); + }, + `Expected TypeError for option.amount.value: "${amount}"` + ); + } +}, `For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value, then throw a TypeError`); + +test(() => { + smokeTest(); + const shippingOptions = [defaultShippingOption]; + const details = Object.assign({}, defaultDetails, { shippingOptions }); + const request = new PaymentRequest(defaultMethods, details); + assert_equals( + request.shippingOption, + null, + "shippingOption must be null, as requestShipping is missing" + ); + // defaultDetails lacks shipping options + const request2 = new PaymentRequest(defaultMethods, defaultDetails, { + requestShipping: true, + }); + assert_equals( + request2.shippingOption, + null, + `request2.shippingOption must be null` + ); +}, "If there is no selected shipping option, then PaymentRequest.shippingOption remains null"); + +test(() => { + smokeTest(); + const selectedOption = Object.assign({}, defaultShippingOption, { + selected: true, + id: "the-id", + }); + const shippingOptions = [selectedOption]; + const details = Object.assign({}, defaultDetails, { shippingOptions }); + const requestNoShippingRequested1 = new PaymentRequest( + defaultMethods, + details + ); + assert_equals( + requestNoShippingRequested1.shippingOption, + null, + "Must be null when no shipping is requested (defaults to false)" + ); + const requestNoShippingRequested2 = new PaymentRequest( + defaultMethods, + details, + { requestShipping: false } + ); + assert_equals( + requestNoShippingRequested2.shippingOption, + null, + "Must be null when requestShipping is false" + ); + const requestWithShipping = new PaymentRequest(defaultMethods, details, { + requestShipping: "truthy value", + }); + assert_equals( + requestWithShipping.shippingOption, + "the-id", + "Selected option must be 'the-id'" + ); +}, "If there is a selected shipping option, and requestShipping is set, then that option becomes synchronously selected"); + +test(() => { + smokeTest(); + const failOption1 = Object.assign({}, defaultShippingOption, { + selected: true, + id: "FAIL1", + }); + const failOption2 = Object.assign({}, defaultShippingOption, { + selected: false, + id: "FAIL2", + }); + const passOption = Object.assign({}, defaultShippingOption, { + selected: true, + id: "the-id", + }); + const shippingOptions = [failOption1, failOption2, passOption]; + const details = Object.assign({}, defaultDetails, { shippingOptions }); + const requestNoShipping = new PaymentRequest(defaultMethods, details, { + requestShipping: false, + }); + assert_equals( + requestNoShipping.shippingOption, + null, + "shippingOption must be null, as requestShipping is false" + ); + const requestWithShipping = new PaymentRequest(defaultMethods, details, { + requestShipping: true, + }); + assert_equals( + requestWithShipping.shippingOption, + "the-id", + "selected option must 'the-id" + ); +}, "If requestShipping is set, and if there is a multiple selected shipping options, only the last is selected."); + +test(() => { + smokeTest(); + const selectedOption = Object.assign({}, defaultShippingOption, { + selected: true, + }); + const unselectedOption = Object.assign({}, defaultShippingOption, { + selected: false, + }); + const shippingOptions = [selectedOption, unselectedOption]; + const details = Object.assign({}, defaultDetails, { shippingOptions }); + const requestNoShipping = new PaymentRequest(defaultMethods, details); + assert_equals( + requestNoShipping.shippingOption, + null, + "shippingOption must be null, because requestShipping is false" + ); + assert_throws_js( + TypeError, + () => { + new PaymentRequest(defaultMethods, details, { requestShipping: true }); + }, + "Expected to throw a TypeError because duplicate IDs" + ); +}, "If there are any duplicate shipping option ids, and shipping is requested, then throw a TypeError"); + +test(() => { + smokeTest(); + const dupShipping1 = Object.assign({}, defaultShippingOption, { + selected: true, + id: "DUPLICATE", + label: "Fail 1", + }); + const dupShipping2 = Object.assign({}, defaultShippingOption, { + selected: false, + id: "DUPLICATE", + label: "Fail 2", + }); + const shippingOptions = [dupShipping1, defaultShippingOption, dupShipping2]; + const details = Object.assign({}, defaultDetails, { shippingOptions }); + const requestNoShipping = new PaymentRequest(defaultMethods, details); + assert_equals( + requestNoShipping.shippingOption, + null, + "shippingOption must be null, because requestShipping is false" + ); + assert_throws_js( + TypeError, + () => { + new PaymentRequest(defaultMethods, details, { requestShipping: true }); + }, + "Expected to throw a TypeError because duplicate IDs" + ); +}, "Throw when there are duplicate shippingOption ids, even if other values are different"); + // Process payment details modifiers: test(() => { smokeTest(); @@ -474,4 +654,44 @@ test(() => { }); }, "Rethrow any exceptions of JSON-serializing modifier.data"); +//Setting ShippingType attribute during construction +test(() => { + smokeTest(); + assert_throws_js(TypeError, () => { + new PaymentRequest(defaultMethods, defaultDetails, { + shippingType: "invalid", + }); + }); +}, "Shipping type should be valid"); + +test(() => { + smokeTest(); + const request = new PaymentRequest(defaultMethods, defaultDetails, {}); + assert_equals(request.shippingAddress, null, "must be null"); +}, "PaymentRequest.shippingAddress must initially be null"); + +test(() => { + smokeTest(); + const request1 = new PaymentRequest(defaultMethods, defaultDetails, {}); + assert_equals(request1.shippingType, null, "must be null"); + const request2 = new PaymentRequest(defaultMethods, defaultDetails, { + requestShipping: false, + }); + assert_equals(request2.shippingType, null, "must be null"); +}, "If options.requestShipping is not set, then request.shippingType attribute is null."); + +test(() => { + smokeTest(); + // option.shippingType defaults to 'shipping' + const request1 = new PaymentRequest(defaultMethods, defaultDetails, { + requestShipping: true, + }); + assert_equals(request1.shippingType, "shipping", "must be shipping"); + const request2 = new PaymentRequest(defaultMethods, defaultDetails, { + requestShipping: true, + shippingType: "delivery", + }); + assert_equals(request2.shippingType, "delivery", "must be delivery"); +}, "If options.requestShipping is true, request.shippingType will be options.shippingType."); + </script> diff --git a/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html b/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html index c608608c7e..b4ca2a0c40 100644 --- a/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html +++ b/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html @@ -178,6 +178,63 @@ test(() => { } }, "Check and canonicalize invalid details.displayItems amount and rethrow RangeError."); +// Process shipping options: +test(() => { + assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); + const shippingOptions = []; + for (const validCurrency of wellFormedCurrencyCodes) { + const shippingOption = { + id: `test` + Math.random(), + label: "shipping option", + amount: { currency: validCurrency, value: "5.00" }, + selected: !shippingOptions.length, + }; + const details = { + total: defaultTotal, + shippingOptions: [shippingOption], + }; + try { + new PaymentRequest(defaultMethods, details, { requestShipping: true }); + } catch (err) { + assert_unreached( + `Unexpected exception with valid shippingOption currency code "${validCurrency}": ${err.message}.` + ); + } + shippingOptions.push(shippingOption); + } + try { + const details = Object.assign({}, defaultDetails, { shippingOptions }); + new PaymentRequest(defaultMethods, details, { requestShipping: true }); + } catch (err) { + assert_unreached( + `Unexpected error with multiple valid shppingOptions: ${err.message}.` + ); + } +}, "Check and canonicalize valid details.shippingOptions amount."); + +test(() => { + assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); + for (const invalidCurrency of invalidCurrencyCodes) { + const shippingOption = { + id: "test", + label: "shipping option", + amount: { currency: invalidCurrency, value: "5.00" }, + selected: true, + }; + const details = { + total: defaultTotal, + shippingOptions: [shippingOption], + }; + assert_throws_js( + RANGE_ERROR, + () => { + new PaymentRequest(defaultMethods, details, { requestShipping: true }); + }, + `Expected RangeError with invalid shippingOption currency code "${invalidCurrency}".` + ); + } +}, "Check and canonicalize invalid details.shippingOptions amount and rethrow RangeError."); + // Process payment details modifiers: test(() => { assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); diff --git a/testing/web-platform/tests/payment-request/payment-request-onshippingaddresschange-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-onshippingaddresschange-attribute.https.html new file mode 100644 index 0000000000..5b2538992f --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-onshippingaddresschange-attribute.https.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for onshippingaddresschange attribute</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#onshippingaddresschange-attribute"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +const applePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } +}); +const basicCard = Object.freeze({ supportedMethods: "basic-card" }); +const defaultMethods = Object.freeze([basicCard, applePay]); +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + assert_idl_attribute(request, "onshippingaddresschange"); +}, "Must have a onshippingaddresschange IDL attribute"); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new Event("shippingaddresschange"); + let didHandle = false; + request.onshippingaddresschange = evt => { + assert_equals(ev, evt, "must be same event"); + didHandle = true; + }; + request.dispatchEvent(ev); + assert_true(didHandle, "event did not fire"); +}, `onshippingaddresschange attribute is a generic handler for "shippingaddresschange"`); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new PaymentRequestUpdateEvent("shippingaddresschange"); + let didHandle = false; + request.onshippingaddresschange = evt => { + assert_equals(ev, evt, "must be same event"); + didHandle = true; + }; + request.dispatchEvent(ev); + assert_true(didHandle, "event did not fire"); +}, `onshippingaddresschange attribute is a handler for PaymentRequestUpdateEvent`); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new PaymentRequestUpdateEvent("shippingaddresschange"); + let didHandle = false; + let didListen = false; + request.onshippingaddresschange = evt => { + assert_equals(ev, evt, "must be same event"); + didHandle = true; + }; + request.addEventListener("shippingaddresschange", evt => { + assert_equals(ev, evt, "must be same event"); + didListen = true; + }); + request.dispatchEvent(ev); + assert_true(didHandle, "onshippingaddresschange did not receive the event"); + assert_true(didListen, "addEventListener did not receive the event"); +}, `onshippingaddresschange attribute and listeners both work`); +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-onshippingoptionchange-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-onshippingoptionchange-attribute.https.html new file mode 100644 index 0000000000..43ea5dcce8 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-onshippingoptionchange-attribute.https.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for onshippingoptionchange attribute</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#onshippingoptionchange-attribute"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +const applePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } +}); +const basicCard = Object.freeze({ supportedMethods: "basic-card" }); +const defaultMethods = Object.freeze([basicCard, applePay]); +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + assert_idl_attribute(request, "onshippingoptionchange"); +}, "Must have a onshippingoptionchange IDL attribute"); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new Event("shippingoptionchange"); + let didHandle = false; + request.onshippingoptionchange = evt => { + assert_equals(ev, evt, "must be same event"); + didHandle = true; + }; + request.dispatchEvent(ev); + assert_true(didHandle, "event did not fire"); +}, `onshippingoptionchange attribute is a generic handler for "shippingoptionchange"`); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new PaymentRequestUpdateEvent("shippingoptionchange"); + let didHandle = false; + request.onshippingoptionchange = evt => { + assert_equals(ev, evt, "must be same event"); + didHandle = true; + }; + request.dispatchEvent(ev); + assert_true(didHandle, "event did not fire"); +}, `onshippingoptionchange attribute is a handler for PaymentRequestUpdateEvent`); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new PaymentRequestUpdateEvent("shippingoptionchange"); + let didHandle = false; + let didListen = false; + request.onshippingoptionchange = evt => { + assert_equals(ev, evt, "must be same event"); + didHandle = true; + }; + request.addEventListener("shippingoptionchange", evt => { + assert_equals(ev, evt, "must be same event"); + didListen = true; + }); + request.dispatchEvent(ev); + assert_true(didHandle, "onshippingoptionchange did not receive the event"); + assert_true(didListen, "addEventListener did not receive the event"); +}, `onshippingoptionchange attribute and listeners both work`); + +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-shippingAddress-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-shippingAddress-attribute.https.html new file mode 100644 index 0000000000..08918356b6 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-shippingAddress-attribute.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequest shippingAddress attribute</title> +<link rel="help" href="https://w3c.github.io/payment-request/#shippingaddress-attribute"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const validMethod = Object.freeze({ supportedMethods: "foo" }); +const validMethods = Object.freeze([validMethod]); +const validAmount = Object.freeze({ currency: "USD", value: "5.00" }); +const validTotal = Object.freeze({ + label: "label", + amount: validAmount, +}); +const validDetails = Object.freeze({ total: validTotal }); + +test(() => { + const request = new PaymentRequest(validMethods, validDetails); + assert_idl_attribute(request, "shippingAddress"); +}, "Must have a .shippingAddress IDL attribute."); + +test(() => { + const request = new PaymentRequest(validMethods, validDetails); + assert_equals(request.shippingAddress, null, "expected null"); +}, ".shippingAddress attribute must default to null."); + +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-shippingOption-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-shippingOption-attribute.https.html new file mode 100644 index 0000000000..b5f9ea65c6 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-shippingOption-attribute.https.html @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequest shippingOption attribute</title> +<link rel="help" href="https://w3c.github.io/payment-request/#shippingoption-attribute"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const validMethod = Object.freeze({ supportedMethods: "foo" }); +const validMethods = Object.freeze([validMethod]); +const validAmount = Object.freeze({ currency: "USD", value: "5.00" }); +const validTotal = Object.freeze({ + label: "label", + amount: validAmount, +}); +const validDetails = Object.freeze({ total: validTotal }); +const validShippingOption = Object.freeze({ + id: "valid", + label: "Valid shipping option", + amount: validAmount, + selected: false, +}); + +const requestShipping = Object.freeze({ + requestShipping: true, +}); + +test(() => { + const request = new PaymentRequest(validMethods, validDetails); + assert_idl_attribute(request, "shippingOption"); +}, "Must have a .shippingOption IDL attribute."); + +test(() => { + const request = new PaymentRequest(validMethods, validDetails); + assert_equals(request.shippingOption, null, "expected null"); +}, ".shippingOption attribute must default to null."); + +test(() => { + const detailsWithShippingOptions = Object.assign({}, validDetails, { + shippingOptions: [validShippingOption], + }); + const request = new PaymentRequest( + validMethods, + detailsWithShippingOptions, + requestShipping + ); + assert_equals(request.shippingOption, null, "expected null"); +}, "If there is a single shipping option, but selected is false, then .shippingOption must be null."); + +test(() => { + const shippingOption2 = Object.assign({}, validShippingOption, { + id: "valid2", + }); + const detailsWithShippingOptions = Object.assign({}, validDetails, { + shippingOptions: [validShippingOption, shippingOption2], + }); + const request = new PaymentRequest( + validMethods, + detailsWithShippingOptions, + requestShipping + ); + assert_equals(request.shippingOption, null, "expected null"); +}, "If there are multiple shipping options all with `selected` set to false, then .shippingOption is null."); + +test(() => { + const shippingOption2 = Object.assign({}, validShippingOption, { + id: "pass", + selected: true, + }); + const detailsWithShippingOptions = Object.assign({}, validDetails, { + shippingOptions: [shippingOption2, validShippingOption], + }); + const request = new PaymentRequest( + validMethods, + detailsWithShippingOptions, + requestShipping + ); + assert_equals(request.shippingOption, "pass", "expected 'pass'"); +}, "Given multiple shipping options, it must use the selected shipping option for .shippingOption value."); + +test(() => { + const shippingOption1 = Object.assign({}, validShippingOption, { + id: "fail", + selected: true, + }); + const shippingOption2 = Object.assign({}, validShippingOption, { + id: "pass", + selected: true, + }); + const detailsWithShippingOptions = Object.assign({}, validDetails, { + shippingOptions: [shippingOption1, shippingOption2, validShippingOption], + }); + const request = new PaymentRequest( + validMethods, + detailsWithShippingOptions, + requestShipping + ); + assert_equals(request.shippingOption, "pass", "expected 'pass'"); +}, "If there are multiple of the shipping options with selected true, then .shippingOption is the last selected shipping option in order."); +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-shippingType-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-shippingType-attribute.https.html new file mode 100644 index 0000000000..11f75b1c86 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-shippingType-attribute.https.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Mozilla and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequest's shippingType attribute</title> +<link rel="help" href="https://w3c.github.io/payment-request/#shippingtype-attribute"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +const paymentShipingTypes = Object.freeze(["delivery", "pickup", "shipping"]); +const applePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } +}); +const basicCard = Object.freeze({ supportedMethods: "basic-card" }); +const defaultMethods = Object.freeze([basicCard, applePay]); +const defaultDetails = Object.freeze({ + total: { + label: "", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + assert_idl_attribute(request, "shippingType"); +}, "Must have a shippingType IDL attribute"); + +test(() => { + const request1 = new PaymentRequest(defaultMethods, defaultDetails, {}); + assert_equals(request1.shippingType, null, "must be null"); + const request2 = new PaymentRequest(defaultMethods, defaultDetails, { + requestShipping: false, + }); + assert_equals(request2.shippingType, null, "must be null"); + for (const shippingType of paymentShipingTypes) { + const request = new PaymentRequest(defaultMethods, defaultDetails, { + requestShipping: false, + shippingType, + }); + assert_equals(request.shippingType, null, "must be null"); + } +}, "If options.requestShipping is false, then request.shippingType attribute is null."); + +test(() => { + // option.shippingType defaults to 'shipping' + const defaultRequest = new PaymentRequest(defaultMethods, defaultDetails, { + requestShipping: true, + }); + assert_equals(defaultRequest.shippingType, "shipping", "must be shipping"); + for (const shippingType of paymentShipingTypes) { + const request = new PaymentRequest(defaultMethods, defaultDetails, { + requestShipping: true, + shippingType, + }); + assert_equals( + request.shippingType, + shippingType, + `must be ${shippingType}` + ); + } +}, "If options.requestShipping is true, request.shippingType will be options.shippingType."); +</script> diff --git a/testing/web-platform/tests/payment-request/payment-response/helpers.js b/testing/web-platform/tests/payment-request/payment-response/helpers.js index 1242ecb743..3e4f5cfd36 100644 --- a/testing/web-platform/tests/payment-request/payment-response/helpers.js +++ b/testing/web-platform/tests/payment-request/payment-response/helpers.js @@ -65,8 +65,30 @@ async function getPaymentRequestResponse(options, id) { label: "Total due", amount: { currency: "USD", value: "1.0" }, }, + shippingOptions: [ + { + id: "fail1", + label: "Fail option 1", + amount: { currency: "USD", value: "5.00" }, + selected: false, + }, + { + id: "pass", + label: "Pass option", + amount: { currency: "USD", value: "5.00" }, + selected: true, + }, + { + id: "fail2", + label: "Fail option 2", + amount: { currency: "USD", value: "5.00" }, + selected: false, + }, + ], }; const request = new PaymentRequest(methods, details, options); + request.onshippingaddresschange = ev => ev.updateWith(details); + request.onshippingoptionchange = ev => ev.updateWith(details); const response = await request.show(); return { request, response }; } @@ -106,5 +128,23 @@ async function runManualTest(button, options, expected = {}, id = undefined) { assert_equals(typeof response.details, "object", "Expected an object"); // Testing that this does not throw: response.toJSON(); + if (options && options.requestShipping) { + assert_equals( + response.shippingOption, + "pass", + "request.shippingOption must be 'pass'" + ); + } else { + assert_equals( + request.shippingOption, + null, + "If requestShipping is falsy, request.shippingOption must be null" + ); + assert_equals( + response.shippingOption, + null, + "request.shippingOption must be null" + ); + } }, button.textContent.trim()); } diff --git a/testing/web-platform/tests/payment-request/payment-response/retry-method-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/retry-method-manual.https.html new file mode 100644 index 0000000000..a5aab49e38 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/retry-method-manual.https.html @@ -0,0 +1,296 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-retry"> +<title> + PaymentResponse.prototype.retry() method +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="helpers.js"></script> +<script> +test(() => { + assert_true( + "retry" in PaymentResponse.prototype, + "retry must be in prototype" + ); + assert_true( + PaymentResponse.prototype.retry instanceof Function, + "retry must be a function" + ); +}, "PaymentResponse.prototype must have a retry() function (smoke test)."); + +function checkCompletedCantRetry(button) { + button.disabled = true; + promise_test(async t => { + const { response } = await getPaymentRequestResponse(); + // sets response.[[complete]] to true. + await response.complete("success"); + return promise_rejects_dom( + t, + "InvalidStateError", + response.retry(), + "response.[[complete]] is true, so rejects with InvalidStateError." + ); + }, button.textContent.trim()); +} + +function repeatedCallsToRetry(button) { + button.disabled = true; + promise_test(async t => { + const { response } = await getPaymentRequestResponse(); + const retryPromise = response.retry(); + await promise_rejects_dom( + t, + "InvalidStateError", + response.retry(), + "Calling retry() again rejects with an InvalidStateError" + ); + await retryPromise; + await response.complete("success"); + }, button.textContent.trim()); +} + +function callCompleteWhileRetrying(button) { + button.disabled = true; + promise_test(async t => { + const { response } = await getPaymentRequestResponse(); + const retryPromise = response.retry(); + const completePromise1 = response.complete("success"); + const completePromise2 = response.complete("fail"); + assert_not_equals( + completePromise1, + completePromise2, + "complete() must return unique promises" + ); + await promise_rejects_dom( + t, + "InvalidStateError", + completePromise1, + "Calling complete() while retrying rejects with an InvalidStateError" + ); + await promise_rejects_dom( + t, + "InvalidStateError", + completePromise2, + "Calling complete() while retrying rejects with an InvalidStateError" + ); + assert_not_equals( + completePromise1, + completePromise2, + "complete() must return unique promises" + ); + await retryPromise; + await response.complete("success"); + }, button.textContent.trim()); +} + +function callingRequestAbortMustNotAbort(button) { + button.disabled = true; + promise_test(async t => { + const { response, request } = await getPaymentRequestResponse(); + const retryPromise = response.retry(); + await promise_rejects_dom( + t, + "InvalidStateError", + request.abort(), + "Calling request.abort() while retrying rejects with an InvalidStateError" + ); + await retryPromise; + await response.complete("success"); + }, button.textContent.trim()); +} + +function canRetryMultipleTimes(button) { + button.disabled = true; + promise_test(async t => { + const { response } = await getPaymentRequestResponse(); + assert_equals( + await response.retry(), + undefined, + "Expected undefined as the resolve value" + ); + assert_equals( + await response.retry(), + undefined, + "Expected undefined as the resolve value" + ); + await response.complete("success"); + await promise_rejects_dom( + t, + "InvalidStateError", + response.retry(), + "Calling retry() after complete() rejects with a InvalidStateError" + ); + }, button.textContent.trim()); +} + +function userCanAbortARetry(button) { + button.disabled = true; + promise_test(async t => { + const { response } = await getPaymentRequestResponse(); + await promise_rejects_dom( + t, + "AbortError", + response.retry(), + "The user aborting a retry rejects with a AbortError" + ); + await promise_rejects_dom( + t, + "InvalidStateError", + response.retry(), + "After the user aborts, response [[complete]] is true so retry() must reject with InvalidStateError" + ); + await promise_rejects_dom( + t, + "InvalidStateError", + response.complete("success"), + "After the user aborts, response [[complete]] is true, so complete() rejects with a InvalidStateError" + ); + }, button.textContent.trim()); +} + +function userIsShownErrorsFields(button) { + button.disabled = true; + promise_test(async t => { + const { response, request } = await getPaymentRequestResponse({ requestShipping: true }); + const retryPromise = response.retry({ + shippingAddress: { city: "Invalid city", addressLine: "Invalid address line" }, + }); + await retryPromise; + await response.complete("success"); + }, button.textContent.trim()); +} + +function abortTheUpdate(button) { + button.disabled = true; + promise_test(async t => { + const { response, request } = await getPaymentRequestResponse({ + requestShipping: true, + }); + const shipOptionChangePromise = new Promise(resolve => { + request.onshippingoptionchange = event => { + // causes "abort the update" to run + event.updateWith({ total: "error!" }); + resolve(); + }; + }); + const retryPromise = response.retry(); + await shipOptionChangePromise; + await promise_rejects_js( + t, + TypeError, + retryPromise, + "retry() aborts with a TypeError, because totals' value is invalid" + ); + await promise_rejects_dom( + t, + "InvalidStateError", + response.complete("success"), + "After the user aborts, response [[complete]] is true, so complete() rejects with a InvalidStateError" + ); + }, button.textContent.trim()); +} + +function callingRetryReturnsUniquePromise(button){ + button.disabled = true; + promise_test(async t => { + const { response } = await getPaymentRequestResponse(); + const retryPromise = response.retry(); + const promises = new Set([ + retryPromise, + response.retry(), + response.retry(), + ]); + assert_equals(promises.size, 3, "Must have three unique objects"); + await retryPromise; + await response.complete(); + }, button.textContent.trim()); +}; + + +</script> +<h2> + Manual Tests for PaymentResponse.retry() - Please run in order! +</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 presented with the payment sheet, use any credit card select to "Pay" multiple times. +</p> +<ol> + <li> + <button onclick="checkCompletedCantRetry(this);"> + A completed payment request cannot be retried. + </button> + </li> + <li> + <button onclick="repeatedCallsToRetry(this);"> + Calling retry() more than once yield a rejected promise, but the + retryPromise resolves independently. + </button> + </li> + <li> + <button onclick="callCompleteWhileRetrying(this);"> + Calling complete() while a retry() is in progress results in an InvalidStateError. + </button> + </li> + <li> + <button onclick="callingRequestAbortMustNotAbort(this);"> + Trying to abort() via PaymentRequest is not possible. + </button> + </li> + <li> + <button onclick="canRetryMultipleTimes(this);"> + It's possible to retry() multiple times and eventually complete(). + After complete(), however, retry() rejects with an InvalidStateError. + </button> + </li> + <li> + <p> + When shown the payment sheet, hit pay once, then abort retrying the payment. + </p> + <button onclick="userCanAbortARetry(this);"> + The user aborting retrying a payment causes the retryPromise to reject with AbortError. + Aborting a payment is causes it complete. + </button> + </li> + <li> + <p> + When shown the payment sheet, hit pay once. Check payment sheet for error fields. + Then hit escape or otherwise abort the payment. + </p> + <button onclick="userIsShownErrorsFields(this);"> + When retrying, the user is shown error fields to fix. + </button> + </li> + <li> + <p> + When shown the payment sheet, hit pay once. + Then, change the shipping option. + Select to pay again. + </p> + <button onclick="abortTheUpdate(this);"> + When "abort the update" occurs because of an update error, + the `retryPromise` is rejected and response.[[complete]] becomes true. + </button> + </li> + <li> + <p> + When shown the payment sheet, hit pay once. Then retry once. + </p> + <button onclick="callingRetryReturnsUniquePromise(this);"> + Calling retry() multiple times is always a new object. + </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">owners</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/retry-method-warnings-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/retry-method-warnings-manual.https.html new file mode 100644 index 0000000000..b68bf18309 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/retry-method-warnings-manual.https.html @@ -0,0 +1,158 @@ +<!DOCTYPE html> <meta charset="utf-8" /> +<title>Warn when errorFields don't match request[[options]]</title> +<link rel="help" href="https://github.com/w3c/payment-request/pull/807" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="helpers.js"></script> +<script> + test(() => { + assert_true( + "retry" in PaymentResponse.prototype, + "retry must be in prototype" + ); + assert_true( + PaymentResponse.prototype.retry instanceof Function, + "retry must be a function" + ); + }, "PaymentResponse.prototype must have a retry() function (smoke test)."); + + const defaultOptions = { + requestPayerName: false, + requestPayerEmail: false, + requestPayerPhone: false, + requestShipping: false, + }; + function testShowWarning(button, errorFields) { + button.disabled = true; + promise_test(async () => { + const { response } = await getPaymentRequestResponse(defaultOptions); + await response.retry(errorFields); + await response.complete(); + }); + } +</script> +<h2>Manual Tests - Please run in order!</h2> +<p> + Please open the developer console. Each of the tests below should generate a + warning in the developer console. +</p> +<p>When presented with the payment sheet, hit pay twice.</p> +<ol> + <li> + <button onclick="testShowWarning(this, {payer: {name: 'Dont show this'}});"> + Show warning if `requestPayerName` if false, and `errorFields.payer.name` is + present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {payer: {email: 'Dont show this'}});" + > + Show warning if `requestPayerEmail` if false, and `errorFields.payer.email` + is present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {payer: {phone: 'Dont show this'}});" + > + Show warning if `requestPayerPhone` if false, and `errorFields.payer.phone` + is present. + </button> + </li> + <li> + <button onclick="testShowWarning(this, {shippingAddress: {}});"> + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {addressLine: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.addressLine` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {city: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.city` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {country: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.country` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {dependentLocality: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.dependentLocality` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {organization: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.organization` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {phone: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.phone` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {postalCode: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.postalCode` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {recipient: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.recipient` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {region: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.region` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {regionCode: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.regionCode` member present. + </button> + </li> + <li> + <button + onclick="testShowWarning(this, {shippingAddress: {sortingCode: 'Dont show this'}});" + > + Show warning if `requestShipping` if false, and + `errorFields.shippingAddress.sortingCode` member present. + </button> + </li> + <li><button onclick="done()">Done!</button></li> +</ol> diff --git a/testing/web-platform/tests/payment-request/payment-response/shippingAddress-attribute-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/shippingAddress-attribute-manual.https.html new file mode 100644 index 0000000000..f9f0a6e4fa --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/shippingAddress-attribute-manual.https.html @@ -0,0 +1,101 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-shippingaddress"> +<title> + PaymentResponse.prototype.shippingAddress +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="helpers.js"></script> +<script> +async function checkNullShippingAddress(button, options) { + button.disabled = true; + const { request, response } = await getPaymentRequestResponse(options); + await response.complete(); + test(() => { + assert_idl_attribute(response, "shippingAddress"); + assert_equals( + response.shippingAddress, + null, + "Expected response.shippingAddress to be null" + ); + assert_equals( + request.shippingAddress, + null, + "Expected request.shippingAddress to be null" + ); + }, button.textContent.trim()); +} + +async function runManualTest(button, options = {}, expected = {}, id) { + button.disabled = true; + const { request, response } = await getPaymentRequestResponse(options, id); + await response.complete(); + test(() => { + assert_equals(response.requestId, request.id, `Expected ids to match`); + assert_idl_attribute(response, "shippingAddress"); + const { shippingAddress: addr } = request; + assert_true( + addr instanceof ContactAddress, + "Expect instance of ContactAddress" + ); + try { + addr.toJSON(); + } catch (err) { + assert_unreached( + `Unexpected exception calling ContactAddress.toJSON(): ${err}` + ); + } + if (expected.shippingAddress === null) { + return; + } + assert_equals(addr.country, "AF", "Expected AF for country"); + assert_true( + addr.addressLine instanceof Array, + "Expected addressLine to be an array" + ); + assert_equals( + addr.addressLine.toString(), + "1 wpt street", + "Expected '1 wpt street' for addressLine" + ); + assert_equals(addr.city, "Kabul", "Expected city to be Kabul"); + assert_equals(addr.postalCode, "1001", "Expect postalCode to be 1001"); + assert_equals(addr.recipient, "web platform test"); + }, button.textContent.trim()); +} +</script> +<h2>shippingAddress attribute</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 prompted, please enter "web platform test" as recipient, at address "1 wpt street" in "Kabul, Afghanistan", zip/postal code 1001. +</p> +<ol> + <li> + <button onclick="checkNullShippingAddress(this, undefined)"> + If options is undefined, then shippingAddress must be null. + </button> + </li> + <li> + <button onclick="checkNullShippingAddress(this, {})"> + If the requestShipping member is missing, then shippingAddress must be null. + </button> + </li> + <li> + <button onclick="checkNullShippingAddress(this, {requestShipping: false})"> + If the requestShipping member is false, then shippingAddress must be null. + </button> + </li> + <li> + <button onclick="runManualTest(this, {requestShipping: true}).then(done)"> + If the requestShipping member is true, then shippingAddress must be "1 wpt street" in "Kabul, Afghanistan", zip code 1001. + </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/payment-response/shippingOption-attribute-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/shippingOption-attribute-manual.https.html new file mode 100644 index 0000000000..687d3a52de --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/shippingOption-attribute-manual.https.html @@ -0,0 +1,43 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-shippingoption"> +<title> + PaymentResponse.prototype.complete() method +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="helpers.js"></script> +<h2>shippingOption attribute</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> + For the last test, please select the only available shipping option and select "Pay". +</p> +<ol> + <li> + <button onclick="runManualTest(this, undefined, { shippingOption: null })"> + If the options is undefined, then shippingOption must be null. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestShipping: undefined }, { shippingOption: null })"> + If the requestShipping member is missing, then shippingOption must be null. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestShipping: false }, { shippingOption: null })"> + If the requestShipping member is false, then shippingOption must be null. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestShipping: true }, { shippingOption: 'pass' }).then(done)"> + If the requestShipping member is true, then shippingOption must be the id of the selected shipping option ("pass"). + </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/shipping-address-changed-manual.https.html b/testing/web-platform/tests/payment-request/shipping-address-changed-manual.https.html new file mode 100644 index 0000000000..aad57cd724 --- /dev/null +++ b/testing/web-platform/tests/payment-request/shipping-address-changed-manual.https.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequest shippingAddress attribute</title> +<link rel="help" href="https://w3c.github.io/payment-request/#shippingaddress-attribute"> +<link rel="help" href="https://w3c.github.io/payment-request/#onshippingaddresschange-attribute"> +<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: "valid", + label: "Shipping Option", + amount: validAmount, + selected: false, +}); +const validDetails = Object.freeze({ + total: validTotal, + shippingOptions: [validShippingOption], +}); +const requestShipping = Object.freeze({ + requestShipping: true, +}); + +function testShippingAddressChange() { + promise_test(async t => { + const request = new PaymentRequest( + validMethods, + validDetails, + requestShipping + ); + assert_equals( + request.shippingAddress, + null, + "request.shippingAddress must initially be null" + ); + const listenerPromise = new Promise(resolve => { + request.addEventListener("shippingaddresschange", () => { + resolve(request.shippingAddress); + }); + }); + const handlerPromise = new Promise(resolve => { + request.onshippingaddresschange = () => { + resolve(request.shippingAddress); + }; + }); + request.show().catch(err => err); + const results = await Promise.all([listenerPromise, handlerPromise]); + results.forEach(obj => { + assert_true(obj instanceof ContactAddress, + "Expected instance of ContactAddress"); + assert_equals(obj.organization, "", "organization should be redacted"); + assert_equals(obj.phone, "", "phone should be redacted"); + assert_equals(obj.recipient, "", "recipient should be redacted"); + assert_equals(obj.addressLine.length, 0, "addressLine should be redacted"); + }); + await request.abort(); + }); + done(); +} + +</script> + +<h2>PaymentRequest shippingAddress attribute</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 presented, enter or select a shipping address. +</p> +<ol> + <li> + <button onclick="testShippingAddressChange()"> + When the shipping address is manually changed, request.shippingAddress is a ContactAddress. + </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/show-method-optional-promise-rejects.https.html b/testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html index 4a41f28fc9..3b42965503 100644 --- a/testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html +++ b/testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html @@ -84,6 +84,10 @@ total: invalidNegativeTotal, }); + // PaymentOptions + const validOptions = Object.freeze({ + requestShipping: true, + }); // PaymentItem const validPaymentItem = Object.freeze({ @@ -100,6 +104,24 @@ 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, @@ -144,7 +166,8 @@ promise_test(async (t) => { const request = new PaymentRequest( validMethods, - validDetails + validDetails, + validOptions ); await test_driver.bless("Payment request"); const detailsPromise = Promise.resolve(badDetails); @@ -194,6 +217,21 @@ ); testBadUpdate( + "Updating with duplicate shippingOptions (same IDs) results in a TypeError.", + { + ...validDetails, + shippingOptions: [validShippingOption, validShippingOption], + }, + TypeError + ); + + testBadUpdate( + "Updating with a shippingOption with an invalid currency value results in a RangError.", + { ...validDetails, shippingOptions: invalidShippingOptions }, + RangeError + ); + + testBadUpdate( "Must throw a RangeError when a modifier's total item has an invalid currency.", { ...validDetails, modifiers: [modifierWithInvalidTotal, validModifier] }, RangeError diff --git a/testing/web-platform/tests/payment-request/show-method-optional-promise-resolves-manual.https.html b/testing/web-platform/tests/payment-request/show-method-optional-promise-resolves-manual.https.html new file mode 100644 index 0000000000..5360a9704a --- /dev/null +++ b/testing/web-platform/tests/payment-request/show-method-optional-promise-resolves-manual.https.html @@ -0,0 +1,339 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test for PaymentRequest.show(optional promise) method</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +setup({ + allow_uncaught_exception: true, + explicit_done: true, + explicit_timeout: true, +}); + +// DATA USED BY TESTS +// PaymentMethods +const validMethods = Object.freeze([ + { + supportedMethods: "valid-but-wont-ever-match", + }, + { + supportedMethods: "basic-card", + }, + { + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } + }, +]); + +// Amounts +const failAmount = Object.freeze({ + currency: "USD", + value: "1.00", +}); +const passAmount = Object.freeze({ + currency: "CAD", + value: "50.00", +}); +const neutralAmount = Object.freeze({ + currency: "AUD", + value: "0.00", +}); + +// Labels +const failLabel = "💥 TEST HAS FAILED 💥"; +const passLabel = "✅ TEST HAS PASSED ✅"; +const neutralLabel = "Ignore this label"; +// Totals +const failTotal = Object.freeze({ + label: failLabel, + amount: failAmount, +}); +const passTotal = Object.freeze({ + label: passLabel, + amount: passAmount, +}); +const neutralTotal = Object.freeze({ + label: neutralLabel, + amount: passAmount, +}); + +// PaymentItem +const failPaymentItem = Object.freeze({ + amount: failAmount, + label: failLabel, +}); +const failPaymentItems = Object.freeze([failPaymentItem]); + +const passPaymentItem = Object.freeze({ + amount: passAmount, + label: passLabel, +}); +const passPaymentItems = Object.freeze([passPaymentItem]); + +// PaymentShippingOptions +const failShippingOption = Object.freeze({ + id: "fail", + label: failLabel, + amount: failAmount, +}); +const failShippingOptions = Object.freeze([failShippingOption]); + +const neutralShippingOption = Object.freeze({ + id: "neutral", + label: neutralLabel, + amount: neutralAmount, +}); + +const updatedShippingOption1 = Object.freeze({ + id: "updated-1", + label: `${passLabel} - option 1`, + amount: passAmount, +}); +const updatedShippingOption2 = Object.freeze({ + id: "updated-2", + label: `${passLabel} - option 2 (MUST BE SELECTED!)`, + amount: passAmount, + selected: true, +}); +const passShippingOptions = Object.freeze([ + updatedShippingOption1, + updatedShippingOption2, +]); + +// Modifiers +// create a modifier objects for each validMethods +// and single additional display item +const failModifiers = validMethods.map(modifier => { + const label = `${failLabel} - (${modifier.supportedMethods})`; + return { + ...modifier, + total: { + ...failTotal, + label, + }, + additionalDisplayItems: [ + { + ...failPaymentItem, + label, + }, + ], + }; +}); +// Updates the total for each, and changes the additionalDisplayItems +const passModifiers = failModifiers.map(modifier => { + const label = `${passLabel} - (${modifier.supportedMethods})`; + return { + ...modifier, + total: { + ...passTotal, + label, + }, + additionalDisplayItems: [ + { + ...passPaymentItem, + label, + }, + ], + }; +}); + +// PaymentDetailsInit +const failDetails = Object.freeze({ + displayItems: failPaymentItems, + id: "this cannot be changed", + modifiers: failModifiers, + shippingOptions: failShippingOptions, + total: failTotal, +}); + +const neutralDetails = Object.freeze({ + displayItems: [], + modifiers: [], + shippingOptions: [neutralShippingOption], + total: neutralTotal, +}); + +function smokeTest() { + promise_test(async t => { + const request = new PaymentRequest(validMethods, failDetails); + await promise_rejects_js( + t, + TypeError, + request.show({ + total: "This throws a TypeError", + }), + "expected TypeError" + ); + }, "smoke test - checks if the optional details are supported on show() method"); +} + +function runUpdateDetailsAlgorithm( + buttonElement, + details, + options = { + requestShipping: true, + }, + initialDetails = failDetails, +) { + const testAssertion = buttonElement.textContent.trim(); + buttonElement.disabled = true; + promise_test(async t => { + const request = new PaymentRequest(validMethods, initialDetails, options); + const detailsPromise = Promise.resolve(details); + const acceptPromise = request.show(detailsPromise); + assert_equals(request.id, "this cannot be changed", "id must never change."); + await promise_rejects_dom( + t, + "AbortError", + acceptPromise, + "expected AbortError" + ); + }, testAssertion); +} +</script> +<h2> + PaymentRequest <code>.show(optional detailsPromise)</code> tests +</h2> +<p> + These test cause <code>detailsPromise</code> to resolve successfully with some updated value. As such, that will cause + something in the payment sheet to change. Each test describes what is expected to change - if anything. +</p> +<p> + <strong>Instructions:</strong> Click on each button in sequence from top to bottom without refreshing the page. The payment + sheet will be shown. If required, confirm that the expected value appears in the payment sheet. Finally, manually abort/cancel + the payment request by closing the payment sheet. +</p> +<ol> + <li><button onclick="smokeTest()">If the payment sheet is shown, the test has failed.</button></li> + <li><button onclick=" + const details = { + ...neutralDetails, + id: 'fail', + }; + runUpdateDetailsAlgorithm(this, details); + "> + When the payment sheet is shown, the provided `id` must have no effect on the payment request. + </button></li> + <li><button onclick=" + const details = { + ...neutralDetails, + total: passTotal + }; + runUpdateDetailsAlgorithm(this, details); + "> + When the payment sheet is shown, the total must be CAD$50 with the label "✅ TEST HAS PASSED ✅". + </button></li> + <li><button onclick=" + const details = { + ...neutralDetails, + displayItems: passPaymentItems, + }; + runUpdateDetailsAlgorithm(this, details); + "> + When the payment sheet is shown, there must be a one display item with a value of CAD$50 with the label "✅ TEST HAS PASSED ✅". + </button> + </li> + <li><button onclick=" + const auItem = { + ...passPaymentItem, + amount: { value: '40', currency: 'AUD'}, + pending: true + } + const details = { + ...neutralDetails, + displayItems: passPaymentItems.concat(auItem), + }; + runUpdateDetailsAlgorithm(this, details); + "> + When the payment sheet is shown, there must be + two display items: One with a value of CAD$50, another with + value AUD$40 that is pending. + </button> + </li> + <li><button onclick=" + const details = { + ...neutralDetails, + shippingOptions: [updatedShippingOption1], + }; + runUpdateDetailsAlgorithm(this, details); + "> + When the payment sheet is shown, there must be a one shipping option + with a value of CAD$50. + </button> + </li> + <li><button onclick=" + const details = { + ...neutralDetails, + shippingOptions: passShippingOptions, + }; + runUpdateDetailsAlgorithm(this, details); + "> + When the payment sheet is shown, there must be + two shipping options: One with a value of CAD$50, another with + value AUD$40 that is selected. + </button> + </li> + <li><button onclick=" + const details = { + ...neutralDetails, + modifiers: passModifiers, + }; + runUpdateDetailsAlgorithm(this, details); + "> + When the payment sheet is shown, the total should be CAD$50. + </button> + </li> + <li> + <button onclick=" + const details = { + ...neutralDetails, + shippingOptions: [], + error: passLabel, + }; + runUpdateDetailsAlgorithm(this, details); + "> + When the payment sheet is shown, the string "✅ TEST HAS PASSED ✅" should be shown + somewhere in the user interface. Alternatively, the payment sheet must indicate to the + end user that it's not possible to ship their order. + </button> + </li> + <li> + <button onclick=" + const details = { + ...neutralDetails, + error: failLabel, + }; + runUpdateDetailsAlgorithm(this, details, {requestShipping: false}); + "> + When the payment sheet is shown, there should not be any errors shown. + </button> + </li> + <li> + <button onclick=" + const initialDetails = {total: passTotal, id: 'this cannot be changed'}; + const updatedDetails = {}; + runUpdateDetailsAlgorithm( + this, updatedDetails, {requestShipping: false}, initialDetails); + "> + Resolving the show promise with empty details will preserve the details from + the constructor. When the payment sheet is shown, the string + "✅ TEST HAS PASSED ✅" should be shown. + </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/updateWith-method-pmi-handling-manual.https.html b/testing/web-platform/tests/payment-request/updateWith-method-pmi-handling-manual.https.html new file mode 100644 index 0000000000..1a52978ca3 --- /dev/null +++ b/testing/web-platform/tests/payment-request/updateWith-method-pmi-handling-manual.https.html @@ -0,0 +1,140 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test for validity of payment method identifiers when calling updateWith() method</title> +<link rel="help" href="https://www.w3.org/TR/payment-request/#updatewith()-method"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +setup({ + explicit_done: true, + explicit_timeout: true, + allow_uncaught_exception: 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: "https://:@wpt.fyi:443/payment-request", +}); + +const validMethods = Object.freeze([ + validMethod, + applePay, + { supportedMethods: "basic-card" }, +]); + +const validAmount = Object.freeze({ + currency: "USD", + value: "1.0", +}); + +const validTotal = Object.freeze({ + label: "Default Total", + amount: validAmount, +}); + +const validShippingOption = Object.freeze({ + id: "standard", + label: "Shipping option", + amount: validAmount, + selected: true, +}); + +const validDetails = Object.freeze({ + total: validTotal, + shippingOptions: [validShippingOption], +}); + +const validModifier = Object.freeze({ + supportedMethods: "basic-card", + total: validTotal, +}); + +function manualTest(button, { invalidMethod }) { + button.disabled = true; + promise_test(async t => { + const request = new PaymentRequest(validMethods, validDetails, { + requestShipping: true, + }); + const listener = ev => { + const invalidModifier = Object.assign({}, validModifier, { + supportedMethods: invalidMethod, + }); + const invalidDetails = Object.assign({}, validDetails, { + modifiers: [validModifier, invalidModifier], + }); + ev.updateWith(invalidDetails); + }; + // We test against a valid and an invalid modifier + request.addEventListener("shippingaddresschange", listener, { once: true }); + const showPromise = request.show(); + await promise_rejects_js(t, RangeError, showPromise); + }, button.textContent.trim()); +} +</script> +<h2>updateWith() method: test validity of payment method identifiers.</h2> +<p> + When shown a payment sheet, select a different address. +</p> +<ol> + <li> + <button onclick="manualTest(this, {invalidMethod: 'https://:password@example.com'});"> + Must throw if the URL has a password. + </button> + </li> + <li> + <button onclick="manualTest(this, {invalidMethod: 'https://username@example.com'});"> + Must throw if the URL has a username. + </button> + </li> + <li> + <button onclick="manualTest(this, {invalidMethod: 'https://username:password@example.com/pay'});"> + Must throw if the URL has a username and a password. + </button> + </li> + <li> + <button onclick="manualTest(this, {invalidMethod: 'http://username:password@example.com/pay'});"> + Must throw if it's http, and has a username and password. + </button> + </li> + <li> + <button onclick="manualTest(this, {invalidMethod: 'http://foo.com:100000000/pay'});"> + Must throw if the URL is invalid (port range). + </button> + </li> + <li> + <button onclick="manualTest(this, {invalidMethod: 'basic-💳'});"> + Must throw if the PMI contains characters that are out of range. + </button> + </li> + <li> + <button onclick="manualTest(this, {invalidMethod: 'not-https://wpt.fyi/payment-request'});"> + Must throw if not https. + </button> + </li> + <li> + <button onclick="manualTest(this, {invalidMethod: '¡basic-*-card!'});"> + Must throw if the standardized PMI contains characters outside the ascii range. + </button> + </li> + <li> + <button onclick="manualTest(this, {invalidMethod: 'Basic-Card'});"> + Must throw if standardized PMI has uppercase characters. + </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/user-accepts-payment-request-algo-manual.https.html b/testing/web-platform/tests/payment-request/user-accepts-payment-request-algo-manual.https.html new file mode 100644 index 0000000000..300f04811f --- /dev/null +++ b/testing/web-platform/tests/payment-request/user-accepts-payment-request-algo-manual.https.html @@ -0,0 +1,230 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#user-accepts-the-payment-request-algorithm"> +<title> + User accepts the payment request algorithm +</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 basicCardMethod = Object.freeze({ + supportedMethods: "basic-card", +}); +const validMethod = Object.freeze({ + supportedMethods: "this-is-just-for-testings-will-never-match", +}); +const methods = Object.freeze([basicCardMethod, validMethod, applePay]); +const validAmount = Object.freeze({ + currency: "USD", + value: "5.00", +}); +const shippingOptions = [ + Object.freeze({ + id: "option1", + label: "Option 1", + amount: validAmount, + selected: false, + }), + Object.freeze({ + id: "option 2", + label: "Option 2", + amount: validAmount, + selected: true, + }), +]; + +const detailsNoShippingOptions = Object.freeze({ + total: { + label: "Total due", + amount: validAmount, + }, +}); + +const detailsWithShippingOptions = Object.assign({}, detailsNoShippingOptions, { + shippingOptions, +}); + +const optionsRequestNothing = Object.freeze({ + requestShipping: false, + requestPayerEmail: false, + requestPayerName: false, + requestPayerPhone: false, +}); + +const optionsRequestEverything = Object.freeze({ + requestShipping: true, + requestPayerEmail: true, + requestPayerName: true, + requestPayerPhone: true, +}); + +test(() => { + // smoke test + try { + new PaymentRequest(methods, detailsNoShippingOptions); + } catch (err) { + done(); + throw err; + } +}, "Must be able to construct a payment request (smoke test)"); + +function testAcceptRequestAlgorithm( + button, + details, + options = {}, + expectedResponse = {} +) { + button.disabled = true; + promise_test(async t => { + const request = new PaymentRequest(methods, details, options); + const response = await request.show(); + assert_true( + response instanceof PaymentResponse, + "Expected an instance of PaymentResponse." + ); + // Response [[calledComplete]] is false, so this shouldn't throw + await response.complete("success"); + // For good measure, test that subsequent complete() + for (const state of [undefined, "success", "fail", "unknown"]) { + await promise_rejects_dom( + t, + "InvalidStateError", + response.complete(state), + "Response [[calledComplete]] is true, so InvalidStateError" + ); + } + assert_equals( + request.id, + response.requestId, + "Request and response ids must match." + ); + assert_true(response.details instanceof Object, "Expected an object"); + assert_equals( + response.shippingAddress, + request.shippingAddress, + "Request and response must reference same shippingAddress (or both null)." + ); + assert_equals( + response.shippingOption, + request.shippingOption, + "Request and response must be the same value (or both null)." + ); + if (options.requestShipping === true) { + assert_true( + response.shippingAddress instanceof ContactAddress, + "Expected an instance of ContactAddress." + ); + } + const expected = { + methodName: "basic-card", + payerEmail: options.requestPayerEmail + ? expectedResponse.payerEmail + : null, + payerName: options.requestPayerName ? expectedResponse.payerName : null, + payerPhone: options.requestPayerPhone + ? expectedResponse.payerPhone + : null, + }; + for (const [attr, expectedValue] of Object.entries(expected)) { + assert_equals( + response[attr], + expectedValue, + `response.${attr} must be ${expectedValue}` + ); + } + await promise_rejects_dom( + t, + "InvalidStateError", + request.show(), + "Request [[state]] is closed, so InvalidStateError" + ); + }, button.textContent.trim()); +} + +</script> + +<section> + <h2 id="user-accepts-payment-request">User accepts payment request</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 shown the payment sheet, please input a credit card and select Pay. + </p> + <ol> + <li> + <button onclick=" + const detailsWithId = Object.assign({}, detailsNoShippingOptions, { id: 'pass' }); + testAcceptRequestAlgorithm(this, detailsWithId, optionsRequestNothing);"> + User accepts payment request, but not shipping is requested. + </button> Use any credit card to pay. + </li> + <li> + <button onclick=" + const requestShipping = Object.assign({}, optionsRequestNothing, {requestShipping: true}); + const expectedValues = { shippingOption: 'option 2' }; + testAcceptRequestAlgorithm(this, detailsWithShippingOptions, requestShipping, expectedValues);"> + User accepts payment request, merchant requests shipping. + </button> Select any shipping option, and use any credit card to pay. + </li> + <li> + <button onclick=" + const requestPayerEmail = Object.assign({}, optionsRequestNothing, {requestPayerEmail: true}); + const expectValues = { payerEmail: 'wpt@w3.org' }; + testAcceptRequestAlgorithm(this, detailsNoShippingOptions, requestPayerEmail, expectValues);"> + User accepts payment request, merchant requests email. + </button> + When prompted, please use "wpt@w3.org" as the email. + </li> + <li> + <button onclick=" + const requestPayerPhone = Object.assign({}, optionsRequestNothing, {requestPayerPhone: true}); + const expectValues = { payerPhone: '+12345678910' }; + testAcceptRequestAlgorithm(this, detailsNoShippingOptions, requestPayerPhone, expectValues);"> + User accepts payment request, merchant requests phone. + </button> + When prompted, please use "+12345678910" as the phone number. + </li> + <li> + <button onclick=" + const requestPayerName = Object.assign({}, optionsRequestNothing, {requestPayerName: true}); + const expectValues = { payerName: 'web platform test' }; + testAcceptRequestAlgorithm(this, detailsNoShippingOptions, requestPayerName, expectValues);"> + User accepts payment request, merchant requests payer's name. + </button> + When prompted, please use "web platform test" as the payer name. + </li> + <li> + <button onclick=" + const expectValues = { + payerEmail: 'wpt@w3.org', + payerName: 'web platform test', + payerPhone: '+12345678910', + shippingOption: 'option 2', + }; + testAcceptRequestAlgorithm(this, detailsWithShippingOptions, optionsRequestEverything, expectValues);"> + User accepts payment request, merchant requests everything. + </button> + When prompted, please use: "+12345678910" as the phone number, "web platform test" as the payer name, and "wpt@w3.org" as the email. Then press Pay. + </li> + <li> + <button onclick="done()">Done</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> |