import { DefaultProxy } from "../../Source/Cesium.js";
import { defaultValue } from "../../Source/Cesium.js";
import { queryToObject } from "../../Source/Cesium.js";
import { Request } from "../../Source/Cesium.js";
import { RequestErrorEvent } from "../../Source/Cesium.js";
import { RequestScheduler } from "../../Source/Cesium.js";
import { Resource } from "../../Source/Cesium.js";
import createCanvas from "../createCanvas.js";
import { Uri } from "../../Source/Cesium.js";
import { when } from "../../Source/Cesium.js";
import dataUriToBuffer from "../dataUriToBuffer.js";

describe("Core/Resource", function () {
  var dataUri =
    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2Nk+M/wHwAEBgIA5agATwAAAABJRU5ErkJggg==";
  var supportsImageBitmapOptions;

  beforeAll(function () {
    return Resource.supportsImageBitmapOptions().then(function (result) {
      supportsImageBitmapOptions = result;
    });
  });

  it("Constructor sets correct properties", function () {
    var proxy = new DefaultProxy("/proxy/");
    var request = new Request();
    function retryFunc() {}

    var resource = new Resource({
      url: "http://test.com/tileset",
      queryParameters: {
        key1: "value1",
        key2: "value2",
      },
      templateValues: {
        key3: "value3",
        key4: "value4",
      },
      headers: {
        Accept: "application/test-type",
      },
      proxy: proxy,
      retryCallback: retryFunc,
      retryAttempts: 4,
      request: request,
    });

    expect(resource.getUrlComponent(false, false)).toEqual(
      "http://test.com/tileset"
    );
    expect(resource.getUrlComponent(true, false)).toEqual(
      "http://test.com/tileset?key1=value1&key2=value2"
    );
    expect(resource.getUrlComponent(false, true)).toEqual(
      proxy.getURL("http://test.com/tileset")
    );
    expect(resource.getUrlComponent(true, true)).toEqual(
      proxy.getURL("http://test.com/tileset?key1=value1&key2=value2")
    );
    expect(resource.url).toEqual(
      proxy.getURL("http://test.com/tileset?key1=value1&key2=value2")
    );
    expect(String(resource)).toEqual(
      proxy.getURL("http://test.com/tileset?key1=value1&key2=value2")
    );
    expect(resource.queryParameters).toEqual({
      key1: "value1",
      key2: "value2",
    });
    expect(resource.templateValues).toEqual({
      key3: "value3",
      key4: "value4",
    });
    expect(resource.headers).toEqual({
      Accept: "application/test-type",
    });
    expect(resource.proxy).toBe(proxy);
    expect(resource.retryCallback).toBe(retryFunc);
    expect(resource.retryAttempts).toEqual(4);
    expect(resource._retryCount).toEqual(0);
    expect(resource.request).toBe(request);
  });

  it("Constructor sets correct properties", function () {
    var url = "http://invalid.domain.com/tileset";
    var resource = new Resource(url);
    expect(resource.url).toEqual(url);
    expect(String(resource)).toEqual(url);
    expect(resource.queryParameters).toEqual({});
    expect(resource.templateValues).toEqual({});
    expect(resource.headers).toEqual({});
    expect(resource.proxy).toBeUndefined();
    expect(resource.retryCallback).toBeUndefined();
    expect(resource.retryAttempts).toEqual(0);
    expect(resource.request).toBeDefined();
  });

  it("_makeRequest returns undefined if the request is throttled", function () {
    var oldMaximumRequests = RequestScheduler.maximumRequests;
    RequestScheduler.maximumRequests = 0;

    var resource = new Resource({
      url: "http://example.invalid/testuri",
      request: new Request({
        throttle: true,
      }),
    });
    var promise = resource._makeRequest({ method: "GET" });
    expect(promise).toBeUndefined();

    RequestScheduler.maximumRequests = oldMaximumRequests;
  });

  it("appendForwardSlash appends a /", function () {
    var resource = new Resource({
      url: "http://test.com/tileset",
    });
    expect(resource.url).toEqual("http://test.com/tileset");
    resource.appendForwardSlash();
    expect(resource.url).toEqual("http://test.com/tileset/");
  });

  it("Setting a url with a query string sets queryParameters correctly", function () {
    var resource = new Resource({
      url: "http://test.com/tileset?foo=bar&baz=foo",
    });
    expect(resource.getUrlComponent()).toEqual("http://test.com/tileset");
    expect(resource.getUrlComponent(true)).toEqual(
      "http://test.com/tileset?foo=bar&baz=foo"
    );
    expect(resource.queryParameters).toEqual({
      foo: "bar",
      baz: "foo",
    });
  });

  it("createIfNeeded returns undefined, if parameter is undefined", function () {
    expect(Resource.createIfNeeded()).toBeUndefined();
  });

  it("createIfNeeded returns Resource, if parameter is a Resource", function () {
    var resource = new Resource({
      url: "http://test.com/tileset",
    });
    expect(Resource.createIfNeeded(resource)).toEqual(resource);
  });

  it("createIfNeeded returns Resource, if parameter is a String", function () {
    var resource = Resource.createIfNeeded("http://test.com/tileset");
    expect(resource.url).toEqual("http://test.com/tileset");
  });

  it("multiple values for query parameters are allowed", function () {
    var resource = new Resource(
      "http://test.com/tileset/endpoint?a=1&a=2&b=3&a=4"
    );
    expect(resource.queryParameters.a).toEqual(["1", "2", "4"]);
    expect(resource.queryParameters.b).toEqual("3");

    expect(resource.url).toEqual(
      "http://test.com/tileset/endpoint?a=1&a=2&a=4&b=3"
    );
  });

  it("multiple values for query parameters works with getDerivedResource without preserverQueryParameters", function () {
    var resource = new Resource(
      "http://test.com/tileset/endpoint?a=1&a=2&b=3&a=4"
    );
    expect(resource.queryParameters.a).toEqual(["1", "2", "4"]);
    expect(resource.queryParameters.b).toEqual("3");

    expect(resource.url).toEqual(
      "http://test.com/tileset/endpoint?a=1&a=2&a=4&b=3"
    );

    var derived = resource.getDerivedResource({
      url: "other_endpoint?a=5&b=6&a=7",
    });

    expect(derived.queryParameters.a).toEqual(["5", "7"]);
    expect(derived.queryParameters.b).toEqual("6");

    expect(derived.url).toEqual(
      "http://test.com/tileset/other_endpoint?a=5&a=7&b=6"
    );
  });

  it("multiple values for query parameters works with getDerivedResource with preserveQueryParameters", function () {
    var resource = new Resource(
      "http://test.com/tileset/endpoint?a=1&a=2&b=3&a=4"
    );
    expect(resource.queryParameters.a).toEqual(["1", "2", "4"]);
    expect(resource.queryParameters.b).toEqual("3");

    expect(resource.url).toEqual(
      "http://test.com/tileset/endpoint?a=1&a=2&a=4&b=3"
    );

    var derived = resource.getDerivedResource({
      url: "other_endpoint?a=5&b=6&a=7",
      preserveQueryParameters: true,
    });

    expect(derived.queryParameters.a).toEqual(["5", "7", "1", "2", "4"]);
    expect(derived.queryParameters.b).toEqual(["6", "3"]);

    expect(derived.url).toEqual(
      "http://test.com/tileset/other_endpoint?a=5&a=7&a=1&a=2&a=4&b=6&b=3"
    );
  });

  it("replaces templateValues in the url", function () {
    var resource = new Resource({
      url: "http://test.com/tileset/{foo}/{bar}",
      templateValues: {
        foo: "test1",
        bar: "test2",
      },
    });

    expect(resource.url).toEqual("http://test.com/tileset/test1/test2");
  });

  it("replaces numeric templateValues", function () {
    var resource = new Resource({
      url: "http://test.com/tileset/{0}/{1}",
      templateValues: {
        0: "test1",
        1: "test2",
      },
    });

    expect(resource.url).toEqual("http://test.com/tileset/test1/test2");
  });

  it("leaves templateValues unchanged that are not provided", function () {
    var resource = new Resource({
      url: "http://test.com/tileset/{foo}/{bar}",
    });

    expect(resource.url).toEqual("http://test.com/tileset/{foo}/{bar}");
  });

  it("url encodes replacement templateValues in the url", function () {
    var resource = new Resource({
      url: "http://test.com/tileset/{foo}/{bar}",
      templateValues: {
        foo: "a/b",
        bar: "x$y#",
      },
    });

    expect(resource.url).toEqual("http://test.com/tileset/a%2Fb/x%24y%23");
  });

  it("getDerivedResource sets correct properties", function () {
    var proxy = new DefaultProxy("/proxy/");
    var request = new Request();
    function retryFunc() {}

    var parent = new Resource({
      url: "http://test.com/tileset?key=value",
      queryParameters: {
        foo: "bar",
      },
      templateValues: {
        key5: "value5",
        key6: "value6",
      },
    });
    parent.appendForwardSlash();

    var resource = parent.getDerivedResource({
      url: "tileset.json",
      queryParameters: {
        key1: "value1",
        key2: "value2",
      },
      templateValues: {
        key3: "value3",
        key4: "value4",
      },
      headers: {
        Accept: "application/test-type",
      },
      proxy: proxy,
      retryCallback: retryFunc,
      retryAttempts: 4,
      request: request,
    });

    expect(resource.getUrlComponent(false, false)).toEqual(
      "http://test.com/tileset/tileset.json"
    );
    expect(resource.getUrlComponent(true, false)).toEqual(
      "http://test.com/tileset/tileset.json?key1=value1&key2=value2&key=value&foo=bar"
    );
    expect(resource.getUrlComponent(false, true)).toEqual(
      proxy.getURL("http://test.com/tileset/tileset.json")
    );
    expect(resource.getUrlComponent(true, true)).toEqual(
      proxy.getURL(
        "http://test.com/tileset/tileset.json?key1=value1&key2=value2&key=value&foo=bar"
      )
    );
    expect(resource.url).toEqual(
      proxy.getURL(
        "http://test.com/tileset/tileset.json?key1=value1&key2=value2&key=value&foo=bar"
      )
    );
    expect(resource.queryParameters).toEqual({
      foo: "bar",
      key: "value",
      key1: "value1",
      key2: "value2",
    });
    expect(resource.templateValues).toEqual({
      key5: "value5",
      key6: "value6",
      key3: "value3",
      key4: "value4",
    });
    expect(resource.headers).toEqual({
      Accept: "application/test-type",
    });
    expect(resource.proxy).toBe(proxy);
    expect(resource.retryCallback).toBe(retryFunc);
    expect(resource.retryAttempts).toEqual(4);
    expect(resource._retryCount).toEqual(0);
    expect(resource.request).toBe(request);
  });

  it("getDerivedResource works with directory parent resource", function () {
    var parent = new Resource({
      url: "http://test.com/tileset/",
    });

    expect(parent.url).toEqual("http://test.com/tileset/");

    var resource = parent.getDerivedResource({
      url: "tileset.json",
    });

    expect(resource.url).toEqual("http://test.com/tileset/tileset.json");
  });

  it("getDerivedResource works with file parent resource", function () {
    var parent = new Resource({
      url: "http://test.com/tileset/tileset.json",
    });

    expect(parent.url).toEqual("http://test.com/tileset/tileset.json");

    var resource = parent.getDerivedResource({
      url: "0/0/0.b3dm",
    });

    expect(resource.url).toEqual("http://test.com/tileset/0/0/0.b3dm");
  });

  it("getDerivedResource works with only template values", function () {
    var parent = new Resource({
      url: "http://test.com/terrain/{z}/{x}/{y}.terrain",
    });

    expect(parent.url).toEqual("http://test.com/terrain/{z}/{x}/{y}.terrain");

    var resource = parent.getDerivedResource({
      templateValues: {
        x: 1,
        y: 2,
        z: 0,
      },
    });

    expect(resource.url).toEqual("http://test.com/terrain/0/1/2.terrain");
  });

  it("getDerivedResource works with only query parameters", function () {
    var parent = new Resource({
      url: "http://test.com/terrain",
    });

    expect(parent.url).toEqual("http://test.com/terrain");

    var resource = parent.getDerivedResource({
      queryParameters: {
        x: 1,
        y: 2,
        z: 0,
      },
    });

    expect(resource.url).toEqual("http://test.com/terrain?x=1&y=2&z=0");
  });

  it("setQueryParameters with useAsDefault set to true", function () {
    var resource = new Resource({
      url: "http://test.com/terrain",
      queryParameters: {
        x: 1,
        y: 2,
      },
    });

    expect(resource.queryParameters).toEqual({
      x: 1,
      y: 2,
    });

    resource.setQueryParameters(
      {
        x: 3,
        y: 4,
        z: 0,
      },
      true
    );

    expect(resource.queryParameters).toEqual({
      x: 1,
      y: 2,
      z: 0,
    });
  });

  it("setQueryParameters with useAsDefault set to false", function () {
    var resource = new Resource({
      url: "http://test.com/terrain",
      queryParameters: {
        x: 1,
        y: 2,
      },
    });

    expect(resource.queryParameters).toEqual({
      x: 1,
      y: 2,
    });

    resource.setQueryParameters(
      {
        x: 3,
        y: 4,
        z: 0,
      },
      false
    );

    expect(resource.queryParameters).toEqual({
      x: 3,
      y: 4,
      z: 0,
    });
  });

  it("appendQueryParameters works with non-arrays", function () {
    var resource = new Resource({
      url: "http://test.com/terrain",
      queryParameters: {
        x: 1,
        y: 2,
      },
    });

    expect(resource.queryParameters).toEqual({
      x: 1,
      y: 2,
    });

    resource.appendQueryParameters({
      x: 3,
      y: 4,
      z: 0,
    });

    expect(resource.queryParameters).toEqual({
      x: [3, 1],
      y: [4, 2],
      z: 0,
    });
  });

  it("appendQueryParameters works with arrays/non-arrays", function () {
    var resource = new Resource({
      url: "http://test.com/terrain",
      queryParameters: {
        x: [1, 2],
        y: 2,
        z: [-1, -2],
      },
    });

    expect(resource.queryParameters).toEqual({
      x: [1, 2],
      y: 2,
      z: [-1, -2],
    });

    resource.appendQueryParameters({
      x: 3,
      y: [4, 5],
      z: [-3, -4],
    });

    expect(resource.queryParameters).toEqual({
      x: [3, 1, 2],
      y: [4, 5, 2],
      z: [-3, -4, -1, -2],
    });
  });

  it("setTemplateValues with useAsDefault set to true", function () {
    var resource = new Resource({
      url: "http://test.com/terrain/{z}/{x}/{y}.terrain",
      templateValues: {
        x: 1,
        y: 2,
        map: "my map",
      },
    });

    expect(resource.templateValues).toEqual({
      x: 1,
      y: 2,
      map: "my map",
    });

    resource.setTemplateValues(
      {
        x: 3,
        y: 4,
        z: 0,
        style: "my style",
      },
      true
    );

    expect(resource.templateValues).toEqual({
      x: 1,
      y: 2,
      map: "my map",
      z: 0,
      style: "my style",
    });
  });

  it("setTemplateValues with useAsDefault set to false", function () {
    var resource = new Resource({
      url: "http://test.com/terrain/{z}/{x}/{y}.terrain",
      templateValues: {
        x: 1,
        y: 2,
        map: "my map",
      },
    });

    expect(resource.templateValues).toEqual({
      x: 1,
      y: 2,
      map: "my map",
    });

    resource.setTemplateValues(
      {
        x: 3,
        y: 4,
        z: 0,
        style: "my style",
      },
      false
    );

    expect(resource.templateValues).toEqual({
      x: 3,
      y: 4,
      map: "my map",
      z: 0,
      style: "my style",
    });
  });

  it("retryOnFail doesn't exceed retryAttempts", function () {
    var cb = jasmine.createSpy("retry").and.returnValue(true);
    var resource = new Resource({
      url: "http://test.com/terrain",
      retryCallback: cb,
      retryAttempts: 3,
    });

    var promises = [];
    for (var i = 0; i < 6; ++i) {
      promises.push(resource.retryOnError());
    }

    when.all(promises).then(function (result) {
      expect(result).toEqual([true, true, true, false, false, false]);
      expect(cb.calls.count()).toEqual(3);
      expect(resource._retryCount).toEqual(3);
    });
  });

  it("retryOnFail returns value from callback", function () {
    var result = true;
    var cb = jasmine.createSpy("retry").and.callFake(function () {
      result = !result;
      return result;
    });

    var resource = new Resource({
      url: "http://test.com/terrain",
      retryCallback: cb,
      retryAttempts: 4,
    });

    var promises = [];
    for (var i = 0; i < 6; ++i) {
      promises.push(resource.retryOnError());
    }

    when.all(promises).then(function (result) {
      expect(result).toEqual([false, true, false, true, false, false]);
      expect(cb.calls.count()).toEqual(4);
      expect(resource._retryCount).toEqual(4);
    });
  });

  it("isDataUri returns correct values", function () {
    var dataResource = new Resource({
      url: "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3",
    });

    expect(dataResource.isDataUri).toBe(true);

    var resource = new Resource({
      url: "http://invalid.uri/tileset",
    });

    expect(resource.isDataUri).toBe(false);
  });

  it("isBlobUri returns correct values", function () {
    var dataResource = new Resource({
      url: "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
    });

    expect(dataResource.isBlobUri).toBe(true);

    var resource = new Resource({
      url: "http://invalid.uri/tileset",
    });

    expect(resource.isBlobUri).toBe(false);
  });

  it("post calls with correct method", function () {
    var expectedUrl = "http://test.com/endpoint";
    var expectedResponseType = "json";
    var expectedData = {
      stuff: "myStuff",
    };
    var expectedHeaders = {
      "X-My-Header": "My-Value",
    };
    var expectedResult = {
      status: "success",
    };
    var expectedMimeType = "application/test-data";
    var resource = new Resource({
      url: expectedUrl,
      headers: expectedHeaders,
    });

    spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
      url,
      responseType,
      method,
      data,
      headers,
      deferred,
      overrideMimeType
    ) {
      expect(url).toEqual(expectedUrl);
      expect(responseType).toEqual(expectedResponseType);
      expect(method).toEqual("POST");
      expect(data).toEqual(expectedData);
      expect(headers["X-My-Header"]).toEqual("My-Value");
      expect(headers["X-My-Other-Header"]).toEqual("My-Other-Value");
      expect(overrideMimeType).toBe(expectedMimeType);
      deferred.resolve(expectedResult);
    });

    return resource
      .post(expectedData, {
        responseType: expectedResponseType,
        headers: {
          "X-My-Other-Header": "My-Other-Value",
        },
        overrideMimeType: expectedMimeType,
      })
      .then(function (result) {
        expect(result).toEqual(expectedResult);
      });
  });

  it("static post calls with correct method", function () {
    var expectedUrl = "http://test.com/endpoint";
    var expectedResponseType = "json";
    var expectedData = {
      stuff: "myStuff",
    };
    var expectedHeaders = {
      "X-My-Header": "My-Value",
    };
    var expectedResult = {
      status: "success",
    };
    var expectedMimeType = "application/test-data";

    spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
      url,
      responseType,
      method,
      data,
      headers,
      deferred,
      overrideMimeType
    ) {
      expect(url).toEqual(expectedUrl);
      expect(responseType).toEqual(expectedResponseType);
      expect(method).toEqual("POST");
      expect(data).toEqual(expectedData);
      expect(headers).toEqual(expectedHeaders);
      expect(overrideMimeType).toBe(expectedMimeType);
      deferred.resolve(expectedResult);
    });

    return Resource.post({
      url: expectedUrl,
      data: expectedData,
      responseType: expectedResponseType,
      headers: expectedHeaders,
      overrideMimeType: expectedMimeType,
    }).then(function (result) {
      expect(result).toEqual(expectedResult);
    });
  });

  it("put calls with correct method", function () {
    var expectedUrl = "http://test.com/endpoint";
    var expectedResponseType = "json";
    var expectedData = {
      stuff: "myStuff",
    };
    var expectedHeaders = {
      "X-My-Header": "My-Value",
    };
    var expectedResult = {
      status: "success",
    };
    var expectedMimeType = "application/test-data";
    var resource = new Resource({
      url: expectedUrl,
      headers: expectedHeaders,
    });

    spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
      url,
      responseType,
      method,
      data,
      headers,
      deferred,
      overrideMimeType
    ) {
      expect(url).toEqual(expectedUrl);
      expect(responseType).toEqual(expectedResponseType);
      expect(method).toEqual("PUT");
      expect(data).toEqual(expectedData);
      expect(headers["X-My-Header"]).toEqual("My-Value");
      expect(headers["X-My-Other-Header"]).toEqual("My-Other-Value");
      expect(overrideMimeType).toBe(expectedMimeType);
      deferred.resolve(expectedResult);
    });

    return resource
      .put(expectedData, {
        responseType: expectedResponseType,
        headers: {
          "X-My-Other-Header": "My-Other-Value",
        },
        overrideMimeType: expectedMimeType,
      })
      .then(function (result) {
        expect(result).toEqual(expectedResult);
      });
  });

  it("static put calls with correct method", function () {
    var expectedUrl = "http://test.com/endpoint";
    var expectedResponseType = "json";
    var expectedData = {
      stuff: "myStuff",
    };
    var expectedHeaders = {
      "X-My-Header": "My-Value",
    };
    var expectedResult = {
      status: "success",
    };
    var expectedMimeType = "application/test-data";

    spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
      url,
      responseType,
      method,
      data,
      headers,
      deferred,
      overrideMimeType
    ) {
      expect(url).toEqual(expectedUrl);
      expect(responseType).toEqual(expectedResponseType);
      expect(method).toEqual("PUT");
      expect(data).toEqual(expectedData);
      expect(headers).toEqual(expectedHeaders);
      expect(overrideMimeType).toBe(expectedMimeType);
      deferred.resolve(expectedResult);
    });

    return Resource.put({
      url: expectedUrl,
      data: expectedData,
      responseType: expectedResponseType,
      headers: expectedHeaders,
      overrideMimeType: expectedMimeType,
    }).then(function (result) {
      expect(result).toEqual(expectedResult);
    });
  });

  it("patch calls with correct method", function () {
    var expectedUrl = "http://test.com/endpoint";
    var expectedResponseType = "json";
    var expectedData = {
      stuff: "myStuff",
    };
    var expectedHeaders = {
      "X-My-Header": "My-Value",
    };
    var expectedResult = {
      status: "success",
    };
    var expectedMimeType = "application/test-data";
    var resource = new Resource({
      url: expectedUrl,
      headers: expectedHeaders,
    });

    spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
      url,
      responseType,
      method,
      data,
      headers,
      deferred,
      overrideMimeType
    ) {
      expect(url).toEqual(expectedUrl);
      expect(responseType).toEqual(expectedResponseType);
      expect(method).toEqual("PATCH");
      expect(data).toEqual(expectedData);
      expect(headers["X-My-Header"]).toEqual("My-Value");
      expect(headers["X-My-Other-Header"]).toEqual("My-Other-Value");
      expect(overrideMimeType).toBe(expectedMimeType);
      deferred.resolve(expectedResult);
    });

    return resource
      .patch(expectedData, {
        responseType: expectedResponseType,
        headers: {
          "X-My-Other-Header": "My-Other-Value",
        },
        overrideMimeType: expectedMimeType,
      })
      .then(function (result) {
        expect(result).toEqual(expectedResult);
      });
  });

  it("static patch calls with correct method", function () {
    var expectedUrl = "http://test.com/endpoint";
    var expectedResponseType = "json";
    var expectedData = {
      stuff: "myStuff",
    };
    var expectedHeaders = {
      "X-My-Header": "My-Value",
    };
    var expectedResult = {
      status: "success",
    };
    var expectedMimeType = "application/test-data";

    spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
      url,
      responseType,
      method,
      data,
      headers,
      deferred,
      overrideMimeType
    ) {
      expect(url).toEqual(expectedUrl);
      expect(responseType).toEqual(expectedResponseType);
      expect(method).toEqual("PATCH");
      expect(data).toEqual(expectedData);
      expect(headers).toEqual(expectedHeaders);
      expect(overrideMimeType).toBe(expectedMimeType);
      deferred.resolve(expectedResult);
    });

    return Resource.patch({
      url: expectedUrl,
      data: expectedData,
      responseType: expectedResponseType,
      headers: expectedHeaders,
      overrideMimeType: expectedMimeType,
    }).then(function (result) {
      expect(result).toEqual(expectedResult);
    });
  });

  it("static fetchArrayBuffer calls correct method", function () {
    var url = "http://test.com/data";
    var expectedResult = when.resolve();
    spyOn(Resource.prototype, "fetchArrayBuffer").and.returnValue(
      expectedResult
    );
    var result = Resource.fetchArrayBuffer(url);
    expect(result).toBe(expectedResult);
    return Resource.fetchArrayBuffer(url).then(function () {
      expect(Resource.prototype.fetchArrayBuffer).toHaveBeenCalled();
    });
  });

  it("fetchArrayBuffer calls fetch with expected parameters", function () {
    var url = "http://test.com/data";
    var expectedResult = when.resolve();
    spyOn(Resource.prototype, "fetch").and.returnValue(expectedResult);
    var result = Resource.fetchArrayBuffer(url);
    expect(result).toBe(expectedResult);
    return result.then(function () {
      expect(Resource.prototype.fetch).toHaveBeenCalledWith({
        responseType: "arraybuffer",
      });
    });
  });

  it("static fetchBlob calls correct method", function () {
    var url = "http://test.com/data";
    spyOn(Resource.prototype, "fetchBlob").and.returnValue(when.resolve());
    return Resource.fetchBlob(url).then(function () {
      expect(Resource.prototype.fetchBlob).toHaveBeenCalled();
    });
  });

  it("fetchBlob calls fetch with expected parameters", function () {
    var url = "http://test.com/data";
    var expectedResult = when.resolve();
    spyOn(Resource.prototype, "fetch").and.returnValue(expectedResult);
    var result = Resource.fetchBlob(url);
    expect(result).toBe(expectedResult);
    return result.then(function () {
      expect(Resource.prototype.fetch).toHaveBeenCalledWith({
        responseType: "blob",
      });
    });
  });

  it("fetchArrayBuffer calls fetch with expected parameters", function () {
    var url = "http://test.com/data";
    var expectedResult = when.resolve();
    spyOn(Resource.prototype, "fetch").and.returnValue(expectedResult);
    var result = Resource.fetchArrayBuffer(url);
    expect(result).toBe(expectedResult);
    return result.then(function () {
      expect(Resource.prototype.fetch).toHaveBeenCalledWith({
        responseType: "arraybuffer",
      });
    });
  });

  it("static fetchImage calls correct method", function () {
    var url = "http://test.com/data";
    spyOn(Resource.prototype, "fetchImage").and.returnValue(when.resolve());
    return Resource.fetchImage(url).then(function () {
      expect(Resource.prototype.fetchImage).toHaveBeenCalled();
    });
  });

  it("static fetchText calls correct method", function () {
    var url = "http://test.com/data";
    spyOn(Resource.prototype, "fetchText").and.returnValue(when.resolve());
    return Resource.fetchText(url).then(function () {
      expect(Resource.prototype.fetchText).toHaveBeenCalled();
    });
  });

  it("fetchText calls fetch with expected parameters", function () {
    var url = "http://test.com/data";
    var expectedResult = when.resolve();
    spyOn(Resource.prototype, "fetch").and.returnValue(expectedResult);
    var result = Resource.fetchText(url);
    expect(result).toBe(expectedResult);
    return result.then(function () {
      expect(Resource.prototype.fetch).toHaveBeenCalledWith({
        responseType: "text",
      });
    });
  });

  it("static fetchJson calls correct method", function () {
    var url = "http://test.com/data";
    spyOn(Resource.prototype, "fetchJson").and.returnValue(when.resolve());
    return Resource.fetchJson(url).then(function () {
      expect(Resource.prototype.fetchJson).toHaveBeenCalled();
    });
  });

  it("fetchJson calls fetch with expected parameters and parses result", function () {
    var expectedResult = { x: 123 };
    spyOn(Resource.prototype, "fetch").and.returnValue(
      when.resolve(JSON.stringify(expectedResult))
    );
    return Resource.fetchJson("url").then(function (result) {
      expect(result).toEqual(expectedResult);
      expect(Resource.prototype.fetch).toHaveBeenCalledWith({
        responseType: "text",
        headers: {
          Accept: "application/json,*/*;q=0.01",
        },
      });
    });
  });

  it("static fetchXML calls correct method", function () {
    var url = "http://test.com/data";
    spyOn(Resource.prototype, "fetchXML").and.returnValue(when.resolve());
    return Resource.fetchXML(url).then(function () {
      expect(Resource.prototype.fetchXML).toHaveBeenCalled();
    });
  });

  it("fetchXML calls fetch with expected parameters", function () {
    var url = "http://test.com/data";
    var expectedResult = when.resolve();
    spyOn(Resource.prototype, "fetch").and.returnValue(expectedResult);
    var result = Resource.fetchXML(url);
    expect(result).toBe(expectedResult);
    return result.then(function () {
      expect(Resource.prototype.fetch).toHaveBeenCalledWith({
        responseType: "document",
        overrideMimeType: "text/xml",
      });
    });
  });

  it("static fetchJsonp calls correct method", function () {
    var url = "http://test.com/data";
    spyOn(Resource.prototype, "fetchJsonp").and.returnValue(when.resolve());
    return Resource.fetchJsonp(url).then(function () {
      expect(Resource.prototype.fetchJsonp).toHaveBeenCalled();
    });
  });

  it("static fetch calls correct method", function () {
    var url = "http://test.com/data";
    spyOn(Resource.prototype, "fetch").and.returnValue(when.resolve());
    return Resource.fetch(url).then(function () {
      expect(Resource.prototype.fetch).toHaveBeenCalled();
    });
  });

  it("fetch calls correct method", function () {
    var expectedUrl = "http://test.com/endpoint";
    var expectedResult = {
      status: "success",
    };

    spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
      url,
      responseType,
      method,
      data,
      headers,
      deferred,
      overrideMimeType
    ) {
      expect(url).toEqual(expectedUrl);
      expect(method).toEqual("GET");
      deferred.resolve(expectedResult);
    });

    var resource = new Resource({ url: expectedUrl });
    return resource.fetch().then(function (result) {
      expect(result).toEqual(expectedResult);
    });
  });

  it("static delete calls correct method", function () {
    var url = "http://test.com/data";
    spyOn(Resource.prototype, "delete").and.returnValue(when.resolve());
    return Resource.delete(url).then(function () {
      expect(Resource.prototype.delete).toHaveBeenCalled();
    });
  });

  it("delete calls correct method", function () {
    var expectedUrl = "http://test.com/endpoint";
    var expectedResult = {
      status: "success",
    };

    spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
      url,
      responseType,
      method,
      data,
      headers,
      deferred,
      overrideMimeType
    ) {
      expect(url).toEqual(expectedUrl);
      expect(method).toEqual("DELETE");
      deferred.resolve(expectedResult);
    });

    var resource = new Resource({ url: expectedUrl });
    return resource.delete().then(function (result) {
      expect(result).toEqual(expectedResult);
    });
  });

  it("static head calls correct method", function () {
    var url = "http://test.com/data";
    spyOn(Resource.prototype, "head").and.returnValue(when.resolve({}));
    return Resource.head(url).then(function () {
      expect(Resource.prototype.head).toHaveBeenCalled();
    });
  });

  it("head calls correct method", function () {
    var expectedUrl = "http://test.com/endpoint";
    var expectedResult = {
      "accept-ranges": "bytes",
      "access-control-allow-headers":
        "Origin, X-Requested-With, Content-Type, Accept",
      "access-control-allow-origin": "*",
      "cache-control": "public, max-age=0",
      connection: "keep-alive",
      "content-length": "883",
      "content-type": "image/png",
      date: "Tue, 13 Feb 2018 03:38:55 GMT",
      etag: 'W/"373-15e34d146a1"',
      vary: "Accept-Encoding",
      "x-powered-vy": "Express",
    };
    var headerString = "";
    for (var key in expectedResult) {
      if (expectedResult.hasOwnProperty(key)) {
        headerString += key + ": " + expectedResult[key] + "\r\n";
      }
    }
    var fakeXHR = {
      status: 200,
      send: function () {
        this.onload();
      },
      open: function () {},
      getAllResponseHeaders: function () {
        return headerString;
      },
    };
    spyOn(window, "XMLHttpRequest").and.returnValue(fakeXHR);

    spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
      url,
      responseType,
      method,
      data,
      headers,
      deferred,
      overrideMimeType
    ) {
      expect(url).toEqual(expectedUrl);
      expect(method).toEqual("HEAD");
      Resource._DefaultImplementations.loadWithXhr(
        url,
        responseType,
        method,
        data,
        headers,
        deferred,
        overrideMimeType
      );
    });

    var resource = new Resource({ url: expectedUrl });
    return resource.head().then(function (result) {
      expect(result.date).toEqual(expectedResult.date);
      expect(result["last-modified"]).toEqual(expectedResult["last-modified"]);
      expect(result["x-powered-by"]).toEqual(expectedResult["x-powered-by"]);
      expect(result.etag).toEqual(expectedResult.etag);
      expect(result["content-type"]).toEqual(expectedResult["content-type"]);
      expect(result["access-control-allow-origin"]).toEqual(
        expectedResult["access-control-allow-origin"]
      );
      expect(result["cache-control"]).toEqual(expectedResult["cache-control"]);
      expect(result["accept-ranges"]).toEqual(expectedResult["accept-ranges"]);
      expect(result["access-control-allow-headers"]).toEqual(
        expectedResult["access-control-allow-headers"]
      );
      expect(result["content-length"]).toEqual(
        expectedResult["content-length"]
      );
    });
  });

  it("static options calls correct method", function () {
    var url = "http://test.com/data";
    spyOn(Resource.prototype, "options").and.returnValue(when.resolve({}));
    return Resource.options(url).then(function () {
      expect(Resource.prototype.options).toHaveBeenCalled();
    });
  });

  it("options calls correct method", function () {
    var expectedUrl = "http://test.com/endpoint";
    var expectedResult = {
      "access-control-allow-headers":
        "Origin, X-Requested-With, Content-Type, Accept",
      "access-control-allow-methods": "GET, PUT, POST, DELETE, OPTIONS",
      "access-control-allow-origin": "*",
      connection: "keep-alive",
      "content-length": "2",
      "content-type": "text/plain; charset=utf-8",
      date: "Tue, 13 Feb 2018 03:38:55 GMT",
      etag: 'W/"2-nOO9QiTIwXgNtWtBJezz8kv3SLc"',
      vary: "Accept-Encoding",
      "x-powered-vy": "Express",
    };
    var headerString = "";
    for (var key in expectedResult) {
      if (expectedResult.hasOwnProperty(key)) {
        headerString += key + ": " + expectedResult[key] + "\r\n";
      }
    }
    var fakeXHR = {
      status: 200,
      send: function () {
        this.onload();
      },
      open: function () {},
      getAllResponseHeaders: function () {
        return headerString;
      },
    };
    spyOn(window, "XMLHttpRequest").and.returnValue(fakeXHR);

    spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
      url,
      responseType,
      method,
      data,
      headers,
      deferred,
      overrideMimeType
    ) {
      expect(url).toEqual(expectedUrl);
      expect(method).toEqual("OPTIONS");
      Resource._DefaultImplementations.loadWithXhr(
        url,
        responseType,
        method,
        data,
        headers,
        deferred,
        overrideMimeType
      );
    });

    var resource = new Resource({ url: expectedUrl });
    return resource.options().then(function (result) {
      expect(result.date).toEqual(expectedResult.date);
      expect(result["x-powered-by"]).toEqual(expectedResult["x-powered-by"]);
      expect(result.etag).toEqual(expectedResult.etag);
      expect(result["content-type"]).toEqual(expectedResult["content-type"]);
      expect(result["access-control-allow-origin"]).toEqual(
        expectedResult["access-control-allow-origin"]
      );
      expect(result["access-control-allow-methods"]).toEqual(
        expectedResult["access-control-allow-methods"]
      );
      expect(result["access-control-allow-headers"]).toEqual(
        expectedResult["access-control-allow-headers"]
      );
      expect(result["content-length"]).toEqual(
        expectedResult["content-length"]
      );
    });
  });

  it("can load an SVG", function () {
    return Resource.fetchImage("./Data/Images/Red16x16.svg").then(function (
      loadedImage
    ) {
      expect(loadedImage.width).toEqual(16);
      expect(loadedImage.height).toEqual(16);
    });
  });

  it("can load an image preferring blob", function () {
    return Resource.fetchImage("./Data/Images/Green.png", true).then(function (
      loadedImage
    ) {
      expect(loadedImage.width).toEqual(1);
      expect(loadedImage.height).toEqual(1);
    });
  });

  it("can load an image from a data URI", function () {
    return Resource.fetchImage(dataUri).then(function (loadedImage) {
      expect(loadedImage.width).toEqual(1);
      expect(loadedImage.height).toEqual(1);
    });
  });

  describe("fetchImage with ImageBitmap", function () {
    var canvas;
    beforeAll(function () {
      canvas = createCanvas(1, 2);
    });

    afterAll(function () {
      document.body.removeChild(canvas);
    });

    function getColorAtPixel(image, x, y) {
      var context = canvas.getContext("2d");
      context.drawImage(image, 0, 0, image.width, image.height);
      var imageData = context.getImageData(0, 0, 1, 1);
      return [
        imageData.data[0],
        imageData.data[1],
        imageData.data[2],
        imageData.data[3],
      ];
    }

    it("can call supportsImageBitmapOptions", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      return Resource.supportsImageBitmapOptions().then(function (result) {
        expect(typeof result).toEqual("boolean");
      });
    });

    it("can load and decode an image", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      return Resource.fetchImage({
        url: "./Data/Images/Green.png",
        preferImageBitmap: true,
      }).then(function (loadedImage) {
        expect(loadedImage.width).toEqual(1);
        expect(loadedImage.height).toEqual(1);
        expect(loadedImage).toBeInstanceOf(ImageBitmap);
      });
    });

    it("correctly flips image when ImageBitmapOptions are supported", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      var loadedImage;

      return Resource.fetchImage({
        url: "./Data/Images/BlueOverRed.png",
        flipY: true,
        preferImageBitmap: true,
      })
        .then(function (image) {
          loadedImage = image;
          return Resource.supportsImageBitmapOptions();
        })
        .then(function (supportsImageBitmapOptions) {
          if (supportsImageBitmapOptions) {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([
              255,
              0,
              0,
              255,
            ]);
          } else {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([
              0,
              0,
              255,
              255,
            ]);
          }
        });
    });

    it("correctly loads image without flip when ImageBitmapOptions are supported", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      var loadedImage;

      return Resource.fetchImage({
        url: "./Data/Images/BlueOverRed.png",
        flipY: false,
        preferImageBitmap: true,
      })
        .then(function (image) {
          loadedImage = image;
          return Resource.supportsImageBitmapOptions();
        })
        .then(function (supportsImageBitmapOptions) {
          if (supportsImageBitmapOptions) {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([
              0,
              0,
              255,
              255,
            ]);
          } else {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([
              0,
              0,
              255,
              255,
            ]);
          }
        });
    });

    it("correctly ignores gamma color profile when ImageBitmapOptions are supported", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      var loadedImage;

      return Resource.fetchImage({
        url: "./Data/Images/Gamma.png",
        flipY: false,
        skipColorSpaceConversion: true,
        preferImageBitmap: true,
      })
        .then(function (image) {
          loadedImage = image;
          return Resource.supportsImageBitmapOptions();
        })
        .then(function (supportsImageBitmapOptions) {
          if (supportsImageBitmapOptions) {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([
              0,
              136,
              0,
              255,
            ]);
          } else {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([0, 59, 0, 255]);
          }
        });
    });

    it("correctly allows gamma color profile when ImageBitmapOptions are supported", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      var loadedImage;

      return Resource.fetchImage({
        url: "./Data/Images/Gamma.png",
        flipY: false,
        skipColorSpaceConversion: false,
        preferImageBitmap: true,
      })
        .then(function (image) {
          loadedImage = image;
          return Resource.supportsImageBitmapOptions();
        })
        .then(function (supportsImageBitmapOptions) {
          if (supportsImageBitmapOptions) {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([0, 59, 0, 255]);
          } else {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([0, 59, 0, 255]);
          }
        });
    });

    it("correctly ignores custom color profile when ImageBitmapOptions are supported", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      var loadedImage;

      return Resource.fetchImage({
        url: "./Data/Images/CustomColorProfile.png",
        flipY: false,
        skipColorSpaceConversion: true,
        preferImageBitmap: true,
      })
        .then(function (image) {
          loadedImage = image;
          return Resource.supportsImageBitmapOptions();
        })
        .then(function (supportsImageBitmapOptions) {
          if (supportsImageBitmapOptions) {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([
              0,
              136,
              0,
              255,
            ]);
          } else {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([
              193,
              0,
              0,
              255,
            ]);
          }
        });
    });

    it("correctly allows custom color profile when ImageBitmapOptions are supported", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      var loadedImage;

      return Resource.fetchImage({
        url: "./Data/Images/CustomColorProfile.png",
        flipY: false,
        skipColorSpaceConversion: false,
        preferImageBitmap: true,
      })
        .then(function (image) {
          loadedImage = image;
          return Resource.supportsImageBitmapOptions();
        })
        .then(function (supportsImageBitmapOptions) {
          if (supportsImageBitmapOptions) {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([
              193,
              0,
              0,
              255,
            ]);
          } else {
            expect(getColorAtPixel(loadedImage, 0, 0)).toEqual([
              193,
              0,
              0,
              255,
            ]);
          }
        });
    });

    it("does not use ImageBitmap when ImageBitmapOptions are not supported", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      spyOn(Resource, "supportsImageBitmapOptions").and.returnValue(
        when.resolve(false)
      );
      spyOn(window, "createImageBitmap").and.callThrough();

      return Resource.fetchImage({
        url: "./Data/Images/Green.png",
        preferImageBitmap: true,
      }).then(function (loadedImage) {
        expect(window.createImageBitmap).not.toHaveBeenCalledWith();
      });
    });

    it("rejects the promise when the image errors", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      return Resource.fetchImage({
        url: "http://example.invalid/testuri.png",
        preferImageBitmap: true,
      })
        .then(function () {
          fail("expected promise to reject");
        })
        .otherwise(function (error) {
          expect(error).toBeInstanceOf(RequestErrorEvent);
        });
    });

    it("rejects the promise with extra error information when image errors and options.preferBlob is true", function () {
      if (!supportsImageBitmapOptions) {
        return;
      }

      // Force the fetching of a bad blob that is not an image to trigger the error
      spyOn(Resource.prototype, "fetch").and.returnValue(
        when.resolve(new Blob([new Uint8Array([])], { type: "text/plain" }))
      );

      return Resource.fetchImage({
        url: "http://example.invalid/testuri.png",
        preferImageBitmap: true,
        preferBlob: true,
      })
        .then(function () {
          fail("expected promise to reject");
        })
        .otherwise(function (error) {
          expect(error.blob).toBeInstanceOf(Blob);
        });
    });
  });

  describe("fetchImage without ImageBitmap", function () {
    beforeAll(function () {
      // Force it to use the Image constructor since these specs all test
      // specific functionality of this code path. For example, the crossOrigin
      // restriction does not apply to images loaded with ImageBitmap.
      spyOn(Resource, "supportsImageBitmapOptions").and.returnValue(
        when.resolve(false)
      );
    });

    it("can load an image", function () {
      return Resource.fetchImage("./Data/Images/Green.png").then(function (
        loadedImage
      ) {
        expect(loadedImage.width).toEqual(1);
        expect(loadedImage.height).toEqual(1);
      });
    });

    it("sets the crossOrigin property for cross-origin images", function () {
      var fakeImage = {};
      var imageConstructorSpy = spyOn(window, "Image").and.returnValue(
        fakeImage
      );

      Resource.fetchImage("http://example.invalid/someImage.png");
      expect(imageConstructorSpy).toHaveBeenCalled();
      expect(fakeImage.crossOrigin).toEqual("");
    });

    it("does not set the crossOrigin property for non-cross-origin images", function () {
      var fakeImage = {};
      var imageConstructorSpy = spyOn(window, "Image").and.returnValue(
        fakeImage
      );

      Resource.fetchImage("./someImage.png");
      expect(imageConstructorSpy).toHaveBeenCalled();
      expect(fakeImage.crossOrigin).toBeUndefined();
    });

    it("does not set the crossOrigin property for data URIs", function () {
      var fakeImage = {};
      var imageConstructorSpy = spyOn(window, "Image").and.returnValue(
        fakeImage
      );

      Resource.fetchImage(dataUri);
      expect(imageConstructorSpy).toHaveBeenCalled();
      expect(fakeImage.crossOrigin).toBeUndefined();
    });

    it("resolves the promise when the image loads", function () {
      var fakeImage = {};
      spyOn(window, "Image").and.returnValue(fakeImage);

      var success = false;
      var failure = false;
      var loadedImage;

      when(
        Resource.fetchImage(dataUri),
        function (image) {
          success = true;
          loadedImage = image;
        },
        function () {
          failure = true;
        }
      );

      // neither callback has fired yet
      expect(success).toEqual(false);
      expect(failure).toEqual(false);

      fakeImage.onload();
      expect(success).toEqual(true);
      expect(failure).toEqual(false);
      expect(loadedImage).toBe(fakeImage);
    });

    it("rejects the promise when the image errors", function () {
      var fakeImage = {};
      spyOn(window, "Image").and.returnValue(fakeImage);

      var success = false;
      var failure = false;
      var loadedImage;

      when(
        Resource.fetchImage(dataUri),
        function (image) {
          success = true;
          loadedImage = image;
        },
        function () {
          failure = true;
        }
      );

      // neither callback has fired yet
      expect(success).toEqual(false);
      expect(failure).toEqual(false);

      fakeImage.onerror();
      expect(success).toEqual(false);
      expect(failure).toEqual(true);
      expect(loadedImage).toBeUndefined();
    });

    it("Calls loadWithXhr with blob response type if headers is set", function () {
      var expectedUrl = "http://example.invalid/testuri.png";
      var expectedHeaders = {
        "X-my-header": "my-value",
      };
      spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
        url,
        responseType,
        method,
        data,
        headers,
        deferred,
        overrideMimeType
      ) {
        expect(url).toEqual(expectedUrl);
        expect(headers).toEqual(expectedHeaders);
        expect(responseType).toEqual("blob");

        var binary = dataUriToBuffer(dataUri);

        deferred.resolve(new Blob([binary], { type: "image/png" }));
      });

      var testResource = new Resource({
        url: expectedUrl,
        headers: expectedHeaders,
      });
      var promise = testResource.fetchImage();
      expect(promise).toBeDefined();

      return promise.then(function (image) {
        expect(image).toBeDefined();
      });
    });

    it("Doesn't call loadWithXhr with blob response type if headers is set but is a data URI", function () {
      spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function (
        url,
        responseType,
        method,
        data,
        headers,
        deferred,
        overrideMimeType
      ) {
        deferred.reject("this shouldn't happen");
      });

      spyOn(Resource._Implementations, "createImage")
        .and.callFake(function (url, crossOrigin, deferred) {
          expect(url).toEqual(dataUri);
        })
        .and.callThrough();

      var testResource = new Resource({
        url: dataUri,
        headers: {
          "X-my-header": "my-value",
        },
      });
      var promise = testResource.fetchImage();
      expect(promise).toBeDefined();

      return promise.then(function (image) {
        expect(image).toBeDefined();
      });
    });

    describe("retries when Resource has the callback set", function () {
      it("rejects after too many retries", function () {
        var fakeImage = {};
        spyOn(window, "Image").and.returnValue(fakeImage);

        var cb = jasmine.createSpy("retry").and.returnValue(true);

        var resource = new Resource({
          url: "http://example.invalid/image.png",
          retryCallback: cb,
          retryAttempts: 1,
        });

        var promise = resource.fetchImage();

        expect(promise).toBeDefined();

        var success = false;
        var failure = false;
        promise
          .then(function () {
            success = true;
          })
          .otherwise(function () {
            failure = true;
          });

        expect(success).toBe(false);
        expect(failure).toBe(false);

        fakeImage.onerror("some error"); // This should retry
        expect(success).toBe(false);
        expect(failure).toBe(false);

        expect(cb.calls.count()).toEqual(1);
        var receivedResource = cb.calls.argsFor(0)[0];
        expect(receivedResource.url).toEqual(resource.url);
        expect(receivedResource._retryCount).toEqual(1);
        expect(cb.calls.argsFor(0)[1]).toEqual("some error");

        fakeImage.onerror(); // This fails because we only retry once
        expect(success).toBe(false);
        expect(failure).toBe(true);
      });

      it("rejects after callback returns false", function () {
        var fakeImage = {};
        spyOn(window, "Image").and.returnValue(fakeImage);

        var cb = jasmine.createSpy("retry").and.returnValue(false);

        var resource = new Resource({
          url: "http://example.invalid/image.png",
          retryCallback: cb,
          retryAttempts: 2,
        });

        var promise = resource.fetchImage();

        expect(promise).toBeDefined();

        var success = false;
        var failure = false;
        promise
          .then(function (value) {
            success = true;
          })
          .otherwise(function (error) {
            failure = true;
          });

        expect(success).toBe(false);
        expect(failure).toBe(false);

        fakeImage.onerror("some error"); // This fails because the callback returns false
        expect(success).toBe(false);
        expect(failure).toBe(true);

        expect(cb.calls.count()).toEqual(1);
        var receivedResource = cb.calls.argsFor(0)[0];
        expect(receivedResource.url).toEqual(resource.url);
        expect(receivedResource._retryCount).toEqual(1);
        expect(cb.calls.argsFor(0)[1]).toEqual("some error");
      });

      it("resolves after retry", function () {
        var fakeImage = {};
        spyOn(window, "Image").and.returnValue(fakeImage);

        var cb = jasmine.createSpy("retry").and.returnValue(true);

        var resource = new Resource({
          url: "http://example.invalid/image.png",
          retryCallback: cb,
          retryAttempts: 1,
        });

        var promise = resource.fetchImage();

        expect(promise).toBeDefined();

        var success = false;
        var failure = false;
        promise
          .then(function (value) {
            success = true;
          })
          .otherwise(function (error) {
            failure = true;
          });

        expect(success).toBe(false);
        expect(failure).toBe(false);

        fakeImage.onerror("some error"); // This should retry
        expect(success).toBe(false);
        expect(failure).toBe(false);

        expect(cb.calls.count()).toEqual(1);
        var receivedResource = cb.calls.argsFor(0)[0];
        expect(receivedResource.url).toEqual(resource.url);
        expect(receivedResource._retryCount).toEqual(1);
        expect(cb.calls.argsFor(0)[1]).toEqual("some error");

        fakeImage.onload();
        expect(success).toBe(true);
        expect(failure).toBe(false);
      });
    });
  });

  describe("loadWithXhr", function () {
    var loadWithXhr = function (options) {
      var resource = new Resource(options);
      return resource._makeRequest({
        responseType: options.responseType,
        overrideMimeType: options.overrideMimeType,
        method: defaultValue(options.method, "GET"),
        data: options.data,
      });
    };

    describe("data URI loading", function () {
      it("can load URI escaped text with default response type", function () {
        return loadWithXhr({
          url: "data:,Hello%2C%20World!",
        }).then(function (result) {
          expect(result).toEqual("Hello, World!");
        });
      });

      it("can load URI escaped text with responseType=text", function () {
        return loadWithXhr({
          url: "data:,Hello%2C%20World!",
          responseType: "text",
        }).then(function (result) {
          expect(result).toEqual("Hello, World!");
        });
      });

      it("can load Base64 encoded text with default response type", function () {
        return loadWithXhr({
          url: "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==",
        }).then(function (result) {
          expect(result).toEqual("Hello, World!");
        });
      });

      it("can load Base64 encoded text with responseType=text", function () {
        return loadWithXhr({
          url: "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==",
          responseType: "text",
        }).then(function (result) {
          expect(result).toEqual("Hello, World!");
        });
      });

      it("can load Base64 & URI encoded text with default responseType", function () {
        return loadWithXhr({
          url: "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D",
        }).then(function (result) {
          expect(result).toEqual("Hello, World!");
        });
      });

      it("can load Base64 & URI encoded text with responseType=text", function () {
        return loadWithXhr({
          url: "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D",
          responseType: "text",
        }).then(function (result) {
          expect(result).toEqual("Hello, World!");
        });
      });

      it("can load URI escaped HTML as text with default responseType", function () {
        return loadWithXhr({
          url: "data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E",
        }).then(function (result) {
          expect(result).toEqual("<h1>Hello, World!</h1>");
        });
      });

      it("can load URI escaped HTML as text with responseType=text", function () {
        return loadWithXhr({
          url: "data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E",
          responseType: "text",
        }).then(function (result) {
          expect(result).toEqual("<h1>Hello, World!</h1>");
        });
      });

      it("can load URI escaped text as JSON", function () {
        return loadWithXhr({
          url: "data:application/json,%7B%22key%22%3A%22value%22%7D",
          responseType: "json",
        }).then(function (result) {
          expect(result.key).toEqual("value");
        });
      });

      it("can load Base64 encoded text as JSON", function () {
        return loadWithXhr({
          url: "data:application/json;base64,eyJrZXkiOiJ2YWx1ZSJ9",
          responseType: "json",
        }).then(function (result) {
          expect(result.key).toEqual("value");
        });
      });

      it("can load URI escaped HTML as document", function () {
        return loadWithXhr({
          url: "data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E",
          responseType: "document",
        }).then(function (result) {
          expect(result.querySelector("h1")).not.toBeNull();
        });
      });

      var tile =
        "data:;base64, ";

      it("can load Base64 encoded data as arraybuffer", function () {
        return loadWithXhr({
          url: tile,
          responseType: "arraybuffer",
        }).then(function (result) {
          expect(result.byteLength).toEqual(3914);
        });
      });

      var image =
        "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEISURBVEhLvVXBDYQwDOuojHKj8LhBbpTbpBCEkZsmIVTXq1RVQGrHiWlLmTTqPiZBlyLgy/KSZQ5JSHDQ/mCYCsC8106kDU0AdwRnvYZArWRcAl0dcYJq1hWCb3hBrumbDAVMwAC82WoRvgMnVMDBnB0nYZFTbE6BBvdUGqVqCbjBIk3PyFFR/NU7EKzru+qZsau3ryPwwCRLKYOzutZuCL6fUmWeJGzNzL/RxAMrUmASSCkkAayk2IxPlwhAAYGpsiHQjbLccfdOY5gKkCXAMi7SscAwbQpAnKyctWyUZ6z8ja3OGMepwD8asz+9FnSvbhU8uVOHFIwQsI3/p0CfhuqCSQuxLqsN6mu8SS+N42MAAAAASUVORK5CYII=";

      it("can load Base64 encoded data as blob", function () {
        return loadWithXhr({
          url: image,
          responseType: "blob",
        }).then(function (result) {
          expect(result.type).toEqual("image/png");

          var blobUrl = URL.createObjectURL(result);

          return Resource.fetchImage(blobUrl).then(function (image) {
            expect(image.width).toEqual(24);
            expect(image.height).toEqual(24);
          });
        });
      });

      xit("can support 2xx HTTP status (other than 200)", function () {
        return loadWithXhr({
          method: "POST",
          url: "http://jsonplaceholder.typicode.com/posts",
          data: {
            title: "foo",
            body: "bar",
            userId: 1,
          },
        }).then(function (result) {
          expect(JSON.parse(result).id).toEqual(101);
        });
      });
    });

    describe("URL loading using XHR", function () {
      describe("returns a promise that rejects when the request", function () {
        it("results in an HTTP status code greater than or equal to 300", function () {
          return loadWithXhr({
            url: "http://example.invalid",
          })
            .then(function () {
              fail("expected promise to reject");
            })
            .otherwise(function (err) {
              expect(err).toBeInstanceOf(RequestErrorEvent);
            });
        });

        it("loads an invalid JSON string response with a json responseType", function () {
          return loadWithXhr({
            url: "Data/htmlString.txt",
            responseType: "json",
          })
            .then(function () {
              fail("expected promise to reject");
            })
            .otherwise(function (err) {
              expect(err).toBeDefined();
              expect(err).toBeInstanceOf(Error);
            });
        });
      });

      describe("returns a promise that resolves when the request loads", function () {
        it("a non-null response with default responseType", function () {
          return loadWithXhr({
            url: "Data/Models/Box/ReadMe.txt",
          }).then(function (result) {
            expect(result).toBe(
              "CesiumBoxTest-NoTechnique.gltf is a modified glTF that has techniques, shaders & programs removed."
            );
          });
        });

        it("a non-null response with a browser-supported responseType", function () {
          return loadWithXhr({
            url: "Data/Models/Box/ReadMe.txt",
            responseType: "text",
          }).then(function (result) {
            expect(result).toBe(
              "CesiumBoxTest-NoTechnique.gltf is a modified glTF that has techniques, shaders & programs removed."
            );
          });
        });

        it("a valid JSON string response as JSON with a json responseType", function () {
          return loadWithXhr({
            url: "Data/jsonString.txt",
            responseType: "json",
          }).then(function (result) {
            expect(result).toEqual(
              jasmine.objectContaining({ hello: "world" })
            );
          });
        });
      });
    });

    describe("URL loading using mocked XHR", function () {
      var fakeXHR;

      beforeEach(function () {
        fakeXHR = jasmine.createSpyObj("XMLHttpRequest", [
          "send",
          "open",
          "setRequestHeader",
          "abort",
          "getAllResponseHeaders",
        ]);
        fakeXHR.simulateError = function () {
          fakeXHR.response = "";
          if (typeof fakeXHR.onerror === "function") {
            fakeXHR.onerror();
          }
        };
        fakeXHR.simulateHttpResponse = function (statusCode, response) {
          fakeXHR.status = statusCode;
          fakeXHR.response = response;
          if (typeof fakeXHR.onload === "function") {
            fakeXHR.onload();
          }
        };
        fakeXHR.simulateResponseXMLLoad = function (responseXML) {
          fakeXHR.status = 200;
          fakeXHR.responseXML = responseXML;
          if (typeof fakeXHR.onload === "function") {
            fakeXHR.onload();
          }
        };
        fakeXHR.simulateResponseTextLoad = function (responseText) {
          fakeXHR.simulateHttpResponse(200, responseText);
        };

        spyOn(window, "XMLHttpRequest").and.returnValue(fakeXHR);
      });

      describe("returns a promise that rejects when the request", function () {
        it("errors", function () {
          var promise = loadWithXhr({
            url: "http://example.invalid",
          });

          expect(promise).toBeDefined();

          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          fakeXHR.simulateError();
          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeInstanceOf(RequestErrorEvent);
        });

        it("results in an HTTP status code less than 200", function () {
          var promise = loadWithXhr({
            url: "http://example.invalid",
          });

          expect(promise).toBeDefined();

          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          fakeXHR.simulateHttpResponse(199);
          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeInstanceOf(RequestErrorEvent);
        });

        it("is an image with status code 204 with preferImageBitmap", function () {
          if (!supportsImageBitmapOptions) {
            return;
          }

          var promise = Resource.fetchImage({
            url: "./Data/Images/Green.png",
            preferImageBitmap: true,
          });

          expect(promise).toBeDefined();

          var resolved = false;
          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolved = true;
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          fakeXHR.simulateHttpResponse(204);
          expect(resolved).toBe(false);
          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeDefined();
        });

        it("resolves undefined for status code 204", function () {
          var promise = loadWithXhr({
            url: "http://example.invalid",
          });

          expect(promise).toBeDefined();

          var resolved = false;
          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolved = true;
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          fakeXHR.simulateHttpResponse(204);
          expect(resolved).toBe(true);
          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();
        });
      });

      describe("returns a promise that resolves when the request loads", function () {
        it("a null response with a '' responseType and non-null responseXML with child nodes", function () {
          var promise = loadWithXhr({
            url: "http://example.invalid",
            responseType: "",
          });

          expect(promise).toBeDefined();

          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          var responseXML = {
            hasChildNodes: jasmine
              .createSpy("hasChildNodes")
              .and.returnValue(true),
          };
          fakeXHR.simulateResponseXMLLoad(responseXML);
          expect(resolvedValue).toEqual(responseXML);
          expect(rejectedError).toBeUndefined();
        });

        it("a null response with a document responseType and non-null responseXML with child nodes", function () {
          var promise = loadWithXhr({
            url: "http://example.invalid",
            responseType: "document",
          });

          expect(promise).toBeDefined();

          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          var responseXML = {
            hasChildNodes: jasmine
              .createSpy("hasChildNodes")
              .and.returnValue(true),
          };
          fakeXHR.simulateResponseXMLLoad(responseXML);
          expect(resolvedValue).toEqual(responseXML);
          expect(rejectedError).toBeUndefined();
        });

        it("a null response with a '' responseType and non-null responseText", function () {
          var promise = loadWithXhr({
            url: "http://example.invalid",
            responseType: "",
          });

          expect(promise).toBeDefined();

          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          var responseText = "hello world";
          fakeXHR.simulateResponseTextLoad(responseText);
          expect(resolvedValue).toEqual(responseText);
          expect(rejectedError).toBeUndefined();
        });

        it("a null response with a text responseType and non-null responseText", function () {
          var promise = loadWithXhr({
            url: "http://example.invalid",
            responseType: "text",
          });

          expect(promise).toBeDefined();

          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          var responseText = "hello world";
          fakeXHR.simulateResponseTextLoad(responseText);
          expect(resolvedValue).toEqual(responseText);
          expect(rejectedError).toBeUndefined();
        });
      });

      describe("retries when Resource has the callback set", function () {
        it("rejects after too many retries", function () {
          var cb = jasmine.createSpy("retry").and.returnValue(true);

          var resource = new Resource({
            url: "http://example.invalid",
            retryCallback: cb,
            retryAttempts: 1,
          });

          var promise = loadWithXhr(resource);

          expect(promise).toBeDefined();

          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          fakeXHR.simulateError(); // This should retry
          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          expect(cb.calls.count()).toEqual(1);
          var receivedResource = cb.calls.argsFor(0)[0];
          expect(receivedResource.url).toEqual(resource.url);
          expect(receivedResource._retryCount).toEqual(1);
          expect(cb.calls.argsFor(0)[1] instanceof RequestErrorEvent).toBe(
            true
          );

          fakeXHR.simulateError(); // This fails because we only retry once
          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeInstanceOf(RequestErrorEvent);
        });

        it("rejects after callback returns false", function () {
          var cb = jasmine.createSpy("retry").and.returnValue(false);

          var resource = new Resource({
            url: "http://example.invalid",
            retryCallback: cb,
            retryAttempts: 2,
          });

          var promise = loadWithXhr(resource);

          expect(promise).toBeDefined();

          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          fakeXHR.simulateError(); // This fails because the callback returns false
          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeInstanceOf(RequestErrorEvent);

          expect(cb.calls.count()).toEqual(1);
          var receivedResource = cb.calls.argsFor(0)[0];
          expect(receivedResource.url).toEqual(resource.url);
          expect(receivedResource._retryCount).toEqual(1);
          expect(cb.calls.argsFor(0)[1] instanceof RequestErrorEvent).toBe(
            true
          );
        });

        it("resolves after retry", function () {
          var cb = jasmine.createSpy("retry").and.returnValue(true);

          var resource = new Resource({
            url: "http://example.invalid",
            retryCallback: cb,
            retryAttempts: 1,
          });

          var promise = loadWithXhr(resource);

          expect(promise).toBeDefined();

          var resolvedValue;
          var rejectedError;
          promise
            .then(function (value) {
              resolvedValue = value;
            })
            .otherwise(function (error) {
              rejectedError = error;
            });

          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          fakeXHR.simulateError(); // This should retry
          expect(resolvedValue).toBeUndefined();
          expect(rejectedError).toBeUndefined();

          expect(cb.calls.count()).toEqual(1);
          var receivedResource = cb.calls.argsFor(0)[0];
          expect(receivedResource.url).toEqual(resource.url);
          expect(receivedResource._retryCount).toEqual(1);
          expect(cb.calls.argsFor(0)[1] instanceof RequestErrorEvent).toBe(
            true
          );

          fakeXHR.simulateHttpResponse(200, "OK");
          expect(resolvedValue).toBeDefined();
          expect(rejectedError).toBeUndefined();
        });
      });
    });
  });

  describe("fetchJsonp", function () {
    it("returns a promise that resolves when the request loads", function () {
      var testUrl = "http://example.invalid/testuri";
      spyOn(Resource._Implementations, "loadAndExecuteScript").and.callFake(
        function (url, name, deffered) {
          expect(url).toContain(testUrl);
          expect(name).toContain("loadJsonp");
          expect(deffered).toBeDefined();
        }
      );
      Resource.fetchJsonp(testUrl);
    });

    it("returns a promise that rejects when the request errors", function () {
      var testUrl = "http://example.invalid/testuri";
      return Resource.fetchJsonp(testUrl).otherwise(function (error) {
        expect(error).toBeDefined();
      });
    });

    it("Uses callback name specified in options", function () {
      var testUrl = "test";
      var options = {
        callbackParameterName: "testCallback",
      };
      spyOn(Resource._Implementations, "loadAndExecuteScript").and.callFake(
        function (url, functionName, deferred) {
          expect(url).toContain("callback=loadJsonp");
        }
      );
      Resource.fetchJsonp(testUrl, options);
    });

    describe("retries when Resource has the callback set", function () {
      it("rejects after too many retries", function () {
        //var cb = jasmine.createSpy('retry').and.returnValue(true);
        var cb = jasmine
          .createSpy("retry")
          .and.callFake(function (resource, error) {
            return true;
          });

        var lastDeferred;
        spyOn(Resource._Implementations, "loadAndExecuteScript").and.callFake(
          function (url, functionName, deferred) {
            lastDeferred = deferred;
          }
        );

        var resource = new Resource({
          url: "http://example.invalid",
          retryCallback: cb,
          retryAttempts: 1,
        });

        var promise = resource.fetchJsonp();

        expect(promise).toBeDefined();

        var resolvedValue;
        var rejectedError;
        promise
          .then(function (value) {
            resolvedValue = value;
          })
          .otherwise(function (error) {
            rejectedError = error;
          });

        expect(resolvedValue).toBeUndefined();
        expect(rejectedError).toBeUndefined();

        lastDeferred.reject("some error"); // This should retry
        expect(resolvedValue).toBeUndefined();
        expect(rejectedError).toBeUndefined();

        expect(cb.calls.count()).toEqual(1);
        var receivedResource = cb.calls.argsFor(0)[0];
        expect(receivedResource.url).toEqual(resource.url);
        expect(receivedResource._retryCount).toEqual(1);
        expect(cb.calls.argsFor(0)[1]).toEqual("some error");

        lastDeferred.reject("another error"); // This fails because we only retry once
        expect(resolvedValue).toBeUndefined();
        expect(rejectedError).toEqual("another error");
      });

      it("rejects after callback returns false", function () {
        var cb = jasmine.createSpy("retry").and.returnValue(false);

        var lastDeferred;
        spyOn(Resource._Implementations, "loadAndExecuteScript").and.callFake(
          function (url, functionName, deferred) {
            lastDeferred = deferred;
          }
        );

        var resource = new Resource({
          url: "http://example.invalid",
          retryCallback: cb,
          retryAttempts: 2,
        });

        var promise = resource.fetchJsonp();

        expect(promise).toBeDefined();

        var resolvedValue;
        var rejectedError;
        promise
          .then(function (value) {
            resolvedValue = value;
          })
          .otherwise(function (error) {
            rejectedError = error;
          });

        expect(resolvedValue).toBeUndefined();
        expect(rejectedError).toBeUndefined();

        lastDeferred.reject("some error"); // This fails because the callback returns false
        expect(resolvedValue).toBeUndefined();
        expect(rejectedError).toEqual("some error");

        expect(cb.calls.count()).toEqual(1);
        var receivedResource = cb.calls.argsFor(0)[0];
        expect(receivedResource.url).toEqual(resource.url);
        expect(receivedResource._retryCount).toEqual(1);
        expect(cb.calls.argsFor(0)[1]).toEqual("some error");
      });

      it("resolves after retry", function () {
        var cb = jasmine.createSpy("retry").and.returnValue(true);

        var lastDeferred;
        var lastUrl;
        spyOn(Resource._Implementations, "loadAndExecuteScript").and.callFake(
          function (url, functionName, deferred) {
            lastUrl = url;
            lastDeferred = deferred;
          }
        );

        var resource = new Resource({
          url: "http://example.invalid",
          retryCallback: cb,
          retryAttempts: 1,
        });

        var promise = resource.fetchJsonp();

        expect(promise).toBeDefined();

        var resolvedValue;
        var rejectedError;
        promise
          .then(function (value) {
            resolvedValue = value;
          })
          .otherwise(function (error) {
            rejectedError = error;
          });

        expect(resolvedValue).toBeUndefined();
        expect(rejectedError).toBeUndefined();

        lastDeferred.reject("some error"); // This should retry
        expect(resolvedValue).toBeUndefined();
        expect(rejectedError).toBeUndefined();

        expect(cb.calls.count()).toEqual(1);
        var receivedResource = cb.calls.argsFor(0)[0];
        expect(receivedResource.url).toEqual(resource.url);
        expect(receivedResource._retryCount).toEqual(1);
        expect(cb.calls.argsFor(0)[1]).toEqual("some error");

        var uri = new Uri(lastUrl);
        var query = queryToObject(uri.query);
        window[query.callback]("something good");
        expect(resolvedValue).toEqual("something good");
        expect(rejectedError).toBeUndefined();
      });
    });
  });
});
