function testDefaultCtor() {
  var res = new Response();
  is(res.type, "default", "Default Response type is default");
  ok(
    res.headers instanceof Headers,
    "Response should have non-null Headers object"
  );
  is(res.url, "", "URL should be empty string");
  is(res.status, 200, "Default status is 200");
  is(res.statusText, "", "Default statusText is an empty string");
}

function testClone() {
  var orig = new Response("This is a body", {
    status: 404,
    statusText: "Not Found",
    headers: { "Content-Length": 5 },
  });
  var clone = orig.clone();
  is(clone.status, 404, "Response status is 404");
  is(clone.statusText, "Not Found", "Response statusText is POST");
  ok(
    clone.headers instanceof Headers,
    "Response should have non-null Headers object"
  );

  is(
    clone.headers.get("content-length"),
    "5",
    "Response content-length should be 5."
  );
  orig.headers.set("content-length", 6);
  is(
    clone.headers.get("content-length"),
    "5",
    "Response content-length should be 5."
  );

  ok(!orig.bodyUsed, "Original body is not consumed.");
  ok(!clone.bodyUsed, "Clone body is not consumed.");

  var origBody = null;
  var clone2 = null;
  return orig
    .text()
    .then(function (body) {
      origBody = body;
      is(origBody, "This is a body", "Original body string matches");
      ok(orig.bodyUsed, "Original body is consumed.");
      ok(!clone.bodyUsed, "Clone body is not consumed.");

      try {
        orig.clone();
        ok(false, "Cannot clone Response whose body is already consumed");
      } catch (e) {
        is(
          e.name,
          "TypeError",
          "clone() of consumed body should throw TypeError"
        );
      }

      clone2 = clone.clone();
      return clone.text();
    })
    .then(function (body) {
      is(body, origBody, "Clone body matches original body.");
      ok(clone.bodyUsed, "Clone body is consumed.");

      try {
        clone.clone();
        ok(false, "Cannot clone Response whose body is already consumed");
      } catch (e) {
        is(
          e.name,
          "TypeError",
          "clone() of consumed body should throw TypeError"
        );
      }

      return clone2.text();
    })
    .then(function (body) {
      is(body, origBody, "Clone body matches original body.");
      ok(clone2.bodyUsed, "Clone body is consumed.");

      try {
        clone2.clone();
        ok(false, "Cannot clone Response whose body is already consumed");
      } catch (e) {
        is(
          e.name,
          "TypeError",
          "clone() of consumed body should throw TypeError"
        );
      }
    });
}

function testCloneUnfiltered() {
  var url =
    "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200";
  return fetch(url, { mode: "no-cors" }).then(function (response) {
    // By default the chrome-only function should not be available.
    is(response.type, "opaque", "response should be opaque");
    is(
      response.cloneUnfiltered,
      undefined,
      "response.cloneUnfiltered should be undefined"
    );

    // When the test is run in a worker context we can't actually try to use
    // the chrome-only function.  SpecialPowers is not defined.
    if (typeof SpecialPowers !== "object") {
      return;
    }

    // With a chrome code, however, should be able to get an unfiltered response.
    var chromeResponse = SpecialPowers.wrap(response);
    is(
      typeof chromeResponse.cloneUnfiltered,
      "function",
      "chromeResponse.cloneFiltered should be a function"
    );
    var unfiltered = chromeResponse.cloneUnfiltered();
    is(unfiltered.type, "default", "unfiltered response should be default");
    is(unfiltered.status, 200, "unfiltered response should have 200 status");
  });
}

function testError() {
  var res = Response.error();
  is(res.status, 0, "Error response status should be 0");
  try {
    res.headers.set("someheader", "not allowed");
    ok(false, "Error response should have immutable headers");
  } catch (e) {
    ok(true, "Error response should have immutable headers");
  }
}

function testRedirect() {
  var res = Response.redirect("./redirect.response");
  is(res.status, 302, "Default redirect has status code 302");
  is(res.statusText, "", "Default redirect has status text empty");
  var h = res.headers.get("location");
  ok(
    h === new URL("./redirect.response", self.location.href).href,
    "Location header should be correct absolute URL"
  );
  try {
    res.headers.set("someheader", "not allowed");
    ok(false, "Redirects should have immutable headers");
  } catch (e) {
    ok(true, "Redirects should have immutable headers");
  }

  var successStatus = [301, 302, 303, 307, 308];
  for (var i = 0; i < successStatus.length; ++i) {
    var res = Response.redirect("./redirect.response", successStatus[i]);
    is(res.status, successStatus[i], "Status code should match");
  }

  var failStatus = [300, 0, 304, 305, 306, 309, 500];
  for (var i = 0; i < failStatus.length; ++i) {
    try {
      var res = Response.redirect(".", failStatus[i]);
      ok(false, "Invalid status code should fail " + failStatus[i]);
    } catch (e) {
      is(
        e.name,
        "RangeError",
        "Invalid status code should fail " + failStatus[i]
      );
    }
  }
}

