mirror of
https://github.com/servo/servo
synced 2026-05-10 17:12:23 +02:00
862 lines
20 KiB
HTML
862 lines
20 KiB
HTML
<!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 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 basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
|
|
const defaultMethods = Object.freeze([basicCard]);
|
|
const defaultDetails = Object.freeze({
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
});
|
|
|
|
test(() => {
|
|
const request = new PaymentRequest(defaultMethods, defaultDetails);
|
|
assert_true(!!request.id, "must be some truthy value");
|
|
}, "If details.id is missing, assign a identifier");
|
|
|
|
test(() => {
|
|
const request1 = new PaymentRequest(defaultMethods, defaultDetails);
|
|
const request2 = new PaymentRequest(defaultMethods, defaultDetails);
|
|
assert_not_equals(request1.id, request2.id, "UA generated ID must be unique");
|
|
}, "If details.id is missing, assign a unique identifier");
|
|
|
|
test(() => {
|
|
const newDetails = Object.assign({}, defaultDetails, { id: "test123" });
|
|
const request1 = new PaymentRequest(defaultMethods, newDetails);
|
|
const request2 = new PaymentRequest(defaultMethods, newDetails);
|
|
assert_equals(request1.id, newDetails.id, `id must be ${newDetails.id}`);
|
|
assert_equals(request2.id, newDetails.id, `id must be ${newDetails.id}`);
|
|
assert_equals(request1.id, request2.id, "ids need to be the same");
|
|
}, "If the same id is provided, then use it");
|
|
|
|
test(() => {
|
|
const newDetails = Object.assign({}, defaultDetails, {
|
|
id: "".padStart(1024, "a"),
|
|
});
|
|
const request = new PaymentRequest(defaultMethods, newDetails);
|
|
assert_equals(
|
|
request.id,
|
|
newDetails.id,
|
|
`id must be provided value, even if very long and contain spaces`
|
|
);
|
|
}, "Use ids even if they are strange");
|
|
|
|
test(() => {
|
|
assert_equals(
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
id: "foo",
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
}
|
|
).id,
|
|
"foo"
|
|
);
|
|
}, "Use provided request ID");
|
|
|
|
test(() => {
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest([], {
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
});
|
|
}
|
|
);
|
|
}, "If the length of the methodData sequence is zero, then throw a TypeError");
|
|
|
|
test(() => {
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: [],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}, "If the length of the paymentMethod.supportedMethods sequence is zero, " + "then throw a TypeError");
|
|
|
|
test(() => {
|
|
let itThrows = false;
|
|
try {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: ["some-data"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
}
|
|
);
|
|
} catch (err) {
|
|
itThrows = true;
|
|
}
|
|
assert_false(itThrows, "shouldn't throw when using a list");
|
|
}, "Method data must be JSON-serializable object (a list in this case)");
|
|
|
|
test(() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: {
|
|
some: "data",
|
|
},
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
}
|
|
);
|
|
}, "Method data must be JSON-serializable object (an object in this case)");
|
|
|
|
test(() => {
|
|
const recursiveDictionary = {};
|
|
recursiveDictionary.foo = recursiveDictionary;
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: recursiveDictionary,
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
}
|
|
);
|
|
}
|
|
);
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: "a string",
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
}
|
|
);
|
|
}
|
|
);
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: null,
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
}
|
|
);
|
|
},
|
|
"Even though null is JSON-serializable, it's not type 'Object' per ES spec"
|
|
);
|
|
}, "Rethrow any exceptions of JSON-serializing paymentMethod.data into a string");
|
|
|
|
// process total
|
|
const invalidAmounts = [
|
|
"-",
|
|
"notdigits",
|
|
"ALSONOTDIGITS",
|
|
"10.",
|
|
".99",
|
|
"-10.",
|
|
"-.99",
|
|
"10-",
|
|
"1-0",
|
|
"1.0.0",
|
|
"1/3",
|
|
"",
|
|
null,
|
|
" 1.0 ",
|
|
" 1.0 ",
|
|
"1.0 ",
|
|
"USD$1.0",
|
|
"$1.0",
|
|
{
|
|
toString() {
|
|
return " 1.0";
|
|
},
|
|
},
|
|
];
|
|
const invalidTotalAmounts = invalidAmounts.concat([
|
|
"-1",
|
|
"-1.0",
|
|
"-1.00",
|
|
"-1000.000",
|
|
]);
|
|
for (const amount of invalidTotalAmounts) {
|
|
test(() => {
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: amount,
|
|
},
|
|
},
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}, `If details.total.amount.value is not a valid decimal monetary value (in this case "${amount}"), then throw a TypeError`);
|
|
}
|
|
|
|
for (const prop in ["displayItems", "shippingOptions", "modifiers"]) {
|
|
test(() => {
|
|
try {
|
|
const details = Object.assign({}, defaultDetails, { [prop]: [] });
|
|
new PaymentRequest(defaultMethods, details);
|
|
} catch (err) {
|
|
assert_true(false, `${prop} can be zero length`);
|
|
}
|
|
}, `PaymentDetailsBase.${prop} can be 0 length`);
|
|
}
|
|
|
|
test(() => {
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "-1.00",
|
|
},
|
|
},
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}, "If the first character of details.total.amount.value is U+002D HYPHEN-MINUS, then throw a TypeError");
|
|
|
|
for (const amount of invalidAmounts) {
|
|
test(() => {
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
displayItems: [
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: amount,
|
|
},
|
|
},
|
|
],
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}, "For each item in details.displayItems: if item.amount.value is not " + 'a valid decimal monetary value (in this case "' + amount + '"), then throw a TypeError');
|
|
}
|
|
|
|
test(() => {
|
|
let itThrows = false;
|
|
try {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: {
|
|
supportedTypes: ["debit"],
|
|
},
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
displayItems: [
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "-1000",
|
|
},
|
|
},
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "AUD",
|
|
value: "-2000.00",
|
|
},
|
|
},
|
|
],
|
|
}
|
|
);
|
|
} catch (err) {
|
|
itThrows = true;
|
|
}
|
|
assert_false(itThrows, "shouldn't throw when given a negative value");
|
|
}, "Negative values are allowed for displayItems.amount.value, irrespective of total amount");
|
|
|
|
test(() => {
|
|
let itThrows = false;
|
|
const largeMoney = "1".repeat(510);
|
|
|
|
try {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: `${largeMoney}.${largeMoney}`,
|
|
},
|
|
},
|
|
displayItems: [
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: `-${largeMoney}`,
|
|
},
|
|
},
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "AUD",
|
|
value: `-${largeMoney}.${largeMoney}`,
|
|
},
|
|
},
|
|
],
|
|
}
|
|
);
|
|
} catch (err) {
|
|
itThrows = true;
|
|
}
|
|
assert_false(itThrows, "shouldn't throw when given absurd monetary values");
|
|
}, "it handles high precision currency values without throwing");
|
|
|
|
// Process shipping options:
|
|
const defaultAmount = Object.freeze({
|
|
currency: "USD",
|
|
value: "0.0",
|
|
});
|
|
const defaultShippingOption = Object.freeze({
|
|
id: "default",
|
|
label: "",
|
|
amount: defaultAmount,
|
|
selected: false,
|
|
});
|
|
const defaultShippingOptions = Object.freeze([
|
|
Object.assign({}, defaultShippingOption),
|
|
]);
|
|
|
|
for (const amount of invalidAmounts) {
|
|
test(() => {
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
const invalidAmount = Object.assign({}, defaultAmount, {
|
|
value: amount,
|
|
});
|
|
const invalidShippingOption = Object.assign({}, defaultShippingOption, {
|
|
amount: invalidAmount,
|
|
});
|
|
const details = Object.assign({}, defaultDetails, {
|
|
shippingOptions: [invalidShippingOption],
|
|
});
|
|
new PaymentRequest(defaultMethods, details);
|
|
}
|
|
);
|
|
}, "For each option in details.shippingOptions: if option.amount.value is not " + 'a valid decimal monetary value (in this case "' + amount + '"), then throw a TypeError');
|
|
}
|
|
|
|
test(() => {
|
|
const shippingOptions = [defaultShippingOption];
|
|
const details = Object.assign({}, defaultDetails, { shippingOptions });
|
|
const request = new PaymentRequest(defaultMethods, details);
|
|
assert_equals(
|
|
request.shippingOption,
|
|
null,
|
|
"request.shippingOption must be null"
|
|
);
|
|
}, "If there is no selected shipping option, then PaymentRequest.shippingOption remains null");
|
|
|
|
test(() => {
|
|
const selectedOption = Object.assign({}, defaultShippingOption, {
|
|
selected: true,
|
|
id: "PASS",
|
|
});
|
|
const shippingOptions = [selectedOption];
|
|
const details = Object.assign({}, defaultDetails, { shippingOptions });
|
|
const request = new PaymentRequest(defaultMethods, details);
|
|
assert_equals(request.shippingOption, "PASS", "selected option must be PASS");
|
|
}, "If there is a selected shipping option, then it becomes synchronously selected");
|
|
|
|
test(() => {
|
|
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: "PASS",
|
|
});
|
|
const shippingOptions = [failOption1, failOption2, passOption];
|
|
const details = Object.assign({}, defaultDetails, { shippingOptions });
|
|
const request = new PaymentRequest(defaultMethods, details);
|
|
assert_equals(request.shippingOption, "PASS", "selected option must PASS");
|
|
}, "If there is a multiple selected shipping options, only the last is selected");
|
|
|
|
test(() => {
|
|
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 request = new PaymentRequest(defaultMethods, details);
|
|
assert_equals(request.shippingOption, null, "selected option must be null");
|
|
}, "If there are any duplicate shipping option ids, then there are no shipping options");
|
|
|
|
// Process payment details modifiers:
|
|
for (const amount of invalidTotalAmounts) {
|
|
test(() => {
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
const invalidModifier = {
|
|
supportedMethods: ["basic-card"],
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: amount,
|
|
},
|
|
},
|
|
};
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
modifiers: [invalidModifier],
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.0",
|
|
},
|
|
},
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}, `If modifier.total.amount.value is not a valid decimal monetary value (in this case "${amount}"), then throw a TypeError`);
|
|
}
|
|
|
|
for (const amount of invalidAmounts) {
|
|
test(() => {
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
const invalidModifier = {
|
|
supportedMethods: ["basic-card"],
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.0",
|
|
},
|
|
},
|
|
additionalDisplayItems: [
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: amount,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
modifiers: [invalidModifier],
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.0",
|
|
},
|
|
},
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}, `If amount.value of additionalDisplayItems is is not a valid decimal monetary value (in this case "${amount}"), then throw a TypeError`);
|
|
}
|
|
|
|
test(() => {
|
|
let itThrows = false;
|
|
try {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
modifiers: [
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: ["some-data"],
|
|
},
|
|
],
|
|
}
|
|
);
|
|
} catch (err) {
|
|
itThrows = true;
|
|
}
|
|
assert_false(itThrows, "shouldn't throw when given a list");
|
|
}, "Modifier data must be JSON-serializable object (a list in this case)");
|
|
|
|
test(() => {
|
|
let itThrows = false;
|
|
try {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
modifiers: [
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: {
|
|
some: "data",
|
|
},
|
|
},
|
|
],
|
|
}
|
|
);
|
|
} catch (err) {
|
|
itThrows = true;
|
|
}
|
|
assert_false(itThrows, "shouldn't throw when given an object value");
|
|
}, "Modifier data must be JSON-serializable object (a object in this case)");
|
|
|
|
test(() => {
|
|
const recursiveDictionary = {};
|
|
recursiveDictionary.foo = recursiveDictionary;
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
modifiers: [
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: recursiveDictionary,
|
|
},
|
|
],
|
|
}
|
|
);
|
|
}
|
|
);
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
modifiers: [
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: "a string",
|
|
},
|
|
],
|
|
}
|
|
);
|
|
}
|
|
);
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
modifiers: [
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
data: null,
|
|
},
|
|
],
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}, "Rethrow any exceptions of JSON-serializing modifier.data into a string");
|
|
|
|
//Setting ShippingType attribute during construction
|
|
test(() => {
|
|
assert_throws(
|
|
{
|
|
name: "TypeError",
|
|
},
|
|
() => {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: ["basic-card"],
|
|
},
|
|
],
|
|
{
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "1.00",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
shippingType: "invalid",
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}, "Shipping type should be valid");
|
|
|
|
test(() => {
|
|
const request = new PaymentRequest(defaultMethods, defaultDetails, {});
|
|
assert_equals(request.shippingAddress, null, "must be null");
|
|
}, "PaymentRequest.shippingAddress must initially be null");
|
|
|
|
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");
|
|
}, "If options.requestShipping is not set, then request.shippingType attribute is null.");
|
|
|
|
test(() => {
|
|
// 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>
|