<!DOCTYPE html>
<meta charset="utf-8" />
<title>Test for PaymentRequest.show(optional detailsPromise) method</title>
<link
  rel="help"
  href="https://w3c.github.io/browser-payment-api/#show-method"
/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script>
  // See function testBadUpdate() for test details!
  setup({
    allow_uncaught_exception: true,
  });

  // == TEST DATA ===
  // PaymentMethod
  const validMethod = Object.freeze({
    supportedMethods: "valid-but-wont-ever-match",
  });

  const validMethodBasicCard = Object.freeze({
    supportedMethods: "basic-card",
  });

  const validMethodApplePay = Object.freeze({
    supportedMethods: "https://apple.com/apple-pay",
    data: {
      version: 3,
      merchantIdentifier: "merchant.com.example",
      countryCode: "US",
      merchantCapabilities: ["supports3DS"],
      supportedNetworks: ["visa"],
    },
  });

  // Methods
  const validMethods = Object.freeze([
    validMethodBasicCard,
    validMethod,
    validMethodApplePay,
  ]);

  // Amounts
  const validAmount = Object.freeze({
    currency: "USD",
    value: "1.00",
  });

  const invalidAmount = Object.freeze({
    currency: "¡INVALID!",
    value: "A1.0",
  });

  const negativeAmount = Object.freeze({
    currency: "USD",
    value: "-1.00",
  });

  // Totals
  const validTotal = Object.freeze({
    label: "Valid Total",
    amount: validAmount,
  });

  const invalidTotal = Object.freeze({
    label: "Invalid Total",
    amount: invalidAmount,
  });

  const invalidNegativeTotal = Object.freeze({
    label: "Invalid negative total",
    amount: negativeAmount,
  });

  // PaymentDetailsInit
  const validDetails = Object.freeze({
    total: validTotal,
  });

  const invalidDetailsNegativeTotal = Object.freeze({
    total: invalidNegativeTotal,
  });


  // PaymentItem
  const validPaymentItem = Object.freeze({
    amount: validAmount,
    label: "Valid payment item",
  });

  const invalidPaymentItem = Object.freeze({
    amount: invalidAmount,
    label: "Invalid payment item",
  });

  // PaymentItem
  const validPaymentItems = Object.freeze([validPaymentItem]);
  const invalidPaymentItems = Object.freeze([invalidPaymentItem]);

  // PaymentDetailsModifier
  const validModifier = Object.freeze({
    additionalDisplayItems: validPaymentItems,
    supportedMethods: "valid-but-wont-ever-match",
    total: validTotal,
  });

  const modifierWithInvalidDisplayItems = Object.freeze({
    additionalDisplayItems: invalidPaymentItems,
    supportedMethods: "basic-card",
    total: validTotal,
  });

  const modifierWithValidDisplayItems = Object.freeze({
    additionalDisplayItems: validPaymentItems,
    supportedMethods: "basic-card",
    total: validTotal,
  });

  const modifierWithInvalidTotal = Object.freeze({
    additionalDisplayItems: validPaymentItems,
    supportedMethods: "basic-card",
    total: invalidTotal,
  });

  const recursiveData = {};
  recursiveData.foo = recursiveData;
  Object.freeze(recursiveData);

  const modifierWithRecursiveData = Object.freeze({
    supportedMethods: "basic-card",
    total: validTotal,
    data: recursiveData,
  });
  // == END OF TEST DATA ===
  /*
    These test work by creating a "valid" payment request and then
    performing a bad update via `show(detailsPromise)`.
    The `badDetails` cause detailsPromise to reject with `expectedError`.
    */
  function testBadUpdate(testAssertion, badDetails, expectedError) {
    promise_test(async (t) => {
      const request = new PaymentRequest(
        validMethods,
        validDetails
      );
      await test_driver.bless("Payment request");
      const detailsPromise = Promise.resolve(badDetails);
      const acceptPromise = request.show(detailsPromise);
      const test_func =
        typeof expectedError == "function"
          ? promise_rejects_js
          : promise_rejects_dom;
      await test_func(
        t,
        expectedError,
        acceptPromise,
        "badDetails must cause acceptPromise to reject with expectedError"
      );
    }, testAssertion);
  }

  const rejectedPromise = Promise.reject(new SyntaxError("test"));
  testBadUpdate(
    "Rejection of detailsPromise must abort the update with an 'AbortError' DOMException.",
    rejectedPromise,
    "AbortError"
  );

  testBadUpdate(
    "Total in the update is a string, so converting to IDL must abort the update with a TypeError.",
    { total: `this will cause a TypeError!` },
    TypeError
  );

  testBadUpdate(
    "Total is recursive, so converting to IDL must abort the update with a TypeError.",
    { total: recursiveData },
    TypeError
  );

  testBadUpdate(
    "Updating with a negative total results in a TypeError.",
    invalidDetailsNegativeTotal,
    TypeError
  );

  testBadUpdate(
    "Updating with a displayItem with an invalid currency results in RangeError.",
    { ...validDetails, displayItems: invalidPaymentItems },
    RangeError
  );

  testBadUpdate(
    "Must throw a RangeError when a modifier's total item has an invalid currency.",
    { ...validDetails, modifiers: [modifierWithInvalidTotal, validModifier] },
    RangeError
  );

  testBadUpdate(
    "Must throw a RangeError when a modifier display item has an invalid currency.",
    {
      ...validDetails,
      modifiers: [modifierWithInvalidDisplayItems, validModifier],
    },
    RangeError
  );
  testBadUpdate(
    "Must throw as Modifier has a recursive dictionary.",
    { ...validDetails, modifiers: [modifierWithRecursiveData, validModifier] },
    TypeError
  );
</script>
