Revision 0fb0745ad0ebcb51e262f043115ff1fa5b26f876 authored by Philip Jägenstedt on 06 April 2018, 11:10:29 UTC, committed by Philip Jägenstedt on 06 April 2018, 12:08:26 UTC
First dos2unix was used and for text-fonts-202-t-manual.svg this also
removes a UTF-8 BOM.

Then all files were passed through git stripspace, which also removed
some double blank lines which don't look to have been meaningful.

Finally all tabs were replaced by two spaces, as that's the tab width
that seems to have been used when editing these files. Some odd
indentation remains and would have to be fixed manually.
1 parent b80da5d
Raw File
payment-request-constructor.https.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://wpt.fyi/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(new TypeError(), () => new PaymentRequest([], defaultDetails));
}, "If the length of the methodData sequence is zero, then throw a TypeError");

test(() => {
  smokeTest();
  const JSONSerializables = [[], { object: {} }];
  for (const data of JSONSerializables) {
    try {
      const methods = [
        {
          supportedMethods: "https://wpt.fyi/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(new TypeError(), () => {
    const methods = [
      {
        supportedMethods: "https://wpt.fyi/payment-request",
        data: recursiveDictionary,
      },
    ];
    new PaymentRequest(methods, defaultDetails);
  });
  assert_throws(new TypeError(), () => {
    const methods = [
      {
        supportedMethods: "https://wpt.fyi/payment-request",
        data: "a string",
      },
    ];
    new PaymentRequest(methods, defaultDetails);
  });
  assert_throws(
    new TypeError(),
    () => {
      const methods = [
        {
          supportedMethods: "https://wpt.fyi/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(
      new 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", "shippingOptions", "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(new 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(
      new 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://wpt.fyi/payment-request",
          data: {
            supportedTypes: ["debit"],
          },
        },
      ],
      {
        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 shipping options:

const defaultShippingOption = Object.freeze({
  id: "default",
  label: "",
  amount: defaultAmount,
  selected: false,
});
const defaultShippingOptions = Object.freeze([
  Object.assign({}, defaultShippingOption),
]);

test(() => {
  smokeTest();
  for (const amount of invalidAmounts) {
    const invalidAmount = Object.assign({}, defaultAmount, {
      value: amount,
    });
    const invalidShippingOption = Object.assign({}, defaultShippingOption, {
      amount: invalidAmount,
    });
    const details = Object.assign({}, defaultDetails, {
      shippingOptions: [invalidShippingOption],
    });
    assert_throws(
      new TypeError(),
      () => {
        new PaymentRequest(defaultMethods, details, { requestShipping: true });
      },
      `Expected TypeError for option.amount.value: "${amount}"`
    );
  }
}, `For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value, then throw a TypeError`);

test(() => {
  smokeTest();
  const shippingOptions = [defaultShippingOption];
  const details = Object.assign({}, defaultDetails, { shippingOptions });
  const request = new PaymentRequest(defaultMethods, details);
  assert_equals(
    request.shippingOption,
    null,
    "shippingOption must be null, as requestShipping is missing"
  );
  // defaultDetails lacks shipping options
  const request2 = new PaymentRequest(defaultMethods, defaultDetails, {
    requestShipping: true,
  });
  assert_equals(
    request2.shippingOption,
    null,
    `request2.shippingOption must be null`
  );
}, "If there is no selected shipping option, then PaymentRequest.shippingOption remains null");

test(() => {
  smokeTest();
  const selectedOption = Object.assign({}, defaultShippingOption, {
    selected: true,
    id: "the-id",
  });
  const shippingOptions = [selectedOption];
  const details = Object.assign({}, defaultDetails, { shippingOptions });
  const requestNoShippingRequested1 = new PaymentRequest(
    defaultMethods,
    details
  );
  assert_equals(
    requestNoShippingRequested1.shippingOption,
    null,
    "Must be null when no shipping is requested (defaults to false)"
  );
  const requestNoShippingRequested2 = new PaymentRequest(
    defaultMethods,
    details,
    { requestShipping: false }
  );
  assert_equals(
    requestNoShippingRequested2.shippingOption,
    null,
    "Must be null when requestShipping is false"
  );
  const requestWithShipping = new PaymentRequest(defaultMethods, details, {
    requestShipping: "truthy value",
  });
  assert_equals(
    requestWithShipping.shippingOption,
    "the-id",
    "Selected option must be 'the-id'"
  );
}, "If there is a selected shipping option, and requestShipping is set, then that option becomes synchronously selected");

test(() => {
  smokeTest();
  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: "the-id",
  });
  const shippingOptions = [failOption1, failOption2, passOption];
  const details = Object.assign({}, defaultDetails, { shippingOptions });
  const requestNoShipping = new PaymentRequest(defaultMethods, details, {
    requestShipping: false,
  });
  assert_equals(
    requestNoShipping.shippingOption,
    null,
    "shippingOption must be null, as requestShipping is false"
  );
  const requestWithShipping = new PaymentRequest(defaultMethods, details, {
    requestShipping: true,
  });
  assert_equals(
    requestWithShipping.shippingOption,
    "the-id",
    "selected option must 'the-id"
  );
}, "If requestShipping is set, and if there is a multiple selected shipping options, only the last is selected.");

test(() => {
  smokeTest();
  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 requestNoShipping = new PaymentRequest(defaultMethods, details);
  assert_equals(
    requestNoShipping.shippingOption,
    null,
    "shippingOption must be null, because requestShipping is false"
  );
  assert_throws(
    new TypeError(),
    () => {
      new PaymentRequest(defaultMethods, details, { requestShipping: true });
    },
    "Expected to throw a TypeError because duplicate IDs"
  );
}, "If there are any duplicate shipping option ids, and shipping is requested, then throw a TypeError");

test(() => {
  smokeTest();
  const dupShipping1 = Object.assign({}, defaultShippingOption, {
    selected: true,
    id: "DUPLICATE",
    label: "Fail 1",
  });
  const dupShipping2 = Object.assign({}, defaultShippingOption, {
    selected: false,
    id: "DUPLICATE",
    label: "Fail 2",
  });
  const shippingOptions = [dupShipping1, defaultShippingOption, dupShipping2];
  const details = Object.assign({}, defaultDetails, { shippingOptions });
  const requestNoShipping = new PaymentRequest(defaultMethods, details);
  assert_equals(
    requestNoShipping.shippingOption,
    null,
    "shippingOption must be null, because requestShipping is false"
  );
  assert_throws(
    new TypeError(),
    () => {
      new PaymentRequest(defaultMethods, details, { requestShipping: true });
    },
    "Expected to throw a TypeError because duplicate IDs"
  );
}, "Throw when there are duplicate shippingOption ids, even if other values are different");

// Process payment details modifiers:
test(() => {
  smokeTest();
  for (const invalidTotal of invalidTotalAmounts) {
    const invalidModifier = {
      supportedMethods: "https://wpt.fyi/payment-request",
      total: {
        label: "",
        amount: {
          currency: "USD",
          value: invalidTotal,
        },
      },
    };
    assert_throws(
      new 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://wpt.fyi/payment-request",
      total: defaultTotal,
      additionalDisplayItems: [
        {
          label: "",
          amount: {
            currency: "USD",
            value: invalidAmount,
          },
        },
      ],
    };
    assert_throws(
      new 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://wpt.fyi/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://wpt.fyi/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://wpt.fyi/payment-request",
        data: recursiveDictionary,
      },
    ],
  });
  assert_throws(new TypeError(), () => {
    new PaymentRequest(defaultMethods, modifiedDetails);
  });
}, "Rethrow any exceptions of JSON-serializing modifier.data");

//Setting ShippingType attribute during construction
test(() => {
  smokeTest();
  assert_throws(new TypeError(), () => {
    new PaymentRequest(defaultMethods, defaultDetails, {
      shippingType: "invalid",
    });
  });
}, "Shipping type should be valid");

test(() => {
  smokeTest();
  const request = new PaymentRequest(defaultMethods, defaultDetails, {});
  assert_equals(request.shippingAddress, null, "must be null");
}, "PaymentRequest.shippingAddress must initially be null");

test(() => {
  smokeTest();
  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(() => {
  smokeTest();
  // 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>
back to top