Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!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>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
"use strict";
const testMethod = Object.freeze({
});
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 = [
{
},
{
},
];
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 = [
{
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 = [
{
data: recursiveDictionary,
},
];
new PaymentRequest(methods, defaultDetails);
});
assert_throws_js(TypeError, () => {
const methods = [
{
data: "a string",
},
];
new PaymentRequest(methods, defaultDetails);
});
assert_throws_js(
TypeError,
() => {
const methods = [
{
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(
[
{
},
],
{
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 = {
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 = {
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: [
{
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: [
{
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: [
{
data: recursiveDictionary,
},
],
});
assert_throws_js(TypeError, () => {
new PaymentRequest(defaultMethods, modifiedDetails);
});
}, "Rethrow any exceptions of JSON-serializing modifier.data");
</script>