function testOk() {
  var r1 = new Response("", { status: 200 });
  ok(r1.ok, "Response with status 200 should have ok true");

  var r2 = new Response(undefined, { status: 204 });
  ok(r2.ok, "Response with status 204 should have ok true");

  var r3 = new Response("", { status: 299 });
  ok(r3.ok, "Response with status 299 should have ok true");

  var r4 = new Response("", { status: 302 });
  ok(!r4.ok, "Response with status 302 should have ok false");
}

function testBodyUsed() {
  var res = new Response("Sample body");
  ok(!res.bodyUsed, "bodyUsed is initially false.");
  return res
    .text()
    .then(v => {
      is(v, "Sample body", "Body should match");
      ok(res.bodyUsed, "After reading body, bodyUsed should be true.");
    })
    .then(() => {
      return res.blob().then(
        v => {
          ok(false, "Attempting to read body again should fail.");
        },
        e => {
          ok(true, "Attempting to read body again should fail.");
        }
      );
    });
}

function testBodyCreation() {
  var text = "κόσμε";
  var res1 = new Response(text);
  var p1 = res1.text().then(function (v) {
    ok(typeof v === "string", "Should resolve to string");
    is(text, v, "Extracted string should match");
  });

  var res2 = new Response(new Uint8Array([72, 101, 108, 108, 111]));
  var p2 = res2.text().then(function (v) {
    is("Hello", v, "Extracted string should match");
  });

  var res2b = new Response(new Uint8Array([72, 101, 108, 108, 111]).buffer);
  var p2b = res2b.text().then(function (v) {
    is("Hello", v, "Extracted string should match");
  });

  var resblob = new Response(new Blob([text]));
  var pblob = resblob.text().then(function (v) {
    is(v, text, "Extracted string should match");
  });

  var params = new URLSearchParams();
  params.append("item", "Geckos");
  params.append("feature", "stickyfeet");
  params.append("quantity", "700");
  var res3 = new Response(params);
  var p3 = res3.text().then(function (v) {
    var extracted = new URLSearchParams(v);
    is(extracted.get("item"), "Geckos", "Param should match");
    is(extracted.get("feature"), "stickyfeet", "Param should match");
    is(extracted.get("quantity"), "700", "Param should match");
  });

  return Promise.all([p1, p2, p2b, pblob, p3]);
}

function testBodyExtraction() {
  var text = "κόσμε";
  var newRes = function () {
    return new Response(text);
  };
  return newRes()
    .text()
    .then(function (v) {
      ok(typeof v === "string", "Should resolve to string");
      is(text, v, "Extracted string should match");
    })
    .then(function () {
      return newRes()
        .blob()
        .then(function (v) {
          ok(v instanceof Blob, "Should resolve to Blob");
          return readAsText(v).then(function (result) {
            is(result, text, "Decoded Blob should match original");
          });
        });
    })
    .then(function () {
      return newRes()
        .json()
        .then(
          function (v) {
            ok(false, "Invalid json should reject");
          },
          function (e) {
            ok(true, "Invalid json should reject");
          }
        );
    })
    .then(function () {
      return newRes()
        .arrayBuffer()
        .then(function (v) {
          ok(v instanceof ArrayBuffer, "Should resolve to ArrayBuffer");
          var dec = new TextDecoder();
          is(
            dec.decode(new Uint8Array(v)),
            text,
            "UTF-8 decoded ArrayBuffer should match original"
          );
        });
    });
}

function testNullBodyStatus() {
  [204, 205, 304].forEach(function (status) {
    try {
      var res = new Response(new Blob(), { status });
      ok(
        false,
        "Response body provided but status code does not permit a body"
      );
    } catch (e) {
      ok(true, "Response body provided but status code does not permit a body");
    }
  });

  [204, 205, 304].forEach(function (status) {
    try {
      var res = new Response(undefined, { status });
      ok(true, "Response body provided but status code does not permit a body");
    } catch (e) {
      ok(
        false,
        "Response body provided but status code does not permit a body"
      );
    }
  });
}

function runTest() {
  testDefaultCtor();
  testError();
  testRedirect();
  testOk();
  testNullBodyStatus();

  return (
    Promise.resolve()
      .then(testBodyCreation)
      .then(testBodyUsed)
      .then(testBodyExtraction)
      .then(testClone)
      .then(testCloneUnfiltered)
      // Put more promise based tests here.
      .catch(function (e) {
        dump("### ### " + e + "\n");
        ok(false, "got unexpected error!");
      })
  );
}
