| <!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> |