<!DOCTYPE html>
<meta charset="utf-8">
<title>Test currency code usage in 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>
const defaultMethods = [
  Object.freeze({
    supportedMethods: "https://{{domains[nonexistent]}}",
  }),
];
const defaultAmount = Object.freeze({
  currency: "USD",
  value: "1.00",
});
const defaultTotal = Object.freeze({
  label: "Total",
  amount: defaultAmount,
});

const defaultDetails = Object.freeze({
  total: Object.freeze({
    label: "",
    amount: defaultAmount,
  }),
});

// The following are the same set of valid/invalid codes that are used in
// the ECMAScript Internationalization API Specification (ECMA-402) test suite.
const wellFormedCurrencyCodes = [
  "BOB",
  "EUR",
  "usd", // currency codes are case-insensitive
  "XdR",
  "xTs",
];

const invalidCurrencyCodes = [
  "",
  "€",
  "$",
  "SFr.",
  "DM",
  "KR₩",
  "702",
  "ßP",
  "ınr",
];

const RANGE_ERROR = RangeError;

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

const invalidTotal = {
  total: {
    label: "Invalid total",
    amount: invalidAmount,
  },
};

// Ensure we don't get false positives
function smokeTest() {
  new PaymentRequest(defaultMethods, invalidTotal);
}

// Process the total:
test(() => {
  assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
  for (const validCurrency of wellFormedCurrencyCodes) {
    const amount = {
      currency: validCurrency,
      value: "1.00",
    };
    const total = {
      label: "Total",
      amount,
    };
    const details = {
      total,
    };
    try {
      new PaymentRequest(defaultMethods, details);
    } catch (err) {
      assert_unreached(
        `Unexpected exception for details.total.amount "${validCurrency}": ${err.message}`
      );
    }
  }
}, "Check and canonicalize valid details.total.amount");

test(() => {
  assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
  for (const invalidCurrency of invalidCurrencyCodes) {
    const amount = {
      currency: invalidCurrency,
      value: "1.00",
    };
    const total = {
      label: "Total",
      amount,
    };
    const details = {
      total,
    };
    assert_throws_js(
      RANGE_ERROR,
      () => {
        new PaymentRequest(defaultMethods, details);
      },
      `Expected RangeError for details.total.amount given ("${invalidCurrency}").`
    );
  }
}, "Check and canonicalize invalid details.total.amount and rethrow any exceptions.");

// If the displayItems member of details is present, then for each item in details.displayItems:
test(() => {
  assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
  const displayItems = [];
  for (const validCurrency of wellFormedCurrencyCodes) {
    const amount = {
      currency: validCurrency,
      value: "123",
    };
    const displayItem = {
      amount,
      label: "valid currency",
    };
    const details = {
      total: defaultTotal,
      displayItems: [displayItem],
    };
    try {
      new PaymentRequest(defaultMethods, details);
    } catch (err) {
      assert_unreached(
        `Unexpected error with displayItem that had a valid currency "${validCurrency}": ${err.message}`
      );
    }
    displayItems.push(displayItem);
  }
  // Let's make sure it doesn't throw given a list of valid displayItems
  try {
    const details = Object.assign({}, defaultDetails, { displayItems });
    new PaymentRequest(defaultMethods, details);
  } catch (err) {
    assert_unreached(
      `Unexpected error with multiple valid displayItems: ${err.message}`
    );
  }
}, "Check and canonicalize valid details.displayItems amount");

test(() => {
  assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
  for (const invalidCurrency of invalidCurrencyCodes) {
    const amount = {
      currency: invalidCurrency,
      value: "123",
    };
    const displayItem = {
      amount,
      label: "invalid currency",
    };
    const details = {
      total: defaultTotal,
      displayItems: [displayItem],
    };
    assert_throws_js(
      RANGE_ERROR,
      () => {
        new PaymentRequest(defaultMethods, details);
      },
      `Expected RangeError with invalid displayItem currency "${invalidCurrency}".`
    );
  }
}, "Check and canonicalize invalid details.displayItems amount and rethrow RangeError.");

