mirror of
https://github.com/servo/servo
synced 2026-05-12 18:06:32 +02:00
478 lines
12 KiB
HTML
478 lines
12 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 testMethod = Object.freeze({
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
});
|
|
const defaultMethods = Object.freeze([testMethod]);
|
|
const defaultAmount = Object.freeze({
|
|
currency: "USD",
|
|
value: "1.0",
|
|
});
|
|
const defaultNumberAmount = Object.freeze({
|
|
currency: "USD",
|
|
value: 1.0,
|
|
});
|
|
const defaultTotal = Object.freeze({
|
|
label: "Default Total",
|
|
amount: defaultAmount,
|
|
});
|
|
const defaultNumberTotal = Object.freeze({
|
|
label: "Default Number Total",
|
|
amount: defaultNumberAmount,
|
|
});
|
|
const defaultDetails = Object.freeze({
|
|
total: defaultTotal,
|
|
displayItems: [
|
|
{
|
|
label: "Default Display Item",
|
|
amount: defaultAmount,
|
|
},
|
|
],
|
|
});
|
|
const defaultNumberDetails = Object.freeze({
|
|
total: defaultNumberTotal,
|
|
displayItems: [
|
|
{
|
|
label: "Default Display Item",
|
|
amount: defaultNumberAmount,
|
|
},
|
|
],
|
|
});
|
|
|
|
// Avoid false positives, this should always pass
|
|
function smokeTest() {
|
|
new PaymentRequest(defaultMethods, defaultDetails);
|
|
new PaymentRequest(defaultMethods, defaultNumberDetails);
|
|
}
|
|
test(() => {
|
|
smokeTest();
|
|
const request = new PaymentRequest(defaultMethods, defaultDetails);
|
|
assert_true(Boolean(request.id), "must be some truthy value");
|
|
}, "If details.id is missing, assign an identifier");
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
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");
|
|
const seen = new Set();
|
|
// Let's try creating lots of requests, and make sure they are all unique
|
|
for (let i = 0; i < 1024; i++) {
|
|
const request = new PaymentRequest(defaultMethods, defaultDetails);
|
|
assert_false(
|
|
seen.has(request.id),
|
|
`UA generated ID must be unique, but got duplicate! (${request.id})`
|
|
);
|
|
seen.add(request.id);
|
|
}
|
|
}, "If details.id is missing, assign a unique identifier");
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
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(() => {
|
|
smokeTest();
|
|
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(() => {
|
|
smokeTest();
|
|
const request = new PaymentRequest(
|
|
defaultMethods,
|
|
Object.assign({}, defaultDetails, { id: "foo" })
|
|
);
|
|
assert_equals(request.id, "foo");
|
|
}, "Use provided request ID");
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
assert_throws_js(TypeError, () => new PaymentRequest([], defaultDetails));
|
|
}, "If the length of the methodData sequence is zero, then throw a TypeError");
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
const duplicateMethods = [
|
|
{
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
},
|
|
{
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
},
|
|
];
|
|
assert_throws_js(RangeError, () => new PaymentRequest(duplicateMethods, defaultDetails));
|
|
}, "If payment method is duplicate, then throw a RangeError");
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
const JSONSerializables = [[], { object: {} }];
|
|
for (const data of JSONSerializables) {
|
|
try {
|
|
const methods = [
|
|
{
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
data,
|
|
},
|
|
];
|
|
new PaymentRequest(methods, defaultDetails);
|
|
} catch (err) {
|
|
assert_unreached(
|
|
`Unexpected error parsing stringifiable JSON: ${JSON.stringify(
|
|
data
|
|
)}: ${err.message}`
|
|
);
|
|
}
|
|
}
|
|
}, "Modifier method data must be JSON-serializable object");
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
const recursiveDictionary = {};
|
|
recursiveDictionary.foo = recursiveDictionary;
|
|
assert_throws_js(TypeError, () => {
|
|
const methods = [
|
|
{
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
data: recursiveDictionary,
|
|
},
|
|
];
|
|
new PaymentRequest(methods, defaultDetails);
|
|
});
|
|
assert_throws_js(TypeError, () => {
|
|
const methods = [
|
|
{
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
data: "a string",
|
|
},
|
|
];
|
|
new PaymentRequest(methods, defaultDetails);
|
|
});
|
|
assert_throws_js(
|
|
TypeError,
|
|
() => {
|
|
const methods = [
|
|
{
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
data: null,
|
|
},
|
|
];
|
|
new PaymentRequest(methods, defaultDetails);
|
|
},
|
|
"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",
|
|
-10,
|
|
]);
|
|
test(() => {
|
|
smokeTest();
|
|
for (const invalidAmount of invalidTotalAmounts) {
|
|
const invalidDetails = {
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: invalidAmount,
|
|
},
|
|
},
|
|
};
|
|
assert_throws_js(
|
|
TypeError,
|
|
() => {
|
|
new PaymentRequest(defaultMethods, invalidDetails);
|
|
},
|
|
`Expect TypeError when details.total.amount.value is ${invalidAmount}`
|
|
);
|
|
}
|
|
}, `If details.total.amount.value is not a valid decimal monetary value, then throw a TypeError`);
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
for (const prop in ["displayItems", "modifiers"]) {
|
|
try {
|
|
const details = Object.assign({}, defaultDetails, { [prop]: [] });
|
|
new PaymentRequest(defaultMethods, details);
|
|
assert_unreached(`PaymentDetailsBase.${prop} can be zero length`);
|
|
} catch (err) {}
|
|
}
|
|
}, `PaymentDetailsBase members can be 0 length`);
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
assert_throws_js(TypeError, () => {
|
|
new PaymentRequest(defaultMethods, {
|
|
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");
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
for (const invalidAmount of invalidAmounts) {
|
|
const invalidDetails = {
|
|
total: defaultAmount,
|
|
displayItems: [
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: invalidAmount,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
assert_throws_js(
|
|
TypeError,
|
|
() => {
|
|
new PaymentRequest(defaultMethods, invalidDetails);
|
|
},
|
|
`Expected TypeError when item.amount.value is "${invalidAmount}"`
|
|
);
|
|
}
|
|
}, `For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value, then throw a TypeError`);
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
try {
|
|
new PaymentRequest(
|
|
[
|
|
{
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
},
|
|
],
|
|
{
|
|
total: defaultTotal,
|
|
displayItems: [
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: "-1000",
|
|
},
|
|
},
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "AUD",
|
|
value: "-2000.00",
|
|
},
|
|
},
|
|
],
|
|
}
|
|
);
|
|
} catch (err) {
|
|
assert_unreached(
|
|
`shouldn't throw when given a negative value: ${err.message}`
|
|
);
|
|
}
|
|
}, "Negative values are allowed for displayItems.amount.value, irrespective of total amount");
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
const largeMoney = "1".repeat(510);
|
|
try {
|
|
new PaymentRequest(defaultMethods, {
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: `${largeMoney}.${largeMoney}`,
|
|
},
|
|
},
|
|
displayItems: [
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: `-${largeMoney}`,
|
|
},
|
|
},
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "AUD",
|
|
value: `-${largeMoney}.${largeMoney}`,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
} catch (err) {
|
|
assert_unreached(
|
|
`shouldn't throw when given absurd monetary values: ${err.message}`
|
|
);
|
|
}
|
|
}, "it handles high precision currency values without throwing");
|
|
|
|
// Process payment details modifiers:
|
|
test(() => {
|
|
smokeTest();
|
|
for (const invalidTotal of invalidTotalAmounts) {
|
|
const invalidModifier = {
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
total: {
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: invalidTotal,
|
|
},
|
|
},
|
|
};
|
|
assert_throws_js(
|
|
TypeError,
|
|
() => {
|
|
new PaymentRequest(defaultMethods, {
|
|
modifiers: [invalidModifier],
|
|
total: defaultTotal,
|
|
});
|
|
},
|
|
`Expected TypeError for modifier.total.amount.value: "${invalidTotal}"`
|
|
);
|
|
}
|
|
}, `Throw TypeError if modifier.total.amount.value is not a valid decimal monetary value`);
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
for (const invalidAmount of invalidAmounts) {
|
|
const invalidModifier = {
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
total: defaultTotal,
|
|
additionalDisplayItems: [
|
|
{
|
|
label: "",
|
|
amount: {
|
|
currency: "USD",
|
|
value: invalidAmount,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
assert_throws_js(
|
|
TypeError,
|
|
() => {
|
|
new PaymentRequest(defaultMethods, {
|
|
modifiers: [invalidModifier],
|
|
total: defaultTotal,
|
|
});
|
|
},
|
|
`Expected TypeError when given bogus modifier.additionalDisplayItems.amount of "${invalidModifier}"`
|
|
);
|
|
}
|
|
}, `If amount.value of additionalDisplayItems is not a valid decimal monetary value, then throw a TypeError`);
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
const modifiedDetails = Object.assign({}, defaultDetails, {
|
|
modifiers: [
|
|
{
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
data: ["some-data"],
|
|
},
|
|
],
|
|
});
|
|
try {
|
|
new PaymentRequest(defaultMethods, modifiedDetails);
|
|
} catch (err) {
|
|
assert_unreached(
|
|
`Unexpected exception thrown when given a list: ${err.message}`
|
|
);
|
|
}
|
|
}, "Modifier data must be JSON-serializable object (an Array in this case)");
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
const modifiedDetails = Object.assign({}, defaultDetails, {
|
|
modifiers: [
|
|
{
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
data: {
|
|
some: "data",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
try {
|
|
new PaymentRequest(defaultMethods, modifiedDetails);
|
|
} catch (err) {
|
|
assert_unreached(
|
|
`shouldn't throw when given an object value: ${err.message}`
|
|
);
|
|
}
|
|
}, "Modifier data must be JSON-serializable object (an Object in this case)");
|
|
|
|
test(() => {
|
|
smokeTest();
|
|
const recursiveDictionary = {};
|
|
recursiveDictionary.foo = recursiveDictionary;
|
|
const modifiedDetails = Object.assign({}, defaultDetails, {
|
|
modifiers: [
|
|
{
|
|
supportedMethods: "https://{{domains[nonexistent]}}/payment-request",
|
|
data: recursiveDictionary,
|
|
},
|
|
],
|
|
});
|
|
assert_throws_js(TypeError, () => {
|
|
new PaymentRequest(defaultMethods, modifiedDetails);
|
|
});
|
|
}, "Rethrow any exceptions of JSON-serializing modifier.data");
|
|
|
|
</script>
|