<!doctype html>
<meta charset="utf8">
<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-retry">
<title>
  PaymentResponse.prototype.retry() method
</title>
<script src="/js-test-resources/ui-helper.js"></script>
<script src="/resources/payment-request.js"></script>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/helpers.js"></script>
<script>
  setUpAndSmokeTest({ explicit_timeout: true });
</script>
<body>
<script>
test(() => {
  assert_true(
    "retry" in PaymentResponse.prototype,
    "retry must be in prototype"
  );
  assert_true(
    PaymentResponse.prototype.retry instanceof Function,
    "retry must be a function"
  );
}, "PaymentResponse.prototype must have a retry() function (smoke test).");

promise_test(async t => {
  const { response } = await getPaymentRequestResponse();
  // sets response.[[complete]] to true.
  await response.complete("success");
  return promise_rejects_dom(
    t,
    "InvalidStateError",
    response.retry({}),
    "response.[[complete]] is true, so rejects with InvalidStateError."
  );
}, "A completed payment request cannot be retried.");

promise_test(async t => {
  const { response } = await getPaymentRequestResponse();
  const retryPromise = response.retry({});
  await promise_rejects_dom(
    t,
    "InvalidStateError",
    response.retry({}),
    "Calling retry() again rejects with an InvalidStateError"
  );
  internals.mockPaymentCoordinator.acceptPayment();
  await retryPromise;
  await response.complete("success");
}, "Calling retry() more than once yield a rejected promise, but the retryPromise resolves independently.");

promise_test(async t => {
  const { response } = await getPaymentRequestResponse();
  const retryPromise = response.retry({});
  await promise_rejects_dom(
    t,
    "InvalidStateError",
    response.complete("success"),
    "Calling complete() while retrying rejects with an InvalidStateError"
  );
  internals.mockPaymentCoordinator.acceptPayment();
  await retryPromise;
  await response.complete("success");
}, "Calling complete() while a retry() is in progress results in an InvalidStateError.");

promise_test(async t => {
  const { response, request } = await getPaymentRequestResponse();
  const retryPromise = response.retry({});
  await promise_rejects_dom(
    t,
    "InvalidStateError",
    request.abort(),
    "Calling request.abort() while retrying rejects with an InvalidStateError"
  );
  internals.mockPaymentCoordinator.acceptPayment();
  await retryPromise;
  await response.complete("success");
}, "Trying to abort() via PaymentRequest is not possible.");

promise_test(async t => {
  const { response } = await getPaymentRequestResponse();
  var promise = response.retry({});
  internals.mockPaymentCoordinator.acceptPayment();
  assert_equals(
    await promise,
    undefined,
    "Expected undefined as the resolve value"
  );
  promise = response.retry({});
  internals.mockPaymentCoordinator.acceptPayment();
  assert_equals(
    await promise,
    undefined,
    "Expected undefined as the resolve value"
  );
  await response.complete("success");
  await promise_rejects_dom(
    t,
    "InvalidStateError",
    response.retry({}),
    "Calling retry() after complete() rejects with a InvalidStateError"
  );
}, "It's possible to retry() multiple times and eventually complete(). After complete(), however, retry() rejects with an InvalidStateError.");

promise_test(async t => {
  const { response } = await getPaymentRequestResponse();
  var promise = response.retry({});
  internals.mockPaymentCoordinator.cancelPayment();
  await promise_rejects_dom(
    t,
    "AbortError",
    promise,
    "The user aborting a retry rejects with a AbortError"
  );
  await promise_rejects_dom(
    t,
    "InvalidStateError",
    response.retry({}),
    "After the user aborts, response [[complete]] is true so retry() must reject with InvalidStateError"
  );
  await promise_rejects_dom(
    t,
    "InvalidStateError",
    response.complete("success"),
    "After the user aborts, response [[complete]] is true, so complete() rejects with a InvalidStateError"
  );
}, "The user aborting retrying a payment causes the retryPromise to reject with AbortError. Aborting a payment is causes it complete.");

promise_test(async t => {
  const { response, request } = await getPaymentRequestResponse({ requestShipping: true });
  const retryPromise = response.retry({
    shippingAddress: { city: "Invalid city", addressLine: "Invalid address line" },
  });
  const errors = internals.mockPaymentCoordinator.errors;
  assert_equals(errors.length, 2, "Must have two errors");
  assert_equals(errors[0].code, "shippingContactInvalid", "Must be a 'shippingContactInvalid' error");
  assert_equals(errors[0].message, "Invalid address line", "Error message must match");
  assert_equals(errors[0].contactField, "addressLines", "Contact field must match");
  assert_equals(errors[1].code, "shippingContactInvalid", "Must be a 'shippingContactInvalid' error");
  assert_equals(errors[1].message, "Invalid city", "Error message must match");
  assert_equals(errors[1].contactField, "locality", "Contact field must match");
  internals.mockPaymentCoordinator.acceptPayment();
  await retryPromise;
  await response.complete("success");
}, "When retrying, the user is shown error fields to fix.");

promise_test(async t => {
  const { response, request } = await getPaymentRequestResponse({ requestShipping: true });
  // causes "abort the update" to run
  const shippingChangedPromise = new Promise(resolve => {
    request.onshippingoptionchange = () => {
      event.updateWith({ total: { amount: { currency: "USD", value: "INVALID" } }});
      resolve();
    };
  });
  const retryPromise = response.retry({});
  internals.mockPaymentCoordinator.changeShippingOption("option");
  await shippingChangedPromise;
  await promise_rejects_js(
    t,
    TypeError,
    retryPromise,
    "retry() aborts with a TypeError, because totals' value is invalid"
  );
  await promise_rejects_dom(
    t,
    "InvalidStateError",
    response.complete("success"),
    "After the user aborts, response [[complete]] is true, so complete() rejects with a InvalidStateError"
  );
}, 'When "abort the update" occurs because of an update error, the `retryPromise` is rejected and response.[[complete]] becomes true.');

promise_test(async t => {
  const { response } = await getPaymentRequestResponse();
  const retryPromise = response.retry({});
  const promises = new Set([
    retryPromise,
    response.retry({}).catch(() => {}),
    response.retry({}).catch(() => {}),
  ]);
  assert_equals(promises.size, 3, "Must have three unique objects");
  internals.mockPaymentCoordinator.acceptPayment();
  await retryPromise;
  await response.complete();
}, "Calling retry() multiple times is always a new object.");

promise_test(async t => {
  const { response, request } = await getPaymentRequestResponse({ requestShipping: true });
  const retryPromise = response.retry();
  const errors = internals.mockPaymentCoordinator.errors;
  assert_equals(errors.length, 1, "Must have one error");
  assert_equals(errors[0].code, "unknown", "Must be an 'unknown' error");
  assert_equals(errors[0].message, "", "Error message must match");
  assert_equals(errors[0].contactField, undefined, "Contact field must match");
  internals.mockPaymentCoordinator.acceptPayment();
  await retryPromise;
  await response.complete("success");
}, "When retrying without errors, the user is shown an `unknown` error.");

promise_test(async t => {
  const { response, request } = await getPaymentRequestResponse({ requestShipping: true });
  var originalDetails = response.details;
  const retryPromise = response.retry();
  internals.mockPaymentCoordinator.acceptPayment();
  await retryPromise;
  assert_not_equals(response.details, originalDetails, "response.details should be a new object after the user accepts a retry");
  await response.complete("success");
}, "response.details should be updated after the user accepts a retry.");

</script>
