Source code

Revision control

Copy as Markdown

Other Tools

<!doctype html>
<meta charset="utf8">
<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>