// Process shipping options:
test(() => {
  assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
  const shippingOptions = [];
  for (const validCurrency of wellFormedCurrencyCodes) {
    const shippingOption = {
      id: `test` + Math.random(),
      label: "shipping option",
      amount: { currency: validCurrency, value: "5.00" },
      selected: !shippingOptions.length,
    };
    const details = {
      total: defaultTotal,
      shippingOptions: [shippingOption],
    };
    try {
      new PaymentRequest(defaultMethods, details, { requestShipping: true });
    } catch (err) {
      assert_unreached(
        `Unexpected exception with valid shippingOption currency code "${validCurrency}": ${err.message}.`
      );
    }
    shippingOptions.push(shippingOption);
  }
  try {
    const details = Object.assign({}, defaultDetails, { shippingOptions });
    new PaymentRequest(defaultMethods, details, { requestShipping: true });
  } catch (err) {
    assert_unreached(
      `Unexpected error with multiple valid shppingOptions: ${err.message}.`
    );
  }
}, "Check and canonicalize valid details.shippingOptions amount.");

test(() => {
  assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
  for (const invalidCurrency of invalidCurrencyCodes) {
    const shippingOption = {
      id: "test",
      label: "shipping option",
      amount: { currency: invalidCurrency, value: "5.00" },
      selected: true,
    };
    const details = {
      total: defaultTotal,
      shippingOptions: [shippingOption],
    };
    assert_throws_js(
      RANGE_ERROR,
      () => {
        new PaymentRequest(defaultMethods, details, { requestShipping: true });
      },
      `Expected RangeError with invalid shippingOption currency code "${invalidCurrency}".`
    );
  }
}, "Check and canonicalize invalid details.shippingOptions amount and rethrow RangeError.");

// Process payment details modifiers:
test(() => {
  assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
  for (const validCurrency of wellFormedCurrencyCodes) {
    const modifier = {
      supportedMethods: "https://{{domains[nonexistent]}}",
      total: {
        label: "Total due",
        amount: { currency: validCurrency, value: "68.00" },
      },
    };
    const details = {
      total: defaultTotal,
      modifiers: [modifier],
    };
    try {
      new PaymentRequest(defaultMethods, details);
    } catch (err) {
      assert_unreached(
        `Unexpected error with valid modifier currency code "${validCurrency}": ${err.message}`
      );
    }
  }
}, "Check and canonicalize valid modifiers[n].total amount.");

test(() => {
  assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
  for (const invalidCurrency of invalidCurrencyCodes) {
    const modifier = {
      supportedMethods: "https://{{domains[nonexistent]}}",
      total: {
        label: "Total due",
        amount: { currency: invalidCurrency, value: "68.00" },
      },
    };
    const details = {
      total: defaultTotal,
      modifiers: [modifier],
    };
    assert_throws_js(
      RANGE_ERROR,
      () => {
        new PaymentRequest(defaultMethods, details);
      },
      `Expected RangeError with invalid modifier currency code "${invalidCurrency}".`
    );
  }
}, "Check and canonicalize invalid modifiers[n].total amount and rethrow RangeError.");

// Process payment details modifiers:
test(() => {
  assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
  for (const validCurrency of wellFormedCurrencyCodes) {
    const additionalItem = {
      label: "additionalItem",
      amount: { currency: validCurrency, value: "3.00" },
    };
    const modifier = {
      supportedMethods: "https://{{domains[nonexistent]}}",
      total: defaultTotal,
      additionalDisplayItems: [additionalItem],
    };
    const details = {
      total: defaultTotal,
      modifiers: [modifier],
    };
    try {
      new PaymentRequest(defaultMethods, details);
    } catch (err) {
      assert_unreached(
        `Unexpected error with valid additionalDisplayItems[n] currency code "${validCurrency}": ${err.message}`
      );
    }
  }
}, "Check and canonicalize valid modifiers[n].additionaDisplayItem amount.");

test(() => {
  assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
  for (const invalidCurrency of invalidCurrencyCodes) {
    const additionalItem = {
      label: "additionalItem",
      amount: { currency: invalidCurrency, value: "3.00" },
    };
    const modifier = {
      supportedMethods: "https://{{domains[nonexistent]}}",
      total: defaultTotal,
      additionalDisplayItems: [additionalItem],
    };
    const details = {
      total: defaultTotal,
      modifiers: [modifier],
    };
    assert_throws_js(
      RANGE_ERROR,
      () => {
        new PaymentRequest(defaultMethods, details);
      },
      `Expected RangeError with invalid additionalDisplayItems[n] currency code "${invalidCurrency}".`
    );
  }
}, "Check and canonicalize invalid modifiers[n].additionaDisplayItem amount and rethrow RangeError.");
</script>
