Exemple #1
0
describe("jsdom/cancel-requests", { skipIfBrowser: true }, () => {
  let server;
  let host;

  before(() => {
    return createServer((req, res) => {
      setTimeout(() => {
        res.writeHead(200, { "Content-Length": routes[req.url].length });
        res.end(routes[req.url]);
      }, 200);
    })
    .then(s => {
      server = s;
      host = `http://127.0.0.1:${s.address().port}`;
    });
  });

  after(() => {
    return server.destroy();
  });

  specify("aborting env request should stop window creation", { async: true }, t => {
    const req = jsdom.env({
      url: host + "/html",
      created(err, window) {
        assert.ok(err, "There should be an error");
        assert.notOk(window, "the window should not have been created");
        t.done();
      },
      features: {
        FetchExternalResources: ["script"],
        ProcessExternalResources: ["script"]
      }
    });
    req.abort();
  });

  specify("closing window should close requests", { async: true }, t => {
    jsdom.env({
      url: host + "/html",
      created(err, window) {
        assert.ifError(err, "There should be no errors");
        process.nextTick(() => {
          const script = window.document.getElementsByTagName("script")[1];
          script.onload = () => {
            assert.ok(false, "the external script onload should not be executed (old)");
          };
          script.addEventListener("load", () => {
            assert.ok(false, "the external script onload should not be executed");
          }, false);

          window.close();

          setTimeout(() => {
            assert.notOk(window.testJs, "the external script should not execute");
            t.done();
          }, 1000);
        });
      },
      features: {
        FetchExternalResources: ["script"],
        ProcessExternalResources: ["script"]
      }
    });
  });

  specify("closing window should close XHR", { async: true }, t => {
    jsdom.env({
      url: host + "/html",
      created(err, window) {
        assert.ifError(err, "There should be no errors");
        process.nextTick(() => {
          const xhr = new window.XMLHttpRequest();
          xhr.open("GET", "/xhr", true);
          xhr.onload = () => {
            assert.ok(false, "xhr should not trigger load (old)");
            t.done();
          };
          xhr.addEventListener("load", () => {
            assert.ok(false, "xhr should not trigger load");
          }, false);
          xhr.send();
          window.close();
          setTimeout(() => {
            t.done();
          }, 1000);
        });
      }
    });
  });

  specify("stopping window should close requests and error event should be triggered", { async: true }, t => {
    jsdom.env({
      url: host + "/html",
      created(err, window) {
        assert.ifError(err, "There should be no errors");
        process.nextTick(() => {
          const script = window.document.getElementsByTagName("script")[1];
          script.onerror = () => {
            assert.ok(false, "the external script onerror should not be executed (old)");
          };
          script.addEventListener("error", () => {
            assert.ok(false, "the external script onerror should not be executed");
          });
          window.stop();
          setTimeout(() => {
            assert.ok(!window.testJs, "the external script should not execute");
            t.done();
          }, 1000);
        });
      },
      features: {
        FetchExternalResources: ["script"],
        ProcessExternalResources: ["script"]
      }
    });
  });

  specify("stopping window should close xhr and abort event should be triggered", { async: true }, t => {
    jsdom.env({
      url: host + "/html",
      created(err, window) {
        assert.ifError(err, "There should be no errors");
        process.nextTick(() => {
          const xhr = new window.XMLHttpRequest();
          xhr.open("GET", "/xhr", true);
          xhr.onreadystatechange = () => {
            assert.ok(true, "xhr should trigger change state (old)");
          };
          xhr.addEventListener("readystatechange", () => {
            assert.ok(true, "xhr should trigger change state");
          }, false);
          xhr.onload = () => {
            assert.ok(false, "xhr should not trigger load (old)");
            t.done();
          };
          xhr.addEventListener("load", () => {
            assert.ok(false, "xhr should not trigger load");
            t.done();
          }, false);
          xhr.onabort = () => {
            assert.ok(true, "xhr should trigger abort (old)");
          };
          xhr.addEventListener("abort", () => {
            assert.ok(true, "xhr should trigger abort");
          }, false);
          xhr.send();
          window.stop();
          setTimeout(() => {
            t.done();
          }, 1000);
        });
      }
    });
  });
});
Exemple #2
0
describe("API: constructor options", () => {
  describe("(general tests)", () => {
    it("should not mutate the passed-in options object", () => {
      const options = {};

      // eslint-disable-next-line no-new
      new JSDOM(``, options);

      assert.strictEqual(Object.getOwnPropertyNames(options).length, 0);
    });
  });

  describe("referrer", () => {
    it("should allow customizing document.referrer via the referrer option", () => {
      const { document } = (new JSDOM(``, { referrer: "http://example.com/" })).window;

      assert.strictEqual(document.referrer, "http://example.com/");
    });

    it("should throw an error when passing an invalid absolute URL for referrer", () => {
      assert.throws(() => new JSDOM(``, { referrer: "asdf" }), TypeError);
    });

    it("should canonicalize referrer URLs", () => {
      const { document } = (new JSDOM(``, { referrer: "http:example.com" })).window;

      assert.strictEqual(document.referrer, "http://example.com/");
    });

    it("should have a default referrer URL of the empty string", () => {
      const { document } = (new JSDOM()).window;

      assert.strictEqual(document.referrer, "");
    });
  });

  describe("url", () => {
    it("should allow customizing document URL via the url option", () => {
      const { window } = new JSDOM(``, { url: "http://example.com/" });

      assert.strictEqual(window.location.href, "http://example.com/");
      assert.strictEqual(window.document.URL, "http://example.com/");
      assert.strictEqual(window.document.documentURI, "http://example.com/");
    });

    it("should throw an error when passing an invalid absolute URL for url", () => {
      assert.throws(() => new JSDOM(``, { url: "asdf" }), TypeError);
      assert.throws(() => new JSDOM(``, { url: "/" }), TypeError);
    });

    it("should canonicalize document URLs", () => {
      const { window } = new JSDOM(``, { url: "http:example.com" });

      assert.strictEqual(window.location.href, "http://example.com/");
      assert.strictEqual(window.document.URL, "http://example.com/");
      assert.strictEqual(window.document.documentURI, "http://example.com/");
    });

    it("should have a default document URL of about:blank", () => {
      const { window } = new JSDOM();

      assert.strictEqual(window.location.href, "about:blank");
      assert.strictEqual(window.document.URL, "about:blank");
      assert.strictEqual(window.document.documentURI, "about:blank");
    });
  });

  describe("contentType", () => {
    it("should have a default content type of text/html", () => {
      const { document } = (new JSDOM()).window;

      assert.strictEqual(document.contentType, "text/html");
    });

    it("should allow customizing document content type via the contentType option", () => {
      const { document } = (new JSDOM(``, { contentType: "application/funstuff+xml" })).window;

      assert.strictEqual(document.contentType, "application/funstuff+xml");
    });

    it("should not show content type parameters in document.contentType (HTML)", () => {
      const { document } = (new JSDOM(``, { contentType: "text/html; charset=utf8" })).window;

      assert.strictEqual(document.contentType, "text/html");
    });

    it("should not show content type parameters in document.contentType (XML)", () => {
      const { document } = (new JSDOM(``, { contentType: "application/xhtml+xml; charset=utf8" })).window;

      assert.strictEqual(document.contentType, "application/xhtml+xml");
    });

    it("should disallow content types that are unparseable", () => {
      assert.throws(() => new JSDOM(``, { contentType: "" }), Error);
      assert.throws(() => new JSDOM(``, { contentType: "html" }), Error);
      assert.throws(() => new JSDOM(``, { contentType: "text/html/xml" }), Error);
    });

    it("should disallow content types that are not XML or HTML", () => {
      assert.throws(() => new JSDOM(``, { contentType: "text/sgml" }), RangeError);
      assert.throws(() => new JSDOM(``, { contentType: "application/javascript" }), RangeError);
      assert.throws(() => new JSDOM(``, { contentType: "text/plain" }), RangeError);
    });
  });

  describe("userAgent", () => {
    it("should have a default user agent following the correct pattern", () => {
      const expected = `Mozilla/5.0 (${process.platform}) AppleWebKit/537.36 ` +
                       `(KHTML, like Gecko) jsdom/${packageVersion}`;

      const dom = new JSDOM();
      assert.strictEqual(dom.window.navigator.userAgent, expected);
    });

    it("should set the user agent to the given value", () => {
      const dom = new JSDOM(``, { userAgent: "test user agent" });
      assert.strictEqual(dom.window.navigator.userAgent, "test user agent");
    });
  });

  describe("includeNodeLocations", () => {
    it("should throw when set to true alongside an XML content type", () => {
      assert.throws(() => new JSDOM(``, {
        includeNodeLocations: true,
        contentType: "application/xhtml+xml"
      }));
    });

    // mostly tested by nodeLocation() tests in ./methods.js
  });

  describe("cookieJar", () => {
    it("should use the passed cookie jar", () => {
      const cookieJar = new jsdom.CookieJar();
      const dom = new JSDOM(``, { cookieJar });

      assert.strictEqual(dom.cookieJar, cookieJar);
    });

    it("should reflect changes to the cookie jar in document.cookie", () => {
      const cookieJar = new jsdom.CookieJar();
      const { document } = (new JSDOM(``, { cookieJar })).window;

      cookieJar.setCookieSync("foo=bar", document.URL);

      assert.strictEqual(document.cookie, "foo=bar");
    });

    it("should have loose behavior by default when using the CookieJar constructor", () => {
      const cookieJar = new jsdom.CookieJar();
      const { document } = (new JSDOM(``, { cookieJar })).window;

      cookieJar.setCookieSync("foo", document.URL);

      assert.strictEqual(document.cookie, "foo");
    });

    it("should have a loose-by-default cookie jar even if none is passed", () => {
      const dom = new JSDOM();
      const { document } = dom.window;

      dom.cookieJar.setCookieSync("foo", document.URL);

      assert.instanceOf(dom.cookieJar, jsdom.CookieJar);
      assert.strictEqual(document.cookie, "foo");
    });
  });

  describe("virtualConsole", () => {
    it("should use the passed virtual console", () => {
      const virtualConsole = new jsdom.VirtualConsole();
      const dom = new JSDOM(``, { virtualConsole });

      assert.strictEqual(dom.virtualConsole, virtualConsole);
    });

    it("should have a virtual console even if none is passed", () => {
      const dom = new JSDOM();
      assert.instanceOf(dom.virtualConsole, jsdom.VirtualConsole);
    });
  });

  describe("beforeParse", () => {
    it("should execute with a window and document but no nodes", () => {
      let windowPassed;

      const dom = new JSDOM(``, {
        beforeParse(window) {
          assert.instanceOf(window, window.Window);
          assert.instanceOf(window.document, window.Document);

          assert.strictEqual(window.document.doctype, null);
          assert.strictEqual(window.document.documentElement, null);
          assert.strictEqual(window.document.childNodes.length, 0);

          windowPassed = window;
        }
      });

      assert.strictEqual(windowPassed, dom.window);
    });

    it("should not have built-ins on the window by default", () => {
      let windowPassed;

      const dom = new JSDOM(``, {
        beforeParse(window) {
          assert.strictEqual(window.Array, undefined);

          windowPassed = window;
        }
      });

      assert.strictEqual(windowPassed, dom.window);
    });

    it("should have built-ins on the window when running scripts outside-only", () => {
      let windowPassed;

      const dom = new JSDOM(``, {
        runScripts: "outside-only",
        beforeParse(window) {
          assert.typeOf(window.Array, "function");

          windowPassed = window;
        }
      });

      assert.strictEqual(windowPassed, dom.window);
    });

    it("should have built-ins on the window when running scripts dangerously", () => {
      let windowPassed;

      const dom = new JSDOM(``, {
        runScripts: "dangerously",
        beforeParse(window) {
          assert.typeOf(window.Array, "function");

          windowPassed = window;
        }
      });

      assert.strictEqual(windowPassed, dom.window);
    });
  });

  describe("pretendToBeVisual", () => {
    describe("not set", () => {
      it("document should be hidden and in prerender", () => {
        const { document } = (new JSDOM(``)).window;

        assert.strictEqual(document.hidden, true);
        assert.strictEqual(document.visibilityState, "prerender");
      });

      it("document should not have rAF", () => {
        const { window } = new JSDOM(``);

        assert.isUndefined(window.requestAnimationFrame);
        assert.isUndefined(window.cancelAnimationFrame);
      });
    });

    describe("set to true", () => {
      it("document should be not be hidden and be visible", () => {
        const { document } = (new JSDOM(``, { pretendToBeVisual: true })).window;

        assert.strictEqual(document.hidden, false);
        assert.strictEqual(document.visibilityState, "visible");
      });

      it("document should call rAF", { async: true }, context => {
        const { window } = new JSDOM(``, { pretendToBeVisual: true });

        window.requestAnimationFrame(() => {
          context.done();
        });

        // Further functionality tests are in web platform tests
      });
    });
  });
});
Exemple #3
0
describe("browser/index", () => {
  specify("notfound_getelementsbyclassname", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    const p = doc.createElement("p");
    p.className = "unknown";
    body.appendChild(p);
    const elements = doc.getElementsByClassName("first-p");
    assert.equal(elements.length, 0, "no results");
  });

  specify("basic_getelementsbyclassname", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    const p = doc.createElement("p");
    p.className = "first-p";
    body.appendChild(p);
    const elements = doc.getElementsByClassName("first-p");
    assert.equal(elements.item(0), p, "p and first-p");
  });

  specify("multiple_getelementsbyclassname", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    const p = doc.createElement("p");
    p.className = "first-p second third";
    body.appendChild(p);
    const first = doc.getElementsByClassName("first-p").item(0);
    const second = doc.getElementsByClassName("second").item(0);
    const third = doc.getElementsByClassName("third").item(0);
    assert.equal(first, p, "p and first-p");
    assert.equal(second, p, "p and second");
    assert.equal(third, p, "p and third");
  });

  specify("testclassnameworksasexpected", () => {
    const doc = (new JSDOM()).window.document;

    const p = doc.createElement("p");
    p.setAttribute("class", "first-p");
    assert.equal(p.className, "first-p", "class attribute is same as className");
    p.className += " second";
    assert.equal(p.className, "first-p second", "className getter/setter");
  });

  specify("basic_getelementbyid", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    const p = doc.createElement("p");
    p.id = "theid";
    body.appendChild(p);
    const element = doc.getElementById("theid");
    assert.equal(element, p, "p and #theid");
  });

  specify("nonexistant_getelementbyid", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    const p = doc.createElement("p");
    p.id = "theid";
    body.appendChild(p);
    const element = doc.getElementById("non-existant-id");
    assert.equal(element, null, "p and #theid");
  });

  specify("remove_nonexistantattribute", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    assert.doesNotThrow(() => body.removeAttribute("non-existant"), "setValue_throws_NO_MODIFICATION_ERR");
  });

  specify("render_singletag", () => {
    const doc = (new JSDOM()).window.document;

    const p = doc.createElement("p");
    const img = doc.createElement("img");
    p.appendChild(img);
    const out = p.outerHTML;
    assert.equal(out.match(/<\/img>/), null, "end tag not included in output");
  });

  specify("render_specialchars", () => {
    const doc = (new JSDOM()).window.document;

    const p = doc.createElement("p");
    const specials = "\"<>&\xA0";
    const escapedSpecials = "\"&lt;&gt;&amp;&nbsp;";
    p.setAttribute("specials", specials);
    p.innerHTML = escapedSpecials;
    const pp = doc.createElement("p");
    pp.appendChild(p);
    assert.equal(pp.innerHTML, `<p specials="&quot;<>&amp;&nbsp;">"&lt;&gt;&amp;&nbsp;</p>`);
  });

  specify("parse_scripttags", () => {
    const doc = (new JSDOM()).window.document;
    const { head } = doc;

    const scriptHtml = `<script>alert("hello world")</script>`;
    head.innerHTML = scriptHtml;
    assert.equal(scriptHtml, head.innerHTML, "original and processed");
  });

  specify("parse_styletags", () => {
    const doc = (new JSDOM()).window.document;
    const { head } = doc;
    const styleHtml = `<style>body: {color: #fff;}</style>`;
    head.innerHTML = styleHtml;
    assert.equal(styleHtml, head.innerHTML, "original and processed");
  });

  specify("parse_doublespacetags", () => {
    const doc = (new JSDOM()).window.document;
    const html = `<html><body  class="testing" /></html>`;
    assert.doesNotThrow(() => doc.write(html), "setValue_throws_INVALID_CHARACTER_ERR");
  });

  specify("serialize_styleattribute", () => {
    const doc = (new JSDOM()).window.document;

    doc.documentElement.style.color = "black";
    doc.documentElement.style.backgroundColor = "white";
    assert.equal(
      doc.documentElement.outerHTML,
      `<html style="color: black; background-color: white;"><head></head><body></body></html>`
    );
  });

  specify("innerhtml_removeallchildren", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.appendChild(doc.createElement("p"));
    body.innerHTML = "";
    assert.equal(body.childNodes.length, 0, "still has children");
  });

  specify("innerhtml_null", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.appendChild(doc.createElement("p"));
    body.innerHTML = null;
    assert.equal(body.childNodes.length, 0, "still has children");
  });

  specify("parse_doctype_containing_newline", () => {
    const html = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n
             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n<html></html>`;

    const doc = (new JSDOM(html)).window.document;

    assert.ok(doc.doctype, "doctype should not be falsy");
  });

  specify("basic_nodelist_indexOf", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    const p = doc.createElement("p");
    body.appendChild(p);
    const div = doc.createElement("div");
    body.appendChild(div);
    const span = doc.createElement("span");
    body.appendChild(span);
    const index = Array.prototype.indexOf.call(body.childNodes, span);
    assert.equal(index, 2, "indexOf 'span' in childNodes");
  });

  specify("nonexistant_nodelist_indexOf", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    const p = doc.createElement("p");
    body.appendChild(p);
    const div = doc.createElement("div");
    p.appendChild(div);
    const index = Array.prototype.indexOf.call(body.childNodes, div);
    assert.equal(index, -1, "indexOf 'div' in childNodes");
  });

  specify("input_fires_click_event", () => {
    const doc = (new JSDOM(`
      <html><head></head><body>
        <input type="checkbox" id="check" value="check" />
      </body>
    `)).window.document;

    const checkbox = doc.getElementById("check");

    checkbox.addEventListener("click", event => {
      assert.equal(event.type, "click", "event type");
      assert.equal(event.target, checkbox, "event type");
    });

    checkbox.click();
  });

  specify("basic_radio_selected", () => {
    const doc = (new JSDOM(`<html><head></head><body>
        <input type="radio" id="rad0" value="rad0" name="radioGroup0" />
        <input type="radio" id="rad1" value="rad1" name="radioGroup0" checked="checked" />
        <input type="radio" id="rad2" value="rad2" name="radioGroup1" />
      </body>
    `)).window.document;

    const radio0 = doc.getElementById("rad0");
    const radio1 = doc.getElementById("rad1");
    const radio2 = doc.getElementById("rad2");

    assert.ok(!radio0.checked, "radio not checked");
    assert.ok(radio1.checked, "radio checked");
    assert.ok(!radio2.checked, "radio not checked");

    radio2.click();
    radio0.click();
    assert.ok(radio0.checked, "radio checked");
    assert.ok(!radio1.checked, "radio not checked");
    assert.ok(radio2.checked, "radio checked");

    radio1.click();
    assert.ok(!radio0.checked, "radio not checked");
    assert.ok(radio1.checked, "radio checked");
    assert.ok(radio2.checked, "radio checked");
  });

  specify("radio_no_click_deselected", () => {
    const doc = (new JSDOM(`
      <html><head></head><body>
        <input type="radio" id="rad0" value="rad0" name="radioGroup0" />
      </body>
    `)).window.document;

    const radio0 = doc.getElementById("rad0");

    radio0.click();
    assert.ok(radio0.checked, "radio checked");

    radio0.click();
    assert.ok(radio0.checked, "radio checked");
  });

  specify("select_set_value_updates_value", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.innerHTML = `
      <select id="selectElement">
        <option value="x">x</option><option value="y">y</option>
      </select>
    `;

    const select = doc.getElementById("selectElement");

    select.value = "x";
    assert.equal(select.value, "x", "select element value");

    select.value = "y";
    assert.equal(select.value, "y", "select element selectedIndex");
  });

  specify("select_set_value_updates_selectedIndex", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.innerHTML = `
      <select id="selectElement"> +
        <option value="x">x</option><option value="y">y</option>
      </select>
    `;

    const select = doc.getElementById("selectElement");

    select.value = "x";
    assert.equal(select.selectedIndex, 0, "select element selectedIndex");

    select.value = "y";
    assert.equal(select.selectedIndex, 1, "select element selectedIndex");
  });

  specify("select_set_value_updates_option_selected", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.innerHTML = `
      <select id="selectElement">
        <option id="optX" value="x">x</option><option id="optY" value="y">y</option>
      </select>
    `;

    const select = doc.getElementById("selectElement");
    const option0 = doc.getElementById("optX");
    const option1 = doc.getElementById("optY");

    select.value = "x";
    assert.ok(option0.selected, "option element selected");

    select.value = "y";
    assert.ok(option1.selected, "option element selected");
  });

  specify("select_set_selectedIndex_updates_value", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.innerHTML = `
      <select id="selectElement">
        <option value="x">x</option><option value="y">y</option>
      </select>
    `;

    const select = doc.getElementById("selectElement");

    select.selectedIndex = 0;
    assert.equal(select.value, "x", "select element selectedIndex");

    select.selectedIndex = 1;
    assert.equal(select.value, "y", "select element selectedIndex");
  });

  specify("select_set_selectedIndex_updates_selectedIndex", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.innerHTML = `
      <select id="selectElement">
        <option value="x">x</option><option value="y">y</option>
      </select>
    `;

    const select = doc.getElementById("selectElement");

    select.selectedIndex = 0;
    assert.equal(select.selectedIndex, 0, "select element selectedIndex");

    select.selectedIndex = 1;
    assert.equal(select.selectedIndex, 1, "select element selectedIndex");
  });

  specify("select_set_selectedIndex_updates_option_selected", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.innerHTML = `
      <select id="selectElement">
        <option id="optX" value="x">x</option><option id="optY" value="y">y</option>
      </select>
    `;

    const select = doc.getElementById("selectElement");
    const option0 = doc.getElementById("optX");
    const option1 = doc.getElementById("optY");

    select.selectedIndex = 0;
    assert.ok(option0.selected, "option element selected");
    assert.ok(!option1.selected, "option element selected");

    select.selectedIndex = 1;
    assert.ok(option1.selected, "option element selected");
    assert.ok(!option0.selected, "option element selected");
  });

  specify("select_set_option_selected_updates_value", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.innerHTML = `
      <select id="selectElement">
        <option id="optX" value="x">x</option><option id="optY" value="y">y</option>
      </select>
    `;

    const select = doc.getElementById("selectElement");
    const option0 = doc.getElementById("optX");
    const option1 = doc.getElementById("optY");

    select.selectedIndex = 0;
    option0.selected = true;
    assert.equal(select.value, "x", "select element value");

    option1.selected = true;
    assert.equal(select.value, "y", "select element value");
  });

  specify("select_set_option_selected_updates_selectedIndex", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.innerHTML = `
      <select id="selectElement">
        <option id="optX" value="x">x</option><option id="optY" value="y">y</option>
      </select>
    `;

    const select = doc.getElementById("selectElement");
    const option0 = doc.getElementById("optX");
    const option1 = doc.getElementById("optY");

    option0.selected = true;
    assert.equal(select.selectedIndex, 0, "select element selectedIndex");

    option1.selected = true;
    assert.equal(select.selectedIndex, 1, "select element selectedIndex");
  });

  specify("select_set_option_selected_updates_option_selected", () => {
    const doc = (new JSDOM()).window.document;
    const { body } = doc;

    body.innerHTML = `
      <select id="selectElement">
        <option id="optX" value="x">x</option><option id="optY" value="y">y</option>
      </select>
    `;

    const option0 = doc.getElementById("optX");
    const option1 = doc.getElementById("optY");

    option0.selected = true;
    assert.ok(option0.selected, "option element selected");
    assert.ok(!option1.selected, "option element selected");

    option1.selected = true;
    assert.ok(option1.selected, "option element selected");
    assert.ok(!option0.selected, "option element selected");
  });
});
Exemple #4
0
describe("xhr-file-urls", { skipIfBrowser: true }, () => {
  specify("Getting a file URL should work (from the same file URL)", t => {
    // From https://github.com/tmpvar/jsdom/pull/1180
    const { window } = new JSDOM(``, { url: toFileUrl(__filename) });

    const xhr = new window.XMLHttpRequest();
    xhr.onload = () => {
      assert.strictEqual(xhr.responseText, fs.readFileSync(__filename, { encoding: "utf-8" }));
      t.done();
    };

    xhr.open("GET", toFileUrl(__filename), true);
    xhr.send();
  }, {
    async: true
  });

  specify(
    "Getting a file URL should have valid default response without setting responseType",
    t => {
      // From https://github.com/tmpvar/jsdom/pull/1183
      const { window } = new JSDOM(``, { url: toFileUrl(__filename) });

      const xhr = new window.XMLHttpRequest();
      xhr.onload = () => {
        assert.strictEqual(xhr.response, fs.readFileSync(__filename, { encoding: "utf-8" }));
        t.done();
      };

      xhr.open("GET", toFileUrl(__filename), true);
      xhr.send();
    },
    {
      async: true
    }
  );

  specify("Getting a file URL should not throw for getResponseHeader", t => {
    // From https://github.com/tmpvar/jsdom/pull/1180
    const { window } = new JSDOM(``, { url: toFileUrl(__filename) });

    const xhr = new window.XMLHttpRequest();
    xhr.onload = () => {
      assert.doesNotThrow(() => {
        assert.strictEqual(xhr.getResponseHeader("Blahblahblah"), null);
      });
      t.done();
    };

    xhr.open("GET", toFileUrl(__filename), true);
    xhr.send();
  }, {
    async: true
  });

  specify("Getting a file URL should not throw for getAllResponseHeaders", t => {
    // From https://github.com/tmpvar/jsdom/pull/1183
    const { window } = new JSDOM(``, { url: toFileUrl(__filename) });

    const xhr = new window.XMLHttpRequest();
    xhr.onload = () => {
      assert.doesNotThrow(() => {
        assert.strictEqual(xhr.getAllResponseHeaders(), "");
      });
      t.done();
    };

    xhr.open("GET", toFileUrl(__filename), true);
    xhr.send();
  }, {
    async: true
  });
});
describe("Local tests in Web Platform Test format (to-upstream)", () => {
  [
    "console/methods.html",
    "dom/attributes-are-not-nodes.html",
    "dom/collections/HTMLCollection-iterator.html",
    "dom/events/AddEventListenerOptions-once.html",
    "dom/events/EventTarget-add-remove-listener.html",
    "dom/events/EventTarget-prototype-constructor.html",
    "dom/events/EventTarget-this-of-listener.html",
    "dom/nodes/Document-createCDATASection.html",
    "dom/nodes/Document-createCDATASection.xhtml",
    "dom/nodes/Element-hasAttribute.html",
    "dom/nodes/Element-removeAttribute.html",
    "dom/nodes/Element-setAttribute.html",
    "dom/nodes/Element-tagName.html",
    "dom/nodes/attributes-namednodemap.html",
    "dom/nodes/getElementsByClassName-32.html",
    "dom/nodes/getElementsByClassName-empty-set.html",
    "dom/nodes/getElementsByClassName-whitespace-class-names.html",
    "dom/nodes/Node-cloneNode-input.html",
    "dom/nodes/Node-cloneNode-svg.html",
    "dom/nodes/Node-isEqualNode.html",
    "dom/nodes/Node-mutation-adoptNode.html",
    "dom/nodes/ParentNode-querySelector-escapes.html",
    "dom/nodes/Text-wholeText.html",
    "domparsing/DOMParser-dont-upstream.html",
    "domparsing/insert-adjacent.html",
    "FileAPI/blob/Blob-isClosed.html",
    "FileAPI/file/File-lastModified.html",
    "encoding/meta/meta-charset-no-quotes.html",
    "encoding/meta/meta-charset-simple-quotes.html",
    "encoding/meta/meta-charset.html",
    "encoding/meta/meta-http-equiv-no-quotes.html",
    "encoding/meta/meta-http-equiv-reverse.html",
    "encoding/meta/meta-http-equiv-simple-quotes.html",
    "encoding/meta/meta-http-equiv.html",
    "encoding/meta/no-meta.html",
    "html/browsers/windows/nested-browsing-contexts/iframe-referrer.html",
    "html/dom/elements/elements-in-the-dom/click-in-progress-flag.html",
    "html/editing/activation/click-bail-on-disabled.html",
    "html/editing/focus/focus-management/active-element.html",
    "html/editing/focus/focus-management/focus-on-all-elements.html",
    "html/named-access-on-window/basics.html",
    "html/named-access-on-window/changing.html",
    "html/named-access-on-window/doc-no-window.html",
    "html/named-access-on-window/existing-prop.html",
    "html/named-access-on-window/multi-match.html",
    "html/named-access-on-window/nested-context.html",
    "html/named-access-on-window/only-name.html",
    "html/named-access-on-window/removing.html",
    "html/obsolete/requirements-for-implementations/other-elements-attributes-and-apis/applets.html",
    "html/semantics/document-metadata/the-link-element/stylesheet-appropriate-time-to-obtain.html",
    "html/semantics/forms/resetting-a-form/reset-form-2.html",
    "html/semantics/forms/the-button-element/button-click-submits.html",
    "html/semantics/forms/the-button-element/button-type.html",
    "html/semantics/forms/the-form-element/form-action.html",
    "html/semantics/forms/the-input-element/checkbox-click-events.html",
    "html/semantics/forms/the-input-element/disabled-checkbox.html",
    "html/semantics/forms/the-input-element/radio-input-cancel.html",
    "html/semantics/forms/the-label-element/proxy-click-to-associated-element.html",
    "html/semantics/forms/the-option-element/option-index.html",
    "html/semantics/forms/the-select-element/select-multiple.html",
    "html/semantics/forms/the-textarea-element/select.html",
    "html/semantics/forms/the-textarea-element/set-value-reset-selection.html",
    "html/semantics/forms/the-textarea-element/setRangeText.html",
    "html/semantics/forms/the-textarea-element/setSelectionRange.html",
    "html/semantics/forms/the-textarea-element/value-defaultValue-textContent.html",
    "html/semantics/links/links-created-by-a-and-area-elements/html-hyperlink-element-utils-href.html",
    "html/semantics/scripting-1/the-script-element/script-languages-dont-upstream.html",
    "html/semantics/scripting-1/the-script-element/changing-src.html",
    "html/semantics/tabular-data/the-table-element/insertRow-method-03.html",
    "html/semantics/tabular-data/the-table-element/parentless-props.html",
    "html/syntax/parsing/foreign_content_dom_properties.html",
    "html/webappapis/events/event-handler-processing-algorithm-non-booleans.html",
    "html/webappapis/timers/arguments.html",
    "html/webappapis/timers/errors.html",
    "html/webappapis/timers/settimeout-setinterval-handles.html",
    "XMLHttpRequest/formdata-constructor.html",
    "XMLHttpRequest/thrown-error-in-events.html",
    "XMLHttpRequest/send-authentication-cors-post.htm"
  ]
  .forEach(runWebPlatformTest);
});
describe('recorder reducer', () => {
  describe('recorderStart actions', () => {
    it('changes "active" to true and "command" to "start"', () => {
      const newState = {active: true, command: 'start', stream: null, blobs: []}
      expect(reducer(undefined, recorderStart())).to.eql(newState)
    })
  })

  describe('recorderStop actions', () => {
    it('changes "active" to false and "command" to "stop"', () => {
      const origState = {active: true, command: 'start', stream: null, blobs: []}
      const newState = {active: false, command: 'stop', stream: null, blobs: []}
      expect(reducer(origState, recorderStop())).to.eql(newState)
    })
  })

  describe('recorderPause actions', () => {
    it('changes "active" to false and "command" to "pause"', () => {
      const origState = {active: true, command: 'start', stream: null, blobs: []}
      const newState = {active: false, command: 'pause', stream: null, blobs: []}
      expect(reducer(origState, recorderPause())).to.eql(newState)
    })
  })

  describe('recorderResume actions', () => {
    it('changes "active" to true and "command" to "resume"', () => {
      const origState = {active: false, command: 'pause', stream: null, blobs: []}
      const newState = {active: true, command: 'resume', stream: null, blobs: []}
      expect(reducer(origState, recorderResume())).to.eql(newState)
    })
  })

  describe('recorderOnStop actions', () => {
    it('adds a new blob object to the "blobs" array', () => {
      expect(reducer(undefined, recorderOnStop('abcd')).blobs).to.eql(['abcd'])
    })
  })

  describe('recorderGotStream actions', () => {
    it('defines the "stream" object', () => {
      expect(reducer(undefined, recorderGotStream('abcd')).stream).to.equal('abcd')
    })
  })

  describe('recorderUnmount', () => {
    it('resets the state', () => {
      const origState = {active: true, command: 'resume', stream: 'stream', blobs: [1, 2, 3]}
      expect(reducer(origState, recorderUnmount())).to.eql({active: false, command: 'none', stream: null, blobs: []})
    })
  })
})
Exemple #7
0
describe("jsdom/miscellaneous", () => {
  specify("build_window", () => {
    const window = jsdom.jsdom().defaultView;
    assert.notEqual(window, null, "window should not be null");
    assert.notEqual(window.document, null, "window.document should not be null");
  });

  specify("jsdom_takes_html", () => {
    const document = jsdom.jsdom(`<a id="test" href="#test">`);
    assert.equal(
      document.getElementById("test").getAttribute("href"),
      "#test",
      "Passing html into jsdom() should populate the resulting doc"
    );
  });

  specify("jsdom_empty_html", () => {
    const emptyDoc = jsdom.jsdom("");
    const blankDoc = jsdom.jsdom(" ");
    assert.equal(
      emptyDoc.innerHTML,
      blankDoc.innerHTML,
      "Passing blank and empty strings into jsdom() result in the same html"
    );
  });

  specify("jsdom_method_creates_default_document", () => {
    const doc = jsdom.jsdom();
    assert.equal(doc.documentElement.nodeName, "HTML", "Calling jsdom.jsdom() should automatically populate the doc");
  });

  specify("jsdom_method_works_with_referrer_under_document", () => {
    const doc = jsdom.jsdom(undefined, {
      document: {
        referrer: "http://example.com"
      }
    });

    assert.equal(doc.referrer, "http://example.com");
  });

  specify("jquerify_url", { async: true }, t => {
    const jQueryUrl = "http://code.jquery.com/jquery-1.4.4.min.js";
    jsdom.jQueryify(tmpWindow(), jQueryUrl, (window, jQuery) => {
      testFunction(assert, window, jQuery, true);
      t.done();
    });
  });

  specify("jquerify_invalid", { async: true }, t => {
    jsdom.jQueryify(jsdom.jsdom("", { url: "http://www.example.org" }).defaultView, 1, (window, jQuery) => {
      assert.strictEqual(window.jQuery, undefined);
      assert.strictEqual(jQuery, undefined);
      t.done();
    });
  });

  // This is in response to issue # 280 - scripts don't load over https.
  // See: https://github.com/tmpvar/jsdom/issues/280
  //
  // When a transfer is done, HTTPS servers in the wild might emit "close", or
  // might emit "end".  Node"s HTTPS server always emits "end", so we need to
  // fake a "close" to test this fix.
  specify("env_with_https", { async: true }, t => {
    // Save the real https.request so we can restore it later.
    const oldRequest = https.request;

    // Mock response object
    const res = Object.create(EventEmitter.prototype);
    res.setEncoding = () => {};
    res.headers = {};

    // Monkey patch https.request so it emits "close" instead of "end.
    https.request = () => {
      // Mock the request object.
      const req = Object.create(EventEmitter.prototype);
      req.setHeader = () => {};
      req.end = () => {};
      process.nextTick(() => {
        req.emit("response", res);
        process.nextTick(() => {
          res.emit("data", "window.attachedHere = 123");
          res.emit("close");
        });
      });
      return req;
    };

    jsdom.env({
      html: `<a href="/path/to/hello">World</a>`,
      // The script url doesn"t matter as long as its https, since our mocked
      // request doens"t actually fetch anything.
      scripts: "https://doesntmatter.com/script.js",
      strictSSL: false,
      done(errors, window) {
        if (errors) {
          assert.ok(false, errors.message);
        } else {
          assert.notEqual(window.location, null, "window.location should not be null");
          assert.equal(window.attachedHere, 123, "script should execute on our window");
          assert.equal(window.document.getElementsByTagName("a").item(0).innerHTML, "World", "anchor text");
        }
        https.request = oldRequest;
        t.done();
      }
    });
  });

  specify("appendChild_to_document_with_existing_documentElement", () => {
    function t() {
      try {
        const doc = jsdom.jsdom();
        doc.appendChild(doc.createElement("html"));
      } catch (e) {
        assert.equal(e.code, 3, "Should throw HIERARCHY_ERR");
        throw e;
      }
    }
    assert.throws(t);
  });

  specify("importNode", () => {
    assert.doesNotThrow(() => {
      const doc1 = jsdom.jsdom(`<html><body><h1 id="headline">Hello <span id="world">World</span></h1></body></html>`);
      const doc2 = jsdom.jsdom();
      doc2.body.appendChild(doc2.importNode(doc1.getElementById("headline"), true));
      doc2.getElementById("world").className = "foo";
    });
  });

  specify("window_is_augmented_with_dom_features", () => {
    const document = jsdom.jsdom();
    const window = document.defaultView;
    assert.notEqual(window.Element, null, "window.Element should not be null");
  });

  specify("url_resolution", () => {
    const html = `
  <html>
    <head></head>
    <body>
      <a href="http://example.com" id="link1">link1</a>
      <a href="/local.html" id="link2">link2</a>
      <a href="local.html" id="link3">link3</a>
      <a href="../../local.html" id="link4">link4</a>
      <a href="#here" id="link5">link5</a>
      <a href="//example.com/protocol/avoidance.html" id="link6">protocol</a>
    </body>\
  </html>`;

    function testLocal() {
      const url = "file:///path/to/docroot/index.html";
      const doc = jsdom.jsdom(html, { url });
      assert.equal(
        doc.getElementById("link1").href,
        "http://example.com/",
        "Absolute URL should be left alone except for possible trailing slash"
      );
      assert.equal(
        doc.getElementById("link2").href,
        "file:///local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link3").href,
        "file:///path/to/docroot/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link4").href,
        "file:///path/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link5").href,
        "file:///path/to/docroot/index.html#here",
        "Relative URL should be resolved"
      );
      // test.equal(
      //   doc.getElementById("link6").href,
      //   "//prototol/avoidance.html",
      //   "Protocol-less URL should be resolved"
      // );
    }

    function testRemote() {
      const url = "http://example.com/path/to/docroot/index.html";
      const doc = jsdom.jsdom(html, { url });
      assert.equal(
        doc.getElementById("link1").href,
        "http://example.com/",
        "Absolute URL should be left alone except for possible trailing slash"
      );
      assert.equal(
        doc.getElementById("link2").href,
        "http://example.com/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link3").href,
        "http://example.com/path/to/docroot/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link4").href,
        "http://example.com/path/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link5").href,
        "http://example.com/path/to/docroot/index.html#here",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link6").href,
        "http://example.com/protocol/avoidance.html",
        "Relative URL should be resolved"
      );
    }

    function testBase() {
      const url = "about:blank";
      const doc = jsdom.jsdom(html, { url });
      const base = doc.createElement("base");
      base.href = "http://example.com/path/to/docroot/index.html";
      doc.getElementsByTagName("head").item(0).appendChild(base);
      assert.equal(
        doc.getElementById("link1").href,
        "http://example.com/",
        "Absolute URL should be left alone except for possible trailing slash"
      );
      assert.equal(
        doc.getElementById("link2").href,
        "http://example.com/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link3").href,
        "http://example.com/path/to/docroot/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link4").href,
        "http://example.com/path/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link5").href,
        "http://example.com/path/to/docroot/index.html#here",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link6").href,
        "http://example.com/protocol/avoidance.html",
        "Relative URL should be resolved"
      );
    }

    testLocal();
    testRemote();
    testBase();
  });

  specify("numeric_values", () => {
    const html = `<html><body><td data-year="2011" data-month="0" data-day="9">
                  <a href="#" class=" ">9</a>
                </td></body></html>`;
    const document = jsdom.jsdom(html);
    const a = document.body.children.item(0);

    a.innerHTML = 9;
    a.setAttribute("id", 123);
    assert.strictEqual(a.innerHTML, "9", "Element stringify");
    assert.strictEqual(a.getAttributeNode("id").nodeValue, "123", "Attribute stringify");
  });

  specify("mutation_events", () => {
    const document = jsdom.jsdom();
    let created = "";
    let removed = "";
    document.addEventListener("DOMNodeInserted", ev => {
      created += ev.target.tagName;
    });
    document.addEventListener("DOMNodeRemoved", ev => {
      removed += ev.target.tagName;
    });
    const h1 = document.createElement("h1");
    const h2 = document.createElement("h2");
    const h3 = document.createElement("h3");

    document.body.appendChild(h2);
    document.body.insertBefore(h1, h2);
    document.body.insertBefore(h3, null);
    assert.strictEqual("H2H1H3", created, "an event should be dispatched for each created element");

    document.body.removeChild(h1);
    document.body.insertBefore(h3, h2);
    assert.strictEqual("H1H3", removed, "an event should be dispatched for each removed element");

    let text = h2.innerHTML = "foo";
    h2.addEventListener("DOMCharacterDataModified", ev => {
      text = ev.target.nodeValue;
    });
    h2.firstChild.nodeValue = "bar";
    assert.equal(h2.innerHTML, text, "ChactaterData changes should be captured");

    let event;
    h2.setAttribute("class", "foo");
    document.addEventListener("DOMAttrModified", ev => {
      event = ev;
    });
    h2.setAttribute("class", "bar");
    assert.ok(event, "Changing an attribute should trigger DOMAttrModified");
    assert.equal(event.attrName, "class", "attrName should be class");
    assert.equal(event.prevValue, "foo", "prevValue should be foo");
    assert.equal(event.newValue, "bar", "newValue should be bar");

    event = false;
    h2.setAttribute("class", "bar");
    assert.ok(!event, "Setting the same value again should not trigger an event");

    h2.removeAttribute("class");
    assert.ok(event, "Removing an attribute should trigger DOMAttrModified");
    assert.equal(event.attrName, "class", "attrName should be class");
    assert.equal(event.prevValue, "bar", "prevValue should be bar");
  });

  specify("DomSubtreeModifiedEvents", () => {
    const document = jsdom.jsdom();
    let firedAfterAddedChild = false;
    let firedAfterAddedTextNode = false;
    let firedAfterAddingAttr = false;
    let firedAfterChangingAttr = false;
    let firedAfterRemovedAttr = false;

    document.addEventListener("DOMSubtreeModified", () => {
      firedAfterAddedChild = true;
    });
    const div = document.createElement("div");
    document.body.appendChild(div);
    assert.ok(firedAfterAddedChild, "DOMSubtreeModified event should be fired for each created element");

    document.addEventListener("DOMSubtreeModified", () => {
      firedAfterAddedTextNode = true;
    });
    const textNode = document.createTextNode("text node test");
    document.getElementsByTagName("div")[0].appendChild(textNode);
    assert.ok(firedAfterAddedTextNode, "DOMSubtreeModified event should be fired when texnode value changed");
    document.addEventListener("DOMSubtreeModified", () => {
      firedAfterAddingAttr = true;
    });
    document.getElementsByTagName("div")[0].setAttribute("class", "test-class");
    assert.ok(firedAfterAddingAttr, "DOMSubtreeModified event should be fired when attribute added");

    document.addEventListener("DOMSubtreeModified", () => {
      firedAfterChangingAttr = true;
    });
    document.getElementsByTagName("div")[0].setAttribute("class", "test-class-2");
    assert.ok(firedAfterChangingAttr, "DOMSubtreeModified event should be fired when attribute value changed");

    firedAfterChangingAttr = false;
    document.getElementsByTagName("div")[0].setAttribute("class", "test-class-2");
    assert.ok(
      firedAfterChangingAttr === false,
      "DOMSubtreeModified not be fired when new attribute value same as old one"
    );

    document.addEventListener("DOMSubtreeModified", () => {
      firedAfterRemovedAttr = true;
    });
    document.getElementsByTagName("div")[0].removeAttribute("class");
    assert.ok(firedAfterRemovedAttr, "DOMSubtreeModified event should be fired when attribute removed");

    firedAfterRemovedAttr = false;
    document.getElementsByTagName("div")[0].removeAttribute("class");
    assert.ok(
      firedAfterRemovedAttr === false,
      "DOMSubtreeModified not be fired when try to remove attribute does not exists"
    );
  });

  specify("childNodes_updates_on_insertChild", () => {
    const window = jsdom.jsdom("").defaultView;
    const div = window.document.createElement("div");
    let text = window.document.createTextNode("bar");
    div.appendChild(text);
    assert.strictEqual(text, div.childNodes[0],
               "childNodes NodeList should update after appendChild");

    text = window.document.createTextNode("bar");
    div.insertBefore(text, null);
    assert.strictEqual(text, div.childNodes[1],
               "childNodes NodeList should update after insertBefore");
  });

  specify("option_set_selected", () => {
    const window = jsdom.jsdom("").defaultView;
    const select = window.document.createElement("select");

    const option0 = window.document.createElement("option");
    select.appendChild(option0);
    option0.setAttribute("selected", "selected");

    const optgroup = window.document.createElement("optgroup");
    select.appendChild(optgroup);
    const option1 = window.document.createElement("option");
    optgroup.appendChild(option1);

    assert.strictEqual(true, option0.selected, "initially selected");
    assert.strictEqual(false, option1.selected, "initially not selected");
    assert.strictEqual(option1, select.options[1], "options should include options inside optgroup");

    option1.defaultSelected = true;
    assert.strictEqual(false, option0.selected, "selecting other option should deselect this");
    assert.strictEqual(true, option0.defaultSelected, "default should not change");
    assert.strictEqual(true, option1.selected, "selected changes when defaultSelected changes");
    assert.strictEqual(true, option1.defaultSelected, "I just set this");

    option0.defaultSelected = false;
    option0.selected = true;
    assert.strictEqual(true, option0.selected, "I just set this");
    assert.strictEqual(false, option0.defaultSelected, "selected does not set default");
    assert.strictEqual(false, option1.selected, "should deselect others");
    assert.strictEqual(true, option1.defaultSelected, "unchanged");
  });

  specify("children_should_be_available_right_after_document_creation", () => {
    const doc = jsdom.jsdom("<html><body><div></div></body></html>");
    assert.ok((doc.body.children[0] !== undefined), "there should be a body, and it should have a child");
  });

  specify("children_should_be_available_right_after_document_creation_scripts", () => {
    const html = `<html><body>
      <script type="text/javascript">
        const h = document.createElement("div");
        h.innerHTML = '<div style="opacity:0.8"></div>';
        window.myNode = h.childNodes[0];
      </script>
    </body></html>`;

    const window = jsdom.jsdom(html).defaultView;
    assert.ok(window.myNode.nodeType);
  });

  specify("fix_for_issue_221", () => {
    const html = "<html><head></head><body></body></html>";
    const document = jsdom.jsdom(html);
    const div = document.createElement("div");
    document.body.appendChild(div);
    div.appendChild(document.createTextNode("hello world"));
    assert.strictEqual(div.childNodes[0].nodeValue, "hello world",
               "Nodelist children should be populated immediately");
  });

  specify("parsing_and_serializing_entities", () => {
    const html = `<html><body><a href="http://example.com/?a=b&amp;c=d">&lt;&aelig;&#x263a;foo</a>`;
    const document = jsdom.jsdom(html);
    const anchor = document.getElementsByTagName("a")[0];

    assert.strictEqual(anchor.getAttribute("href"), "http://example.com/?a=b&c=d",
                     "href attribute value should be deentitified");

    assert.strictEqual(anchor.firstChild.nodeValue, "<æ☺foo",
                     "nodeValue of text node should be deentitified");

    assert.ok(anchor.outerHTML.indexOf("http://example.com/?a=b&amp;c=d") !== -1,
            "outerHTML of anchor href should be entitified");

    assert.ok(anchor.innerHTML.indexOf("&lt;") === 0,
            "innerHTML of anchor should begin with &lt;");
  });

  specify("parsing_and_serializing_unknown_entities", () => {
    const html = "<html><body>&nowayjose;&#x263a;&#xblah;&#9q;</body></html>";
    const document = jsdom.jsdom(html);
    assert.strictEqual(document.body.firstChild.nodeValue, "&nowayjose;☺lah;	q;",
                     "Unknown and unparsable entities should be handled like a browser would");
    assert.strictEqual(document.body.innerHTML, "&amp;nowayjose;☺lah;	q;",
                     "Unknown and unparsable entities should be handled like a browser would");
  });

  specify("entities_in_script_should_be_left_alone", () => {
    const html = `<!DOCTYPE html><html><head></head><body><script>alert("&quot;");</script></body></html>`;
    const document = jsdom.jsdom(html);
    assert.strictEqual(document.body.innerHTML, `<script>alert("&quot;");</script>`);
    assert.strictEqual(document.body.firstChild.innerHTML, `alert("&quot;");`);
  });

  specify("document_title_and_entities", () => {
    const html = "<html><head><title>&lt;b&gt;Hello&lt;/b&gt;</title></head><body></body></html>";
    const document = jsdom.jsdom(html);

    assert.strictEqual(document.title, "<b>Hello</b>",
      `document.title should be the deentitified version of what was in
      the original HTML`
    );

    document.title = "<b>World</b>";
    assert.strictEqual(document.title, "<b>World</b>",
      `When document.title is set programmatically to something looking like
      HTML tags, then read again, it should have the exact same value, no
      entification should take place`
    );

    document.title = "&lt;b&gt;World&lt;/b&gt;";
    assert.strictEqual(document.title, "&lt;b&gt;World&lt;/b&gt;",
      `When document.title is set programmatically to something looking like
      HTML entities, then read again, it should have the exact same value,
      no deentification should take place`
    );
  });

  specify("setting_and_getting_textContent", () => {
    const html = `<html><head>\n<title>&lt;foo&gt;</title></head>
                  <body>Hello<span><span>, </span>world</span>!</body></html>`;
    const document = jsdom.jsdom(html);

    assert.strictEqual(document.textContent, null,
      "textContent of document should be null"
    );

    assert.strictEqual(document.head.textContent, "\n<foo>",
      "textContent of document.head should be the initial whitespace plus the textContent of the document title"
    );

    assert.strictEqual(
      document.body.textContent,
      "Hello, world!",
      "textContent of document.body should be the concatenation of the textContent values of its child nodes"
    );

    assert.strictEqual(
      document.createTextNode("&lt;b&gt;World&lt;/b&gt;").textContent,
      "&lt;b&gt;World&lt;/b&gt;",
      "textContent of programmatically created text node should be identical to its nodeValue"
    );

    assert.strictEqual(
      document.createComment("&lt;b&gt;World&lt;/b&gt;").textContent,
      "&lt;b&gt;World&lt;/b&gt;",
      "textContent of programmatically created comment node should be identical to its nodeValue"
    );

    const frag = document.createDocumentFragment();
    frag.appendChild(document.createTextNode("&lt;foo&gt;<b></b>"));
    frag.appendChild(document.createElement("div")).appendChild(document.createTextNode("&lt;foo&gt;<b></b>"));

    assert.strictEqual(
      frag.textContent,
      "&lt;foo&gt;<b></b>&lt;foo&gt;<b></b>",
      "textContent of programmatically created document fragment should be the concatenation " +
      "of the textContent values of its child nodes"
    );

    const div = document.createElement("div");
    div.innerHTML = "&amp;lt;b&amp;gt;\nWorld&amp;lt;/b&amp;gt;<span></span><span>" +
                    "<span></span></span><span>&amp;lt;b&amp;gt;World&amp;lt;/b&amp;gt;</span>";

    assert.strictEqual(div.textContent, "&lt;b&gt;\nWorld&lt;/b&gt;&lt;b&gt;W\orld&lt;/b&gt;",
      `textContent of complex programmatically created <div> should be the
      concatenation of the textContent values of its child nodes`
    );
  });

  specify("issues_230_259", () => {
    const instr = `<html><body style="color: #ffffff; foo: bar"></body></html>`;
    const doc = jsdom.jsdom(instr);
    assert.ok(jsdom.serializeDocument(doc).match(/0: *color/) === null);
  });

  // see: https://github.com/tmpvar/jsdom/issues/262
  specify("issue_262", () => {
    const document = jsdom.jsdom("<html><body></body></html>");
    const a = document.createElement("a");
    a.setAttribute("style", "color:blue");
    a.style.setProperty("color", "red");
    assert.equal(a.outerHTML.match(/style="/g).length, 1, "style attribute must not be serialized twice");
  });

  // see: https://github.com/tmpvar/jsdom/issues/267
  specify("issue_267", () => {
    const document = jsdom.jsdom("<html><body></body></html>");
    const a = document.createElement("a");
    a.style.width = "100%";
    assert.ok(a.getAttribute("style").match(/^\s*width\s*:\s*100%\s*;?\s*$/), "style attribute must contain width");
  });

  // Test inline event handlers set on the body.
  specify("test_body_event_handler_inline", { skipIfBrowser: true, async: true }, t => {
    // currently skipped in browsers because of an issue:
    // TODO: https://github.com/tmpvar/jsdom/issues/1379
    const html = `
      <html>
        <head>
          <script>
            function loader () {
              window.loader_called = true;
            }
          </script>
        </head>
        <body onload="loader()"></body>
      </html>`;
    const doc = jsdom.jsdom(html, { deferClose: true });
    const window = doc.defaultView;
    // In JSDOM, listeners registered with addEventListener are called before
    // "traditional" listeners, so listening for "load" will fire before our
    // inline listener.  This means we have to check the value on the next
    // tick.
    window.addEventListener("load", () => {
      process.nextTick(() => {
        assert.equal(window.loader_called, true);
        t.done();
      });
    });
    doc.close();
  });

  // Make sure traditional handlers on the body element set via script are
  // forwarded to the window.
  specify("test_body_event_handler_script", { async: true }, t => {
    const doc = jsdom.jsdom("<html><head></head><body></body></html>",
                          { deferClose: true });
    const window = doc.defaultView;
    assert.equal(window.onload, undefined);
    doc.body.onload = () => {
      t.done();
    };
    assert.notEqual(window.onload, undefined);
    doc.close();
  });

  // Test inline event handlers on a regular element.
  specify("test_element_inline_event_handler", () => {
    const doc = jsdom.jsdom(`
      <html>
        <head></head>
        <body>
          <div onclick="window.divClicked = true;"
               onmouseover="window.divMousedOver = true;"
               onmouseout="window.divCalledFrom = this.tagName;">
            <a></a>
          </div>
        </body>
      </html>`);

    const window = doc.defaultView;
    const div = doc.getElementsByTagName("div")[0];

    assert.equal(window.divClicked, undefined);
    assert.equal(window.divMousedOver, undefined);

    const click = doc.createEvent("MouseEvents");
    click.initEvent("click", false, false);
    div.dispatchEvent(click);
    assert.equal(window.divClicked, true);

    const mouseOver = doc.createEvent("MouseEvents");
    mouseOver.initEvent("mouseover", false, false);
    div.dispatchEvent(mouseOver);
    assert.equal(window.divMousedOver, true);

    const mouseOut = doc.createEvent("MouseEvents");
    mouseOut.initEvent("mouseout", false, false);
    div.dispatchEvent(mouseOut);
    assert.equal(window.divCalledFrom, "DIV");
  });

  // Test for issue 287 - element.onevent check doesn"t work
  // See: https://github.com/tmpvar/jsdom/issues/287
  specify("issue_287", () => {
    const doc = jsdom.jsdom();
    const elem = doc.createElement("form");
    elem.setAttribute("onsubmit", ";");
    assert.equal(typeof elem.onsubmit, "function");
  });

  specify("get_element_by_id", () => {
    const doc = jsdom.jsdom();
    const el = doc.createElement("div");
    el.setAttribute("id", "foo");
    assert.equal(doc.getElementById("foo"), null, "Element must not be found until it has been added to the DOM");

    doc.body.appendChild(el);
    assert.equal(doc.getElementById("foo"), el, "Element must be found after being added");

    el.id = "bar";
    assert.equal(doc.getElementById("foo"), null, "Element must not be found by its previous id");
    assert.equal(doc.getElementById("bar"), el, "Element must be found by its new id");

    el.setAttribute("id", "baz");
    assert.equal(doc.getElementById("bar"), null, "Element must not be found by its previous id");
    assert.equal(doc.getElementById("baz"), el, "Element must be found by its new id");

    el.getAttributeNode("id").nodeValue = "boo";
    assert.equal(doc.getElementById("boo"), el, "Element must be found by its new id");

    doc.body.removeChild(el);
    assert.equal(doc.getElementById(el.id), null, "Element must not be found after it has been removed");
  });

  specify("get_element_by_id_multi_id", () => {
    const doc = jsdom.jsdom();
    const div = doc.createElement("div");
    div.setAttribute("id", "foo");
    doc.body.appendChild(div);
    const span = doc.createElement("span");
    span.setAttribute("id", "foo");
    doc.body.appendChild(span);

    // now if we remove the second element, we should still find the first
    doc.body.removeChild(span);
    assert.equal(doc.getElementById("foo"), div, "Original div#foo must be found after removing invalid span#foo");
  });

  specify("issue_335_inline_event_handlers", () => {
    const doc = jsdom.jsdom(`<a onclick="somefunction()">call some function</a>`);
    const a = doc.getElementsByTagName("a").item(0);
    const onclick = a.getAttribute("onclick");
    assert.notEqual(onclick, null);
    assert.equal(onclick, "somefunction()");
    assert.ok(jsdom.serializeDocument(doc).indexOf("onclick") > -1);
  });

  specify("issue_338_internal_nodelist_props", () => {
    const doc = jsdom.jsdom();
    const props = Object.keys(doc.body.childNodes);
    assert.equal(props.length, 0, "Internal properties must not be enumerable");
  });

  specify("setting_and_getting_script_element_text", () => {
    const doc = jsdom.jsdom("<script></script>");
    const script = doc.getElementsByTagName("script")[0];
    assert.equal(script.text, "");
    script.text = "const x = 3;";
    assert.equal(script.text, "const x = 3;");
    script.text = "const y = 2;";
    assert.equal(script.text, "const y = 2;");
  });

  specify("issue_239_replace_causes_script_execution", { async: true }, t => {
    jsdom.env({
      html: `<script type="text/javascript">window.a = 1;/* remove me */ console.log("executed?")</script>`,
      done(errors, window) {
        window.document.write(jsdom.serializeDocument(window.document).replace("/* remove me */", ""));
        window.document.close();
        assert.equal(typeof window.a, "undefined");
        t.done();
      }
    });
  });

  specify("issue_355_on_events_should_not_execute_js_when_disabled", { async: true }, t => {
    const html = `<html><body onload="undefined()">something</body></html>`;

    jsdom.env(html, e => {
      assert.equal(e, null);
      t.done();
    });
  });

  specify("issue_361_textarea_value_property", () => {
    const doc = jsdom.jsdom(`<html><body><textarea id="mytextarea"></textarea></body></html>`);

    doc.getElementById("mytextarea").value = "<foo>";
    assert.equal(doc.getElementById("mytextarea").value, "<foo>");
  });

  specify("on_events_should_be_called_in_bubbling_phase", () => {
    const doc = jsdom.jsdom(`
      <html>
        <head></head>
        <body>
          <div onclick="window.divClicked = true;"
               onmouseover="window.divMousedOver = true;">
            <a></a>
          </div>
        </body>
      </html>`);

    const window = doc.defaultView;
    const a = doc.getElementsByTagName("a")[0];

    assert.equal(window.divClicked, undefined);
    assert.equal(window.divMousedOver, undefined);

    const click = doc.createEvent("MouseEvents");
    click.initEvent("click", true, false);
    a.dispatchEvent(click);
    assert.equal(window.divClicked, true);

    const mouseOver = doc.createEvent("MouseEvents");
    mouseOver.initEvent("mouseover", true, false);
    a.dispatchEvent(mouseOver);
    assert.equal(window.divMousedOver, true);
  });

  specify("css_classes_should_be_attached_to_dom", () => {
    const dom = jsdom.jsdom().defaultView;

    assert.notEqual(dom.StyleSheet, undefined);
    assert.notEqual(dom.MediaList, undefined);
    assert.notEqual(dom.CSSStyleSheet, undefined);
    assert.notEqual(dom.CSSRule, undefined);
    assert.notEqual(dom.CSSStyleRule, undefined);
    assert.notEqual(dom.CSSMediaRule, undefined);
    assert.notEqual(dom.CSSImportRule, undefined);
    assert.notEqual(dom.CSSStyleDeclaration, undefined);
  });

  specify("issue_530_async_load_events", { async: true }, t => {
    const doc = jsdom.jsdom("<html><head></head><body></body></html>");
    const window = doc.defaultView;

    // Add the load event after the document is already created; it shouldn"t
    // fire until nextTick. The test will fail (with a timeout) if it has
    // already fired.
    window.addEventListener("load", () => {
      assert.ok(true);
      t.done();
    });
  });

  specify("iframe_contents", () => {
    const document = jsdom.jsdom("<iframe></iframe>");
    const iframeDocument = document.querySelector("iframe").contentWindow.document;

    assert.equal(jsdom.serializeDocument(iframeDocument), "<html><head></head><body></body></html>");
    assert.ok(iframeDocument.documentElement);
    assert.ok(iframeDocument.head);
    assert.ok(iframeDocument.body);
  });

  specify("issue_935_document_tostring_returns_null", () => {
    const document = jsdom.jsdom();
    assert.equal(document.toString(), "[object HTMLDocument]");
  });

  specify("addmetatohead", () => {
    const window = jsdom.jsdom().defaultView;
    const meta = window.document.createElement("meta");
    window.document.getElementsByTagName("head").item(0).appendChild(meta);
    const elements = window.document.getElementsByTagName("head").item(0).childNodes;
    assert.strictEqual(elements.item(elements.length - 1), meta, "last element should be the new meta tag");
    assert.ok(jsdom.serializeDocument(window.document).indexOf("<meta>") > -1, "meta should have open tag");
    assert.strictEqual(
      jsdom.serializeDocument(window.document).indexOf("</meta>"),
      -1,
      "meta should not be stringified with a closing tag"
    );
  });

  specify("no global leak when using window.location.reload", () => {
    // https://github.com/tmpvar/jsdom/pull/1032
    assert.equal("errors" in global, false, "there should be no errors global before the call");
    const window = jsdom.jsdom().defaultView;
    window.location.reload();
    assert.equal("errors" in global, false, "there should be no errors global after the call");
  });

  specify("custom userAgent inherits to iframes", () => {
    // https://github.com/tmpvar/jsdom/issues/1344#issuecomment-175272389

    const window = jsdom.jsdom("<!DOCTYPE html><iframe></iframe>", { userAgent: "custom user agent" }).defaultView;

    assert.strictEqual(window.navigator.userAgent, "custom user agent");
    assert.strictEqual(window.frames[0].navigator.userAgent, "custom user agent");
  });

  // these tests require file system access or they start a http server
  describe("node specific tests", { skipIfBrowser: true }, () => {
    specify("fix_for_issue_172", () => {
      jsdom.env(`<html><body><script type="text/javascript"></script></body></html>`, [
        "file:" + path.resolve(__dirname, "../jquery-fixtures/jquery-1.6.2.js")
      ], () => {
        // ensure the callback gets called!
      });
    });

    specify("jquerify_file", { async: true }, t => {
      const jQueryFile = path.resolve(__dirname, "../jquery-fixtures/jquery-1.4.4.js");
      jsdom.jQueryify(tmpWindow(), toFileUrl(jQueryFile), (window, jQuery) => {
        testFunction(assert, window, jQuery, true);
        t.done();
      });
    });

    specify("jquerify_attribute_selector_gh_400", { async: true }, t => {
      const window = jsdom.jsdom().defaultView;

      jsdom.jQueryify(window, "file:" + path.resolve(__dirname, "../jquery-fixtures/jquery-1.11.0.js"), () => {
        assert.doesNotThrow(() => {
          window.$("body").append(`<html><body><div data-foo="bar"/><div data-baz="foo"/></body></html>`);
        });

        assert.equal(window.$("*[data-foo]").length, 1);
        t.done();
      });
    });

    specify("env_with_compression", { async: true }, t => {
      const server = http.createServer((req, res) => {
        switch (req.url) {
          case "/":
            const text = "window.attachedHere = 123";
            const buf = new Buffer(text, "utf-8");
            zlib.gzip(buf, (_, result) => {
              res.writeHead(200, { "Content-Length": result.length, "Content-Encoding": "gzip" });
              res.emit("data", result);
              res.end(result);
            });
            break;
        }
      });

      server.listen(8001, "127.0.0.1", () => {
        jsdom.env({
          html: `<a href="/path/to/hello">World</a>`,
          scripts: "http://127.0.0.1:8001",
          done(errors, window) {
            server.close();
            if (errors) {
              assert.ok(false, errors.message);
            } else {
              assert.notEqual(window.location, null, "window.location should not be null");
              assert.equal(window.attachedHere, 123, "script should execute on our window");
              assert.equal(window.document.getElementsByTagName("a").item(0).innerHTML, "World", "anchor text");
            }
            t.done();
          }
        });
      });
    });

    specify("env_with_features_and_external_resources", { async: true }, t => {
      jsdom.env(
        "http://backbonejs.org/examples/todos/index.html",
        {
          features: {
            FetchExternalResources: ["script", "frame", "link"],
            ProcessExternalResources: ["script", "frame", "link"],
            MutationEvents: "2.0",
            QuerySelector: false
          }
        },
        (error, window) => {
          assert.ifError(error);
          assert.equal(typeof window._, "function", "Underscore loaded");
          assert.equal(typeof window.$, "function", "jQuery loaded");
          t.done();
        }
      );
    });

    specify("ensure_scripts_can_be_disabled_via_options_features", { async: true }, t => {
      const html = `<html><head><script src="./files/hello.js"></script></head>
                 <body><span id="test">hello from html</span></body></html>`;

      const doc2 = jsdom.jsdom(html, {
        url: toFileUrl(__filename),
        features: {
          FetchExternalResources: ["script"],
          ProcessExternalResources: false
        }
      });
      setTimeout(() => {
        assert.equal(doc2.getElementById("test").innerHTML, "hello from html", "js should not be executed (doc2)");
        t.done();
      }, 100);
    });

    specify("ensure_scripts_can_be_executed_via_options_features", { async: true }, t => {
      const html = `<html><head><script src="./files/hello.js"></script></head>
                    <body><span id="test">hello from html</span></body></html>`;

      const doc = jsdom.jsdom(html, {
        url: toFileUrl(__filename),
        features: {
          FetchExternalResources: ["script"],
          ProcessExternalResources: ["script"]
        }
      });

      doc.defaultView.doCheck = () => {
        assert.equal(doc.getElementById("test").innerHTML, "hello from javascript");
        t.done();
      };
    });

    specify("ensure_resolution_is_not_thrown_off_by_hrefless_base_tag", { async: true }, t => {
      const html = `<html><head><base target="whatever">
                 <script src="./files/hello.js"></script></head><body>
                 <span id="test">hello from html</span></body></html>`;

      const doc = jsdom.jsdom(html, {
        url: toFileUrl(__filename),
        features: {
          FetchExternalResources: ["script"],
          ProcessExternalResources: ["script"]
        }
      });

      doc.defaultView.doCheck = () => {
        assert.equal(doc.getElementById("test").innerHTML, "hello from javascript");
        t.done();
      };
    });

    specify("ensure_resources_can_be_skipped_via_options_features", { async: true }, t => {
      const html = `<html><head><script src="./files/hello.js"></script>
                 <script src="./files/nyan.js"></script></head>
                 <body><span id="test">hello from html</span><span id="cat">
                 hello from cat</body></html>`;

      const doc2 = jsdom.jsdom(html, {
        url: toFileUrl(__filename),
        features: {
          FetchExternalResources: ["script"],
          ProcessExternalResources: ["script"],
          SkipExternalResources: new RegExp(".*/files/h")
        }
      });
      doc2.defaultView.onload = () => {
        assert.equal(doc2.getElementById("test").innerHTML, "hello from html", "js should not be executed (doc2)");
        assert.equal(doc2.getElementById("cat").innerHTML, "hello from nyan cat", "js should be executed (doc2)");
        t.done();
      };
    });

    specify("understand_file_protocol", { async: true }, t => {
      const html = `
        <html>
          <head>
            <script type="text/javascript" src="` + toFileUrl("files/hello.js") + `"></script>
          </head>
          <body>
            <span id="test">hello from html</span>
          </body>
        </html>`;

      const doc = jsdom.jsdom(html);
      doc.onload = () => {
        assert.equal(
          doc.getElementById("test").innerHTML,
          "hello from javascript",
          "resource with file protocol should work"
        );
        t.done();
      };
    });

    specify("auto_tostring", () => {
      const buffer = fs.readFileSync(path.resolve(__dirname, "files/env.html"));
      let dom;
      assert.doesNotThrow(() => {
        dom = jsdom.jsdom(buffer);
      }, "buffers should automatically be stringified");
      assert.equal(dom.documentElement.getElementsByTagName("*").length, 3, "should parse as per usual");
    });

    specify("allow_ender_to_run", { async: true }, t => {
      jsdom.env("<a />", ["file:" + path.resolve(__dirname, "files/ender-qwery.js")], (e, w) => {
        assert.ok(!e, "no errors");
        assert.ok(w.ender, "ender exists");
        assert.ok(w.$, "window contains $");
        t.done();
      });
    });

    specify("issue_509_out_of_memory", () => {
      const html = fs.readFileSync(path.resolve(__dirname, "files/reddit.html"));
      jsdom.jsdom(html.toString());
    });

    specify("jquery_val_on_selects", { async: true }, t => {
      const window = jsdom.jsdom().defaultView;

      jsdom.jQueryify(window, "file:" + path.resolve(__dirname, "../jquery-fixtures/jquery-1.11.0.js"), () => {
        window.$("body").append(`<html><body><select id="foo"><option value="first">f</option>
                                 <option value="last">l</option></select></body></html>`);

        assert.equal(
          window.document.querySelector("[value='first']").selected, true,
          "`selected` property should be `true` for first"
        );
        assert.equal(
          window.document.querySelector("[value='last']").selected, false,
          "`selected` property should be `false` for last"
        );

        assert.equal(window.$("[value='first']").val(), "first", "`val()` on first <option> should return its value");
        assert.equal(window.$("[value='last']").val(), "last", "`val()` on last <option> should return its value");

        const f = window.$("#foo");
        assert.equal(f.val(), "first", "`val()` on <select> should return first <option>'s value");

        window.$("#foo").val("last");
        assert.equal(
          window.document.querySelector("[value='first']").selected, false,
          "`selected` property should be `false` for first"
        );
        assert.equal(
          window.document.querySelector("[value='last']").selected, true,
          "`selected` property should be `true` for last"
        );
        assert.equal(window.$("#foo").val(), "last", "`val()` should return last <option>'s value");
        t.done();
      });
    });

    specify("jquery_attr_mixed_case", { async: true }, t => {
      const window = jsdom.jsdom().defaultView;

      jsdom.jQueryify(window, "file:" + path.resolve(__dirname, "../jquery-fixtures/jquery-1.11.0.js"), () => {
        const $el = window.$(`<div mixedcase="blah"></div>`);

        assert.equal($el.attr("mixedCase"), "blah");
        t.done();
      });
    });

    specify("Calling show() method in jQuery 1.11.0 (GH-709)", { async: true }, t => {
      const window = jsdom.jsdom("<!DOCTYPE html><html><head></head><body></body></html>").defaultView;

      jsdom.jQueryify(window, "file:" + path.resolve(__dirname, "../jquery-fixtures/jquery-1.11.0.js"), () => {
        const $el = window.$("<div></div>");

        assert.doesNotThrow(() => {
          $el.show();
        });

        t.done();
      });
    });

    specify("Calling show() method in jQuery 1.11.0, second case (GH-709)", { async: true }, t => {
      const window = jsdom.jsdom("<!DOCTYPE html><html><head></head><body></body></html>").defaultView;

      jsdom.jQueryify(window, "file:" + path.resolve(__dirname, "../jquery-fixtures/jquery-1.11.0.js"), () => {
        const $el1 = window.$("<div></div>");
        const $el2 = window.$("<span></span>");

        assert.doesNotThrow(() => {
          $el1.show();
          $el2.show();
        });

        t.done();
      });
    });

    specify("redirected_url_equal_to_location_href", { async: true }, t => {
      const html = "<p>Redirect</p>";
      const server = http.createServer((req, res) => {
        switch (req.url) {
          case "/":
            res.writeHead(302, { Location: "/redir" });
            res.end();
            break;
          case "/redir":
            res.writeHead(200, { "Content-Length": html.length });
            res.end(html);
            break;
        }
      });

      server.listen(8001, "127.0.0.1", () => {
        jsdom.env({
          url: "http://127.0.0.1:8001",
          done(errors, window) {
            server.close();
            if (errors) {
              assert.ok(false, errors.message);
            } else {
              assert.equal(window.document.body.innerHTML, html, "root page should be redirected");
              assert.equal(window.location.href, "http://127.0.0.1:8001/redir",
                "window.location.href should equal to redirected url");
            }
            t.done();
          }
        });
      });
    });

    specify("script_with_cookie", { async: true }, t => {
      const html = `<!DOCTYPE html><html><head><script src="/foo.js"></script></head><body>foo</body></html>`;

      const server = http.createServer((req, res) => {
        switch (req.url) {
          case "/":
            res.writeHead(200, { "Content-Length": html.length });
            res.end(html);
            break;
          case "/foo.js":
            const cookie = req.headers.cookie;
            const name = cookie ? cookie.split("=")[1] : "no cookie";
            const text = "document.body.innerHTML = 'Hello " + name + "'; window.doCheck();";
            res.writeHead(200, { "Content-Length": text.length });
            res.end(text);
            break;
        }
      });

      server.listen(8001, "127.0.0.1", () => {
        jsdom.env({
          url: "http://127.0.0.1:8001",
          document: { cookie: "name=world" },
          features: {
            FetchExternalResources: ["script"],
            ProcessExternalResources: ["script"]
          },
          created(err, window) {
            window.doCheck = () => {
              server.close();
              assert.ifError(err);
              assert.equal(window.document.body.innerHTML, "Hello world");
              t.done();
            };
          }
        });
      });
    });

    specify("xhr_with_cookie", { async: true }, t => {
      const html = `<!DOCTYPE html><html><head><script>
                 const xhr = new XMLHttpRequest();
                 xhr.onload = function () {
                   document.body.innerHTML = xhr.responseText;
                   window.doCheck();
                 };
                 xhr.open("GET", "/foo.txt", true);
                 xhr.send();
                 </script></head><body>foo</body></html>`;

      const server = http.createServer((req, res) => {
        switch (req.url) {
          case "/":
            res.writeHead(200, { "Content-Length": html.length });
            res.end(html);
            break;
          case "/foo.txt":
            const cookie = req.headers.cookie;
            const name = cookie ? cookie.split("=")[1] : "no cookie";
            const text = "Hello " + name;
            res.writeHead(200, { "Content-Length": text.length });
            res.end(text);
            break;
        }
      });

      server.listen(8001, "127.0.0.1", () => {
        jsdom.env({
          url: "http://127.0.0.1:8001",
          document: { cookie: "name=world" },
          features: {
            FetchExternalResources: ["script"],
            ProcessExternalResources: ["script"]
          },
          done(err, window) {
            window.doCheck = () => {
              server.close();
              assert.ifError(err);
              assert.equal(window.document.body.innerHTML, "Hello world");
              t.done();
            };
          }
        });
      });
    });
  }); // describe("node specific tests")
});
Exemple #8
0
describe("Web Platform Tests", () => {
  // jscs:disable maximumLineLength
  [
    "dom/nodes/CharacterData-appendData.html",
    "dom/nodes/CharacterData-deleteData.html",
    "dom/nodes/CharacterData-insertData.html",
    "dom/nodes/CharacterData-remove.html",
    "dom/nodes/CharacterData-replaceData.html",
    "dom/nodes/Document-adoptNode.html",
    "dom/nodes/Document-contentType/contentType/createHTMLDocument.html",
    "dom/nodes/Document-createComment.html",
    "dom/nodes/Document-createProcessingInstruction.html",
    "dom/nodes/Document-createProcessingInstruction-xhtml.xhtml",
    "dom/nodes/Document-createTextNode.html",
    "dom/nodes/Document-implementation.html",
    "dom/nodes/DocumentType-literal.html",
    "dom/nodes/DocumentType-literal-xhtml.xhtml",
    "dom/nodes/DocumentType-remove.html",
    "dom/nodes/DOMImplementation-createDocumentType.html",
    "dom/nodes/DOMImplementation-createHTMLDocument.html",
    "dom/nodes/DOMImplementation-hasFeature.html",
    "dom/nodes/Element-classlist.html",
    "dom/nodes/Element-getElementsByClassName.html",
    "dom/nodes/Element-remove.html",
    "dom/nodes/attributes.html",
    "dom/nodes/getElementsByClassName-01.htm",
    "dom/nodes/getElementsByClassName-02.htm",
    "dom/nodes/getElementsByClassName-03.htm",
    "dom/nodes/getElementsByClassName-04.htm",
    "dom/nodes/getElementsByClassName-05.htm",
    "dom/nodes/getElementsByClassName-06.htm",
    "dom/nodes/getElementsByClassName-07.htm",
    "dom/nodes/getElementsByClassName-08.htm",
    "dom/nodes/getElementsByClassName-09.htm",
    "dom/nodes/getElementsByClassName-10.xml",
    // "dom/nodes/getElementsByClassName-11.xml", // XML class attribute and localName and namespaces don't work well
    "dom/nodes/getElementsByClassName-12.htm",
    "dom/nodes/getElementsByClassName-13.htm",
    "dom/nodes/getElementsByClassName-14.htm",
    "dom/nodes/getElementsByClassName-15.htm",
    "dom/nodes/getElementsByClassName-16.htm",
    "dom/nodes/getElementsByClassName-17.htm",
    "dom/nodes/getElementsByClassName-18.htm",
    "dom/nodes/getElementsByClassName-19.htm",
    "dom/nodes/getElementsByClassName-20.htm",
    "dom/nodes/getElementsByClassName-21.htm",
    "dom/nodes/getElementsByClassName-22.htm",
    "dom/nodes/getElementsByClassName-23.htm",
    "dom/nodes/getElementsByClassName-24.htm",
    "dom/nodes/getElementsByClassName-25.htm",
    "dom/nodes/getElementsByClassName-26.htm",
    "dom/nodes/getElementsByClassName-27.htm",
    "dom/nodes/getElementsByClassName-28.htm",
    "dom/nodes/getElementsByClassName-29.htm",
    "dom/nodes/getElementsByClassName-30.htm",
    "dom/nodes/getElementsByClassName-31.htm",
    "dom/nodes/Node-baseURI.html",
    "dom/nodes/Node-cloneNode.html",
    "dom/traversal/NodeFilter-constants.html",
    "dom/traversal/NodeIterator.html",
    "domparsing/insert-adjacent.html",
    "html/browsers/browsing-the-web/history-traversal/PopStateEvent.html",
    "html/browsers/browsing-the-web/history-traversal/hashchange_event.html",
    "html/browsers/browsing-the-web/history-traversal/popstate_event.html",
    // "html/browsers/history/the-history-interface/001.html", // complicated navigation stuff and structured cloning
    // "html/browsers/history/the-history-interface/002.html", // complicated navigation stuff and structured cloning
    // "html/browsers/history/the-history-interface/004.html", // subtle timing issues that I can't quite figure out; see comment in History-impl.js
    "html/browsers/history/the-history-interface/005.html",
    "html/browsers/history/the-history-interface/006.html",
    // "html/browsers/history/the-history-interface/007.html", // depends on the load event being delayed properly
    "html/browsers/history/the-history-interface/008.html",
    // "html/browsers/history/the-history-interface/009.html", // complicated navigation stuff for iframes
    // "html/browsers/history/the-history-interface/010.html", // complicated navigation stuff for iframes
    "html/browsers/history/the-history-interface/011.html",
    "html/browsers/history/the-history-interface/012.html",
    "html/browsers/history/the-location-interface/document_location.html",
    "html/browsers/history/the-location-interface/location-stringifier.html",
    "html/browsers/history/the-location-interface/location_hash.html",
    "html/browsers/history/the-location-interface/location_host.html",
    "html/browsers/history/the-location-interface/location_hostname.html",
    "html/browsers/history/the-location-interface/location_href.html",
    "html/browsers/history/the-location-interface/location_pathname.html",
    "html/browsers/history/the-location-interface/location_port.html",
    "html/browsers/history/the-location-interface/location_protocol.html",
    "html/browsers/history/the-location-interface/location_search.html",
    "html/dom/dynamic-markup-insertion/document-writeln/document.writeln-02.html",
    "html/dom/dynamic-markup-insertion/document-writeln/document.writeln-03.html",
    "html/dom/elements/global-attributes/classlist-nonstring.html",
    // "html/infrastructure/urls/terminology-0/document-base-url.html", // we don't support srcdoc <base> correctly
    "html/semantics/forms/the-input-element/selection.html",
    "html/semantics/scripting-1/the-script-element/script-language-type.html",
    "html/semantics/scripting-1/the-script-element/script-languages-01.html",
    // "html/semantics/scripting-1/the-script-element/script-languages-02.html", // our script execution timing is off; see discussion in https://github.com/tmpvar/jsdom/pull/1406
    "html/semantics/scripting-1/the-script-element/script-noembed-noframes-iframe.xhtml",
    // "html/semantics/scripting-1/the-script-element/script-text-xhtml.xhtml", // not sure; XHTML problems?
    "html/semantics/scripting-1/the-script-element/script-text.html",
    // "html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/node-document.html", // templates in XHTML are totally messed up
    // "html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/template-child-nodes.html", // templates in XHTML are totally messed up
    // "html/semantics/scripting-1/the-template-element/additions-to-serializing-xhtml-documents/outerhtml.html", // templates in XHTML are totally messed up
    "html/semantics/scripting-1/the-template-element/additions-to-the-steps-to-clone-a-node/template-clone-children.html",
    "html/semantics/scripting-1/the-template-element/additions-to-the-steps-to-clone-a-node/templates-copy-document-owner.html",
    // "html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-document-type.html", // requires @@toStringTag
    // "html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-test-001.html", // template content owner document semantics not yet implemented
    // "html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-test-002.html", // template content owner document semantics not yet implemented
    // "html/semantics/scripting-1/the-template-element/definitions/template-contents.html", // requires @@toStringTag
    "html/semantics/scripting-1/the-template-element/innerhtml-on-templates/innerhtml.html",
    "html/semantics/scripting-1/the-template-element/serializing-html-templates/outerhtml.html",
    "html/semantics/scripting-1/the-template-element/template-element/content-attribute.html",
    // "html/semantics/scripting-1/the-template-element/template-element/node-document-changes.html", // template content owner document semantics not yet implemented
    // "html/semantics/scripting-1/the-template-element/template-element/template-as-a-descendant.html", // template parsing not quite perfect yet
    "html/semantics/scripting-1/the-template-element/template-element/template-content-node-document.html",
    "html/semantics/scripting-1/the-template-element/template-element/template-content.html",
    "html/semantics/scripting-1/the-template-element/template-element/template-descendant-body.html",
    "html/semantics/scripting-1/the-template-element/template-element/template-descendant-frameset.html",
    "html/semantics/scripting-1/the-template-element/template-element/template-descendant-head.html",
    // "html/semantics/tabular-data/the-table-element/caption-methods.html",
    // "html/semantics/tabular-data/the-table-element/createTBody.html",
    "html/semantics/tabular-data/the-table-element/delete-caption.html",
    "html/semantics/tabular-data/the-table-element/insertRow-method-01.html",
    "html/semantics/tabular-data/the-table-element/insertRow-method-02.html",
    // "html/semantics/tabular-data/the-table-element/tBodies.html",
    // "html/semantics/tabular-data/the-table-element/table-insertRow.html",
    // "html/semantics/tabular-data/the-table-element/table-rows.html",
    "html/syntax/serializing-html-fragments/outerHTML.html",
    // "html/syntax/parsing/html5lib_template.html", // no idea what's going on here
    "html/syntax/parsing/template/additions-to-foster-parenting/template-is-a-foster-parent-element.html",
    "html/syntax/parsing/template/additions-to-foster-parenting/template-is-not-a-foster-parent-element.html",
    "html/syntax/parsing/template/additions-to-the-in-body-insertion-mode/generating-of-implied-end-tags.html",
    "html/syntax/parsing/template/additions-to-the-in-body-insertion-mode/ignore-body-token.html",
    "html/syntax/parsing/template/additions-to-the-in-body-insertion-mode/ignore-frameset-token.html",
    "html/syntax/parsing/template/additions-to-the-in-body-insertion-mode/ignore-head-token.html",
    "html/syntax/parsing/template/additions-to-the-in-body-insertion-mode/ignore-html-token.html",
    "html/syntax/parsing/template/additions-to-the-in-body-insertion-mode/start-tag-body.html",
    "html/syntax/parsing/template/additions-to-the-in-body-insertion-mode/start-tag-html.html",
    "html/syntax/parsing/template/additions-to-the-in-body-insertion-mode/template-end-tag-without-start-one.html",
    // "html/syntax/parsing/template/additions-to-the-in-frameset-insertion-mode/end-tag-frameset.html", // template parsing not quite perfect yet
    "html/syntax/parsing/template/additions-to-the-in-head-insertion-mode/generating-of-implied-end-tags.html",
    "html/syntax/parsing/template/additions-to-the-in-head-insertion-mode/template-end-tag-without-start-one.html",
    "html/syntax/parsing/template/additions-to-the-in-table-insertion-mode/end-tag-table.html",
    "html/syntax/parsing/template/appending-to-a-template/template-child-nodes.html",
    "html/syntax/parsing/template/clearing-the-stack-back-to-a-given-context/clearing-stack-back-to-a-table-body-context.html",
    "html/syntax/parsing/template/clearing-the-stack-back-to-a-given-context/clearing-stack-back-to-a-table-context.html",
    "html/syntax/parsing/template/clearing-the-stack-back-to-a-given-context/clearing-stack-back-to-a-table-row-context.html",
    // "html/syntax/parsing/template/creating-an-element-for-the-token/template-owner-document.html", // template content owner document semantics not yet implemented
    "html/webappapis/atob/base64.html",
    "html/webappapis/timers/evil-spec-example.html",

    "dom/events/Event-constants.html",
    "dom/events/Event-defaultPrevented.html",
    "dom/events/Event-dispatch-bubbles-false.html",
    "dom/events/Event-dispatch-handlers-changed.html",
    "dom/events/Event-dispatch-omitted-capture.html",
    "dom/events/Event-dispatch-propagation-stopped.html",
    "dom/events/Event-dispatch-reenter.html",
    "dom/events/Event-dispatch-target-moved.html",
    "dom/events/Event-dispatch-target-removed.html",
    "dom/events/Event-initEvent.html",
    "dom/events/Event-propagation.html",
    "dom/events/Event-type.html",
    "dom/events/Event-type-empty.html",
    "dom/events/EventTarget-addEventListener.html",
    "dom/events/EventTarget-dispatchEvent-returnvalue.html",
    "dom/events/EventTarget-dispatchEvent.html",
    "dom/events/EventTarget-removeEventListener.html",
    "dom/events/ProgressEvent.html",

    "DOMEvents/ClickFakeEvent.nondocument.html",
    "DOMEvents/event-phases-order.html",
    "DOMEvents/throwing-in-listener-and-window-error-event.html",
    "DOMEvents/throwing-in-listener-when-all-have-not-run-yet.html",

    "FileAPI/fileReader.html",
    "FileAPI/blob/Blob-slice.html",
    "FileAPI/file/File-constructor.html",
    "FileAPI/FileReader/Progress_event_bubbles_cancelable.html",
    "FileAPI/reading-data-section/FileReader-event-handler-attributes.html",
    "FileAPI/reading-data-section/filereader_abort.html",
    "FileAPI/reading-data-section/filereader_error.html",
    "FileAPI/reading-data-section/filereader_readAsArrayBuffer.html",
    "FileAPI/reading-data-section/filereader_readAsDataURL.html",
    "FileAPI/reading-data-section/filereader_readAsText.html",
    "FileAPI/reading-data-section/filereader_readystate.html",
    "FileAPI/reading-data-section/filereader_result.html",
    "XMLHttpRequest/FormData-append.html",
    "XMLHttpRequest/abort-after-receive.htm",
    "XMLHttpRequest/abort-after-send.htm",
    "XMLHttpRequest/abort-after-stop.htm",
    "XMLHttpRequest/abort-after-timeout.htm",
    "XMLHttpRequest/abort-during-done.htm",
    "XMLHttpRequest/abort-during-open.htm",
    "XMLHttpRequest/abort-during-unsent.htm",
    "XMLHttpRequest/abort-during-upload.htm",
    "XMLHttpRequest/abort-event-abort.htm",
    "XMLHttpRequest/abort-event-listeners.htm",
    "XMLHttpRequest/abort-event-loadend.htm",
    "XMLHttpRequest/abort-event-order.htm",
    "XMLHttpRequest/abort-upload-event-abort.htm",
    "XMLHttpRequest/abort-upload-event-loadend.htm",
    "XMLHttpRequest/anonymous-mode-unsupported.htm",
    "XMLHttpRequest/data-uri.htm",
    "XMLHttpRequest/event-abort.htm",
    "XMLHttpRequest/event-error.html",
    "XMLHttpRequest/event-load.htm",
    "XMLHttpRequest/event-loadend.htm",
    "XMLHttpRequest/event-loadstart.htm",
    "XMLHttpRequest/event-progress.htm",
    "XMLHttpRequest/event-readystate-sync-open.htm",
    "XMLHttpRequest/event-readystatechange-loaded.htm",
    "XMLHttpRequest/event-timeout.htm",
    "XMLHttpRequest/event-upload-progress-crossorigin.sub.htm",
    "XMLHttpRequest/event-upload-progress.htm",
    "XMLHttpRequest/formdata-blob.htm",
    "XMLHttpRequest/formdata-delete.htm",
    "XMLHttpRequest/formdata-get.htm",
    "XMLHttpRequest/formdata-has.htm",
    "XMLHttpRequest/formdata-set.htm",
    "XMLHttpRequest/formdata.htm",
    "XMLHttpRequest/getallresponseheaders-cookies.htm",
    "XMLHttpRequest/getallresponseheaders-status.htm",
    "XMLHttpRequest/getresponseheader-case-insensitive.htm",
    "XMLHttpRequest/getresponseheader-chunked-trailer.htm",
    "XMLHttpRequest/getresponseheader-cookies-and-more.htm",
    "XMLHttpRequest/getresponseheader-error-state.htm",
    "XMLHttpRequest/getresponseheader-server-date.htm",
    "XMLHttpRequest/getresponseheader-special-characters.htm",
    "XMLHttpRequest/getresponseheader-unsent-opened-state.htm",
    // "XMLHttpRequest/interface.html", // needs this PR https://github.com/tmpvar/jsdom/pull/1406
    "XMLHttpRequest/open-after-abort.htm",
    "XMLHttpRequest/open-after-setrequestheader.htm",
    "XMLHttpRequest/open-during-abort.htm",
    "XMLHttpRequest/open-method-case-insensitive.htm",
    // "XMLHttpRequest/open-method-case-sensitive.htm", // request module forces upper case
    "XMLHttpRequest/open-method-bogus.htm",
    "XMLHttpRequest/open-method-insecure.htm",
    "XMLHttpRequest/open-method-responsetype-set-sync.htm",
    "XMLHttpRequest/open-open-send.htm",
    "XMLHttpRequest/open-open-sync-send.htm",
    "XMLHttpRequest/open-referer.htm",
    "XMLHttpRequest/open-send-open.htm",
    "XMLHttpRequest/open-sync-open-send.htm",
    "XMLHttpRequest/open-url-about-blank-window.htm",
    "XMLHttpRequest/open-url-base.htm",
    "XMLHttpRequest/open-url-base-inserted.htm",
    "XMLHttpRequest/open-url-base-inserted-after-open.htm",
    // "XMLHttpRequest/open-url-bogus.htm", // I don't understand this one
    "XMLHttpRequest/open-url-encoding.htm",
    "XMLHttpRequest/open-url-fragment.htm",
    "XMLHttpRequest/open-url-javascript-window-2.htm",
    "XMLHttpRequest/open-url-javascript-window.htm",
    "XMLHttpRequest/open-url-multi-window.htm",
    "XMLHttpRequest/open-url-multi-window-2.htm",
    "XMLHttpRequest/open-url-multi-window-3.htm",
    "XMLHttpRequest/open-url-multi-window-4.htm",
    // "XMLHttpRequest/open-url-multi-window-5.htm", // location.reload is not implemented
    // "XMLHttpRequest/open-url-worker-origin.htm", // needs Worker implementation
    // "XMLHttpRequest/open-url-worker-simple.htm", // needs Worker implementation
    "XMLHttpRequest/open-user-password-non-same-origin.htm",
    "XMLHttpRequest/overridemimetype-done-state.htm",
    // "XMLHttpRequest/overridemimetype-headers-received-state-force-shiftjis.htm", // needs proper encoding handling
    "XMLHttpRequest/overridemimetype-invalid-mime-type.htm",
    "XMLHttpRequest/overridemimetype-loading-state.htm",
    // "XMLHttpRequest/overridemimetype-open-state-force-utf-8.htm", // needs proper encoding handling
    "XMLHttpRequest/overridemimetype-open-state-force-xml.htm",
    // "XMLHttpRequest/overridemimetype-unsent-state-force-shiftjis.htm", // needs proper encoding handling
    "XMLHttpRequest/preserve-ua-header-on-redirect.htm",
    "XMLHttpRequest/progress-events-response-data-gzip.htm",
    "XMLHttpRequest/response-data-arraybuffer.htm",
    "XMLHttpRequest/response-data-blob.htm",
    // "XMLHttpRequest/response-data-deflate.htm", // request module does not support deflate
    "XMLHttpRequest/response-data-gzip.htm",
    "XMLHttpRequest/response-data-progress.htm",
    "XMLHttpRequest/response-invalid-responsetype.htm",
    "XMLHttpRequest/response-json.htm",
    "XMLHttpRequest/response-method.htm",
    "XMLHttpRequest/responseText-status.html",
    "XMLHttpRequest/responsetype.html",
    // "XMLHttpRequest/responsexml-basic.htm", // xml namespace issue with getElementById
    // "XMLHttpRequest/responsexml-document-properties.htm", see https://github.com/w3c/web-platform-tests/issues/2668
    "XMLHttpRequest/responsexml-media-type.htm",
    "XMLHttpRequest/responsexml-non-document-types.htm",
    // "XMLHttpRequest/responsexml-non-well-formed.htm", // xml parsing is not strict
    "XMLHttpRequest/security-consideration.sub.html",
    "XMLHttpRequest/send-accept-language.htm",
    "XMLHttpRequest/send-accept.htm",
    "XMLHttpRequest/send-authentication-basic-cors-not-enabled.htm",
    "XMLHttpRequest/send-authentication-basic-cors.htm",
    "XMLHttpRequest/send-authentication-basic-repeat-no-args.htm",
    "XMLHttpRequest/send-authentication-basic-setrequestheader-existing-session.htm",
    "XMLHttpRequest/send-authentication-basic-setrequestheader.htm",
    "XMLHttpRequest/send-authentication-basic.htm",
    "XMLHttpRequest/send-authentication-competing-names-passwords.htm",
    // "XMLHttpRequest/send-authentication-cors-basic-setrequestheader.htm", // seems wrong ?
    "XMLHttpRequest/send-conditional.htm",
    "XMLHttpRequest/send-content-type-charset.htm",
    "XMLHttpRequest/send-content-type-string.htm",
    "XMLHttpRequest/send-data-arraybuffer.htm",
    "XMLHttpRequest/send-data-blob.htm",
    "XMLHttpRequest/send-data-es-object.htm",
    "XMLHttpRequest/send-data-formdata.htm",
    "XMLHttpRequest/send-data-unexpected-tostring.htm",
    "XMLHttpRequest/send-entity-body-basic.htm",
    "XMLHttpRequest/send-entity-body-document-bogus.htm",
    // "XMLHttpRequest/send-entity-body-document.htm", // needs proper encoding handling
    "XMLHttpRequest/send-entity-body-empty.htm",
    "XMLHttpRequest/send-entity-body-get-head-async.htm",
    "XMLHttpRequest/send-entity-body-get-head.htm",
    "XMLHttpRequest/send-entity-body-none.htm",
    "XMLHttpRequest/send-network-error-async-events.sub.htm",
    "XMLHttpRequest/send-network-error-sync-events.sub.htm",
    "XMLHttpRequest/send-no-response-event-loadend.htm",
    "XMLHttpRequest/send-no-response-event-loadstart.htm",
    "XMLHttpRequest/send-no-response-event-order.htm",
    "XMLHttpRequest/send-non-same-origin.sub.htm",
    // "XMLHttpRequest/send-receive-utf16.htm", // needs proper encoding handling
    "XMLHttpRequest/send-redirect-bogus-sync.htm",
    "XMLHttpRequest/send-redirect-bogus.htm",
    // "XMLHttpRequest/send-redirect-infinite-sync.htm", // the test seems broken locally
    // "XMLHttpRequest/send-redirect-infinite.htm", // the test seems broken locally
    "XMLHttpRequest/send-redirect-no-location.htm",
    // "XMLHttpRequest/send-redirect-to-cors.htm", // request module remove content-type header on redirect
    "XMLHttpRequest/send-redirect-to-non-cors.htm",
    // "XMLHttpRequest/send-redirect.htm", // request module remove content-type header on redirect
    "XMLHttpRequest/send-response-event-order.htm",
    "XMLHttpRequest/send-response-upload-event-loadend.htm",
    "XMLHttpRequest/send-response-upload-event-loadstart.htm",
    "XMLHttpRequest/send-response-upload-event-progress.htm",
    "XMLHttpRequest/send-send.htm",
    "XMLHttpRequest/send-sync-blocks-async.htm",
    "XMLHttpRequest/send-sync-no-response-event-load.htm",
    "XMLHttpRequest/send-sync-no-response-event-loadend.htm",
    "XMLHttpRequest/send-sync-no-response-event-order.htm",
    "XMLHttpRequest/send-sync-response-event-order.htm",
    "XMLHttpRequest/send-sync-timeout.htm",
    "XMLHttpRequest/send-timeout-events.htm",
    // "XMLHttpRequest/send-usp.html", // needs URLSearchParams implementation
    "XMLHttpRequest/setrequestheader-after-send.htm",
    "XMLHttpRequest/setrequestheader-allow-empty-value.htm",
    "XMLHttpRequest/setrequestheader-allow-whitespace-in-value.htm",
    "XMLHttpRequest/setrequestheader-before-open.htm",
    "XMLHttpRequest/setrequestheader-bogus-name.htm",
    "XMLHttpRequest/setrequestheader-bogus-value.htm",
    "XMLHttpRequest/setrequestheader-case-insensitive.htm",
    "XMLHttpRequest/setrequestheader-content-type.htm",
    "XMLHttpRequest/setrequestheader-header-allowed.htm",
    "XMLHttpRequest/setrequestheader-header-forbidden.htm",
    "XMLHttpRequest/setrequestheader-open-setrequestheader.htm",
    "XMLHttpRequest/status-async.htm",
    "XMLHttpRequest/status-basic.htm",
    "XMLHttpRequest/status-error.htm",
    "XMLHttpRequest/timeout-cors-async.htm",
    "XMLHttpRequest/timeout-sync.htm",
    "XMLHttpRequest/xmlhttprequest-basic.htm",
    "XMLHttpRequest/xmlhttprequest-eventtarget.htm",
    // "XMLHttpRequest/xmlhttprequest-network-error-sync.htm", // the test seems broken locally
    // "XMLHttpRequest/xmlhttprequest-network-error.htm", // the test seems broken locally
    // "XMLHttpRequest/xmlhttprequest-timeout-aborted.html", // self instanceof Window fails
    // "XMLHttpRequest/xmlhttprequest-timeout-abortedonmain.html", // self instanceof Window fails
    // "XMLHttpRequest/xmlhttprequest-timeout-overrides.html", // self instanceof Window fails
    // "XMLHttpRequest/xmlhttprequest-timeout-overridesexpires.html", // self instanceof Window fails
    // "XMLHttpRequest/xmlhttprequest-timeout-simple.html", // self instanceof Window fails
    // "XMLHttpRequest/xmlhttprequest-timeout-synconmain.html", // self instanceof Window fails
    // "XMLHttpRequest/xmlhttprequest-timeout-twice.html", // self instanceof Window fails
    // "XMLHttpRequest/xmlhttprequest-timeout-worker-aborted.html", // needs worker implementation
    // "XMLHttpRequest/xmlhttprequest-timeout-worker-overrides.html", // needs worker implementation
    // "XMLHttpRequest/xmlhttprequest-timeout-worker-overridesexpires.html", // needs worker implementation
    // "XMLHttpRequest/xmlhttprequest-timeout-worker-simple.html", // needs worker implementation
    // "XMLHttpRequest/xmlhttprequest-timeout-worker-synconworker.html", // needs worker implementation
    // "XMLHttpRequest/xmlhttprequest-timeout-worker-twice.html", // needs worker implementation
    "XMLHttpRequest/xmlhttprequest-unsent.htm",
    "XMLHttpRequest/XMLHttpRequest-withCredentials.html",
    "cors/allow-headers.htm",
    "cors/basic.htm",
    "cors/credentials-flag.htm",
    // "cors/late-upload-events.htm", // I don't know how to fix this one
    "cors/origin.htm",
    // "cors/preflight-cache.htm", // cache should probably be implemented for simple requests before
    "cors/redirect-origin.htm",
    "cors/redirect-preflight.htm",
    // "cors/redirect-preflight-2.htm", // preflight should also be done before redirected requests
                                        // but request module redirects cannot be paused while doing preflight
    "cors/redirect-userinfo.htm",
    // "cors/remote-origin.htm", // postMessage event does not contain source
    "cors/request-headers.htm",
    // "cors/response-headers.htm", // I don't find a spec about combining same value response headers
                                    // and slow synchronous requests cause a timeout on an asynchronous test
    // "cors/simple-requests.htm", // slow synchronous requests cause a timeout on an asynchronous test too
    "cors/status-async.htm",
    "cors/status-preflight.htm",
    "cors/status.htm"
  ]
  .forEach(runWebPlatformTest);
});
Exemple #9
0
describe("API: JSDOM class's methods", () => {
  describe("serialize", () => {
    it("should serialize the default document correctly", () => {
      const dom = new JSDOM();

      assert.strictEqual(dom.serialize(), `<html><head></head><body></body></html>`);
    });

    it("should serialize a text-only document correctly", () => {
      const dom = new JSDOM(`hello`);

      assert.strictEqual(dom.serialize(), `<html><head></head><body>hello</body></html>`);
    });

    it("should serialize a document with HTML correctly", () => {
      const dom = new JSDOM(`<!DOCTYPE html><html><head></head><body><p>hello world!</p></body></html>`);

      assert.strictEqual(dom.serialize(),
                         `<!DOCTYPE html><html><head></head><body><p>hello world!</p></body></html>`);
    });

    it("should serialize documents with omitted and varying-case html or body tags correctly", () => {
      const inputs = [
        "<HTML><BODY></BODY></HTML>",
        "<html><BODY></Body></HTML>",
        "<html><body></body></html>",
        "<body></body>",
        ""
      ];

      const outputs = inputs.map(input => (new JSDOM(input)).serialize());

      for (const output of outputs) {
        assert.strictEqual(output, `<html><head></head><body></body></html>`);
      }
    });
  });

  describe("nodeLocation", () => {
    it("should throw when includeNodeLocations is left as the default (false)", () => {
      const dom = new JSDOM(`<p>Hello</p>`);
      const node = dom.window.document.querySelector("p");

      assert.throws(() => dom.nodeLocation(node));
    });

    it("should throw when includeNodeLocations is set explicitly to false", () => {
      const dom = new JSDOM(`<p>Hello</p>`, { includeNodeLocations: false });
      const node = dom.window.document.querySelector("p");

      assert.throws(() => dom.nodeLocation(node));
    });

    it("should give the correct location for an element", () => {
      const dom = new JSDOM(`<p>Hello</p>`, { includeNodeLocations: true });
      const node = dom.window.document.querySelector("p");

      assert.deepEqual(dom.nodeLocation(node), {
        line: 1,
        col: 1,
        startOffset: 0,
        endOffset: 12,
        startTag: { line: 1, col: 1, startOffset: 0, endOffset: 3 },
        endTag: { line: 1, col: 9, startOffset: 8, endOffset: 12 }
      });
    });

    it("should give the correct location for a text node", () => {
      const dom = new JSDOM(`<p>Hello</p>`, { includeNodeLocations: true });
      const node = dom.window.document.querySelector("p").firstChild;

      assert.deepEqual(dom.nodeLocation(node), {
        line: 1,
        col: 4,
        startOffset: 3,
        endOffset: 8
      });
    });

    it("should give the correct location for a void element", () => {
      const dom = new JSDOM(`<p>Hello
        <img src="foo.jpg">
      </p>`, { includeNodeLocations: true });
      const node = dom.window.document.querySelector("img");

      assert.deepEqual(dom.nodeLocation(node), {
        attrs: {
          src: {
            line: 2,
            col: 14,
            startOffset: 22,
            endOffset: 35
          }
        },
        line: 2,
        col: 9,
        startOffset: 17,
        endOffset: 36
      });
    });
  });

  describe("runVMScript", () => {
    it("should throw when runScripts is left as the default", () => {
      const dom = new JSDOM();
      const script = new vm.Script("this.ran = true;");

      assert.throws(() => dom.runVMScript(script), TypeError);

      assert.strictEqual(dom.window.ran, undefined);
    });

    it("should work when runScripts is set to \"outside-only\"", () => {
      const dom = new JSDOM(``, { runScripts: "outside-only" });
      const script = new vm.Script("this.ran = true;");

      dom.runVMScript(script);

      assert.strictEqual(dom.window.ran, true);
    });

    it("should work when runScripts is set to \"dangerously\"", () => {
      const dom = new JSDOM(``, { runScripts: "dangerously" });
      const script = new vm.Script("this.ran = true;");

      dom.runVMScript(script);

      assert.strictEqual(dom.window.ran, true);
    });

    it("should return the result of the evaluation", () => {
      const dom = new JSDOM(``, { runScripts: "outside-only" });
      const script = new vm.Script("5;");

      const result = dom.runVMScript(script);

      assert.strictEqual(result, 5);
    });

    it("should work with the same script multiple times", () => {
      const dom = new JSDOM(``, { runScripts: "outside-only" });
      const script = new vm.Script("if (!this.ran) { this.ran = 0; } ++this.ran;");

      dom.runVMScript(script);
      dom.runVMScript(script);
      dom.runVMScript(script);

      assert.strictEqual(dom.window.ran, 3);
    });
  });

  describe("reconfigure", () => {
    describe("windowTop", () => {
      it("should reconfigure the window.top property (tested from the outside)", () => {
        const dom = new JSDOM();
        const newTop = { is: "top" };

        dom.reconfigure({ windowTop: newTop });

        assert.strictEqual(dom.window.top, newTop);
      });

      it("should reconfigure the window.top property (tested from the inside)", () => {
        const dom = new JSDOM(``, { runScripts: "dangerously" });
        const newTop = { is: "top" };

        dom.reconfigure({ windowTop: newTop });

        dom.window.document.body.innerHTML = `<script>
          window.topResult = top.is;
        </script>`;

        assert.strictEqual(dom.window.topResult, "top");
      });

      it("should do nothing when no options are passed", () => {
        const dom = new JSDOM();

        dom.reconfigure({ });

        assert.strictEqual(dom.window.top, dom.window);
      });

      it("should change window.top to undefined if passing undefined", () => {
        const dom = new JSDOM();

        dom.reconfigure({ windowTop: undefined });

        assert.strictEqual(dom.window.top, undefined);
      });
    });

    describe("url", () => {
      it("should successfully change the URL", () => {
        const dom = new JSDOM(``, { url: "http://example.com/" });
        const window = dom.window;

        assert.strictEqual(window.document.URL, "http://example.com/");

        function testPass(urlString, expected = urlString) {
          dom.reconfigure({ url: urlString });

          assert.strictEqual(window.location.href, expected);
          assert.strictEqual(window.document.URL, expected);
          assert.strictEqual(window.document.documentURI, expected);
        }

        testPass("http://localhost", "http://localhost/");
        testPass("http://www.localhost", "http://www.localhost/");
        testPass("http://www.localhost.com", "http://www.localhost.com/");
        testPass("https://localhost/");
        testPass("file://path/to/my/location/");
        testPass("http://localhost.subdomain.subdomain/");
        testPass("http://localhost:3000/");
        testPass("http://localhost/");
      });

      it("should throw and not impact the URL when trying to change to an unparseable URL", () => {
        const dom = new JSDOM(``, { url: "http://example.com/" });
        const window = dom.window;

        assert.strictEqual(window.document.URL, "http://example.com/");

        function testFail(url) {
          assert.throws(() => dom.reconfigure({ url }), TypeError);

          assert.strictEqual(window.location.href, "http://example.com/");
          assert.strictEqual(window.document.URL, "http://example.com/");
          assert.strictEqual(window.document.documentURI, "http://example.com/");
        }

        testFail("fail");
        testFail("/fail");
        testFail("fail.com");
        testFail(undefined);
      });


      it("should not throw and not impact the URL when no url option is given", () => {
        const dom = new JSDOM(``, { url: "http://example.com/" });
        const window = dom.window;

        assert.strictEqual(window.document.URL, "http://example.com/");

        assert.doesNotThrow(() => dom.reconfigure({ }));

        assert.strictEqual(window.location.href, "http://example.com/");
        assert.strictEqual(window.document.URL, "http://example.com/");
        assert.strictEqual(window.document.documentURI, "http://example.com/");
      });
    });
  });
});
Exemple #10
0
  describe("reconfigure", () => {
    describe("windowTop", () => {
      it("should reconfigure the window.top property (tested from the outside)", () => {
        const dom = new JSDOM();
        const newTop = { is: "top" };

        dom.reconfigure({ windowTop: newTop });

        assert.strictEqual(dom.window.top, newTop);
      });

      it("should reconfigure the window.top property (tested from the inside)", () => {
        const dom = new JSDOM(``, { runScripts: "dangerously" });
        const newTop = { is: "top" };

        dom.reconfigure({ windowTop: newTop });

        dom.window.document.body.innerHTML = `<script>
          window.topResult = top.is;
        </script>`;

        assert.strictEqual(dom.window.topResult, "top");
      });

      it("should do nothing when no options are passed", () => {
        const dom = new JSDOM();

        dom.reconfigure({ });

        assert.strictEqual(dom.window.top, dom.window);
      });

      it("should change window.top to undefined if passing undefined", () => {
        const dom = new JSDOM();

        dom.reconfigure({ windowTop: undefined });

        assert.strictEqual(dom.window.top, undefined);
      });
    });

    describe("url", () => {
      it("should successfully change the URL", () => {
        const dom = new JSDOM(``, { url: "http://example.com/" });
        const window = dom.window;

        assert.strictEqual(window.document.URL, "http://example.com/");

        function testPass(urlString, expected = urlString) {
          dom.reconfigure({ url: urlString });

          assert.strictEqual(window.location.href, expected);
          assert.strictEqual(window.document.URL, expected);
          assert.strictEqual(window.document.documentURI, expected);
        }

        testPass("http://localhost", "http://localhost/");
        testPass("http://www.localhost", "http://www.localhost/");
        testPass("http://www.localhost.com", "http://www.localhost.com/");
        testPass("https://localhost/");
        testPass("file://path/to/my/location/");
        testPass("http://localhost.subdomain.subdomain/");
        testPass("http://localhost:3000/");
        testPass("http://localhost/");
      });

      it("should throw and not impact the URL when trying to change to an unparseable URL", () => {
        const dom = new JSDOM(``, { url: "http://example.com/" });
        const window = dom.window;

        assert.strictEqual(window.document.URL, "http://example.com/");

        function testFail(url) {
          assert.throws(() => dom.reconfigure({ url }), TypeError);

          assert.strictEqual(window.location.href, "http://example.com/");
          assert.strictEqual(window.document.URL, "http://example.com/");
          assert.strictEqual(window.document.documentURI, "http://example.com/");
        }

        testFail("fail");
        testFail("/fail");
        testFail("fail.com");
        testFail(undefined);
      });


      it("should not throw and not impact the URL when no url option is given", () => {
        const dom = new JSDOM(``, { url: "http://example.com/" });
        const window = dom.window;

        assert.strictEqual(window.document.URL, "http://example.com/");

        assert.doesNotThrow(() => dom.reconfigure({ }));

        assert.strictEqual(window.location.href, "http://example.com/");
        assert.strictEqual(window.document.URL, "http://example.com/");
        assert.strictEqual(window.document.documentURI, "http://example.com/");
      });
    });
  });
Exemple #11
0
describe("jsdom/miscellaneous", () => {
  specify("DOMContentLoaded should not be fired after window.close() (GH-1479)", () => {
    const { window } = new JSDOM(`<html><head>
      <script>
        window.a = 0;
        document.addEventListener("DOMContentLoaded", () => window.a++, false);
      </script>
      </head><body></body></html>`, { runScripts: "dangerously" });

    window.close();
    assert.equal(window.a, 0);
  });

  specify("appendChild_to_document_with_existing_documentElement", () => {
    function t() {
      try {
        const doc = (new JSDOM()).window.document;
        doc.appendChild(doc.createElement("html"));
      } catch (e) {
        assert.equal(e.code, 3, "Should throw HIERARCHY_ERR");
        throw e;
      }
    }
    assert.throws(t);
  });

  specify("importNode", () => {
    assert.doesNotThrow(() => {
      const html1 = `<html><body><h1 id="headline">Hello <span id="world">World</span></h1></body></html>`;
      const doc1 = (new JSDOM(html1)).window.document;
      const doc2 = (new JSDOM()).window.document;
      doc2.body.appendChild(doc2.importNode(doc1.getElementById("headline"), true));
      doc2.getElementById("world").className = "foo";
    });
  });

  specify("window_is_augmented_with_dom_features", () => {
    const { window } = new JSDOM();
    assert.notEqual(window.Element, null, "window.Element should not be null");
  });

  specify("url_resolution", () => {
    const html = `
  <html>
    <head></head>
    <body>
      <a href="http://example.com" id="link1">link1</a>
      <a href="/local.html" id="link2">link2</a>
      <a href="local.html" id="link3">link3</a>
      <a href="../../local.html" id="link4">link4</a>
      <a href="#here" id="link5">link5</a>
      <a href="//example.com/protocol/avoidance.html" id="link6">protocol</a>
    </body>\
  </html>`;

    function testLocal() {
      const url = "file:///path/to/docroot/index.html";
      const doc = (new JSDOM(html, { url })).window.document;
      assert.equal(
        doc.getElementById("link1").href,
        "http://example.com/",
        "Absolute URL should be left alone except for possible trailing slash"
      );
      assert.equal(
        doc.getElementById("link2").href,
        "file:///local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link3").href,
        "file:///path/to/docroot/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link4").href,
        "file:///path/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link5").href,
        "file:///path/to/docroot/index.html#here",
        "Relative URL should be resolved"
      );
      // test.equal(
      //   doc.getElementById("link6").href,
      //   "//prototol/avoidance.html",
      //   "Protocol-less URL should be resolved"
      // );
    }

    function testRemote() {
      const url = "http://example.com/path/to/docroot/index.html";
      const doc = (new JSDOM(html, { url })).window.document;
      assert.equal(
        doc.getElementById("link1").href,
        "http://example.com/",
        "Absolute URL should be left alone except for possible trailing slash"
      );
      assert.equal(
        doc.getElementById("link2").href,
        "http://example.com/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link3").href,
        "http://example.com/path/to/docroot/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link4").href,
        "http://example.com/path/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link5").href,
        "http://example.com/path/to/docroot/index.html#here",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link6").href,
        "http://example.com/protocol/avoidance.html",
        "Relative URL should be resolved"
      );
    }

    function testBase() {
      const url = "about:blank";
      const doc = (new JSDOM(html, { url })).window.document;
      const base = doc.createElement("base");
      base.href = "http://example.com/path/to/docroot/index.html";
      doc.getElementsByTagName("head").item(0).appendChild(base);
      assert.equal(
        doc.getElementById("link1").href,
        "http://example.com/",
        "Absolute URL should be left alone except for possible trailing slash"
      );
      assert.equal(
        doc.getElementById("link2").href,
        "http://example.com/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link3").href,
        "http://example.com/path/to/docroot/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link4").href,
        "http://example.com/path/local.html",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link5").href,
        "http://example.com/path/to/docroot/index.html#here",
        "Relative URL should be resolved"
      );
      assert.equal(
        doc.getElementById("link6").href,
        "http://example.com/protocol/avoidance.html",
        "Relative URL should be resolved"
      );
    }

    testLocal();
    testRemote();
    testBase();
  });

  specify("numeric_values", () => {
    const html = `<html><body><td data-year="2011" data-month="0" data-day="9">
                  <a href="#" class=" ">9</a>
                </td></body></html>`;
    const { document } = (new JSDOM(html)).window;
    const a = document.body.children.item(0);

    a.innerHTML = 9;
    a.setAttribute("id", 123);
    assert.strictEqual(a.innerHTML, "9", "Element stringify");
    assert.strictEqual(a.getAttributeNode("id").nodeValue, "123", "Attribute stringify");
  });

  specify("childNodes_updates_on_insertChild", () => {
    const { window } = new JSDOM();
    const div = window.document.createElement("div");
    let text = window.document.createTextNode("bar");
    div.appendChild(text);
    assert.strictEqual(
      text, div.childNodes[0],
      "childNodes NodeList should update after appendChild"
    );

    text = window.document.createTextNode("bar");
    div.insertBefore(text, null);
    assert.strictEqual(
      text, div.childNodes[1],
      "childNodes NodeList should update after insertBefore"
    );
  });

  specify("option_set_selected", () => {
    const { window } = new JSDOM();
    const select = window.document.createElement("select");

    const option0 = window.document.createElement("option");
    select.appendChild(option0);
    option0.setAttribute("selected", "selected");

    const optgroup = window.document.createElement("optgroup");
    select.appendChild(optgroup);
    const option1 = window.document.createElement("option");
    optgroup.appendChild(option1);

    assert.strictEqual(true, option0.selected, "initially selected");
    assert.strictEqual(false, option1.selected, "initially not selected");
    assert.strictEqual(option1, select.options[1], "options should include options inside optgroup");

    option1.defaultSelected = true;
    assert.strictEqual(false, option0.selected, "selecting other option should deselect this");
    assert.strictEqual(true, option0.defaultSelected, "default should not change");
    assert.strictEqual(true, option1.selected, "selected changes when defaultSelected changes");
    assert.strictEqual(true, option1.defaultSelected, "I just set this");

    option0.defaultSelected = false;
    option0.selected = true;
    assert.strictEqual(true, option0.selected, "I just set this");
    assert.strictEqual(false, option0.defaultSelected, "selected does not set default");
    assert.strictEqual(false, option1.selected, "should deselect others");
    assert.strictEqual(true, option1.defaultSelected, "unchanged");
  });

  specify("fix_for_issue_221", () => {
    const html = "<html><head></head><body></body></html>";
    const { document } = (new JSDOM(html)).window;
    const div = document.createElement("div");
    document.body.appendChild(div);
    div.appendChild(document.createTextNode("hello world"));
    assert.strictEqual(
      div.childNodes[0].nodeValue, "hello world",
      "Nodelist children should be populated immediately"
    );
  });

  specify("parsing_and_serializing_entities", () => {
    const html = `<html><body><a href="http://example.com/?a=b&amp;c=d">&lt;&aelig;&#x263a;foo</a>`;
    const { document } = (new JSDOM(html)).window;
    const anchor = document.getElementsByTagName("a")[0];

    assert.strictEqual(
      anchor.getAttribute("href"), "http://example.com/?a=b&c=d",
      "href attribute value should be deentitified"
    );

    assert.strictEqual(
      anchor.firstChild.nodeValue, "<æ☺foo",
      "nodeValue of text node should be deentitified"
    );

    assert.ok(
      anchor.outerHTML.indexOf("http://example.com/?a=b&amp;c=d") !== -1,
      "outerHTML of anchor href should be entitified"
    );

    assert.ok(
      anchor.innerHTML.indexOf("&lt;") === 0,
      "innerHTML of anchor should begin with &lt;"
    );
  });

  specify("parsing_and_serializing_unknown_entities", () => {
    const html = "<html><body>&nowayjose;&#x263a;&#xblah;&#9q;</body></html>";
    const { document } = (new JSDOM(html)).window;
    assert.strictEqual(
      document.body.firstChild.nodeValue, "&nowayjose;☺lah;\tq;",
      "Unknown and unparsable entities should be handled like a browser would"
    );
    assert.strictEqual(
      document.body.innerHTML, "&amp;nowayjose;☺lah;\tq;",
      "Unknown and unparsable entities should be handled like a browser would"
    );
  });

  specify("entities_in_script_should_be_left_alone", () => {
    const html = `<!DOCTYPE html><html><head></head><body><script>alert("&quot;");</script></body></html>`;
    const { document } = (new JSDOM(html)).window;
    assert.strictEqual(document.body.innerHTML, `<script>alert("&quot;");</script>`);
    assert.strictEqual(document.body.firstChild.innerHTML, `alert("&quot;");`);
  });

  specify("document_title_and_entities", () => {
    const html = "<html><head><title>&lt;b&gt;Hello&lt;/b&gt;</title></head><body></body></html>";
    const { document } = (new JSDOM(html)).window;

    assert.strictEqual(
      document.title, "<b>Hello</b>",
      `document.title should be the deentitified version of what was in
      the original HTML`
    );

    document.title = "<b>World</b>";
    assert.strictEqual(
      document.title, "<b>World</b>",
      `When document.title is set programmatically to something looking like
      HTML tags, then read again, it should have the exact same value, no
      entification should take place`
    );

    document.title = "&lt;b&gt;World&lt;/b&gt;";
    assert.strictEqual(
      document.title, "&lt;b&gt;World&lt;/b&gt;",
      `When document.title is set programmatically to something looking like
      HTML entities, then read again, it should have the exact same value,
      no deentification should take place`
    );
  });

  specify("setting_and_getting_textContent", () => {
    const html = `<html><head>\n<title>&lt;foo&gt;</title></head>
                  <body>Hello<span><span>, </span>world</span>!</body></html>`;
    const { document } = (new JSDOM(html)).window;

    assert.strictEqual(
      document.textContent, null,
      "textContent of document should be null"
    );

    assert.strictEqual(
      document.head.textContent, "\n<foo>",
      "textContent of document.head should be the initial whitespace plus the textContent " +
                       "of the document title"
    );

    assert.strictEqual(
      document.body.textContent,
      "Hello, world!",
      "textContent of document.body should be the concatenation of the textContent values of its child nodes"
    );

    assert.strictEqual(
      document.createTextNode("&lt;b&gt;World&lt;/b&gt;").textContent,
      "&lt;b&gt;World&lt;/b&gt;",
      "textContent of programmatically created text node should be identical to its nodeValue"
    );

    assert.strictEqual(
      document.createComment("&lt;b&gt;World&lt;/b&gt;").textContent,
      "&lt;b&gt;World&lt;/b&gt;",
      "textContent of programmatically created comment node should be identical to its nodeValue"
    );

    const frag = document.createDocumentFragment();
    frag.appendChild(document.createTextNode("&lt;foo&gt;<b></b>"));
    frag.appendChild(document.createElement("div")).appendChild(document.createTextNode("&lt;foo&gt;<b></b>"));

    assert.strictEqual(
      frag.textContent,
      "&lt;foo&gt;<b></b>&lt;foo&gt;<b></b>",
      "textContent of programmatically created document fragment should be the concatenation " +
      "of the textContent values of its child nodes"
    );

    const div = document.createElement("div");
    div.innerHTML = "&amp;lt;b&amp;gt;\nWorld&amp;lt;/b&amp;gt;<span></span><span>" +
                    "<span></span></span><span>&amp;lt;b&amp;gt;World&amp;lt;/b&amp;gt;</span>";

    assert.strictEqual(
      div.textContent, "&lt;b&gt;\nWorld&lt;/b&gt;&lt;b&gt;World&lt;/b&gt;",
      `textContent of complex programmatically created <div> should be the
      concatenation of the textContent values of its child nodes`
    );
  });

  specify("issues_230_259", () => {
    const instr = `<html><body style="color: #ffffff; foo: bar"></body></html>`;
    const dom = new JSDOM(instr);
    assert.ok(dom.serialize().match(/0: *color/) === null);
  });

  // see: https://github.com/tmpvar/jsdom/issues/262
  specify("issue_262", () => {
    const { document } = (new JSDOM()).window;
    const a = document.createElement("a");
    a.setAttribute("style", "color:blue");
    a.style.setProperty("color", "red");
    assert.equal(a.outerHTML.match(/style="/g).length, 1, "style attribute must not be serialized twice");
  });

  // see: https://github.com/tmpvar/jsdom/issues/267
  specify("issue_267", () => {
    const { document } = (new JSDOM()).window;
    const a = document.createElement("a");
    a.style.width = "100%";
    assert.ok(a.getAttribute("style").match(/^\s*width\s*:\s*100%\s*;?\s*$/), "style attribute must contain width");
  });

  // Test inline event handlers set on the body.
  // TODO this currently fails!?
  specify.skip("test_body_event_handler_inline", { skipIfBrowser: true, async: true }, t => {
    // currently skipped in browsers because of an issue:
    // TODO: https://github.com/tmpvar/jsdom/issues/1379
    const html = `
      <html>
        <head>
          <script>
            function loader () {
              window.loader_called = true;
            }
          </script>
        </head>
        <body onload="loader()"></body>
      </html>`;
    const doc = (new JSDOM(html, { runScripts: "dangerously" })).window.document;
    const window = doc.defaultView;
    // In JSDOM, listeners registered with addEventListener are called before
    // "traditional" listeners, so listening for "load" will fire before our
    // inline listener.  This means we have to check the value on the next
    // tick.
    window.addEventListener("load", () => {
      process.nextTick(() => {
        assert.equal(window.loader_called, true);
        t.done();
      });
    });
    doc.close();
  });

  specify("get_element_by_id", () => {
    const doc = (new JSDOM()).window.document;
    const el = doc.createElement("div");
    el.setAttribute("id", "foo");
    assert.equal(doc.getElementById("foo"), null, "Element must not be found until it has been added to the DOM");

    doc.body.appendChild(el);
    assert.equal(doc.getElementById("foo"), el, "Element must be found after being added");

    el.id = "bar";
    assert.equal(doc.getElementById("foo"), null, "Element must not be found by its previous id");
    assert.equal(doc.getElementById("bar"), el, "Element must be found by its new id");

    el.setAttribute("id", "baz");
    assert.equal(doc.getElementById("bar"), null, "Element must not be found by its previous id");
    assert.equal(doc.getElementById("baz"), el, "Element must be found by its new id");

    el.getAttributeNode("id").nodeValue = "boo";
    assert.equal(doc.getElementById("boo"), el, "Element must be found by its new id");

    doc.body.removeChild(el);
    assert.equal(doc.getElementById(el.id), null, "Element must not be found after it has been removed");
  });

  specify("get_element_by_id_multi_id", () => {
    const doc = (new JSDOM()).window.document;
    const div = doc.createElement("div");
    div.setAttribute("id", "foo");
    doc.body.appendChild(div);
    const span = doc.createElement("span");
    span.setAttribute("id", "foo");
    doc.body.appendChild(span);

    // now if we remove the second element, we should still find the first
    doc.body.removeChild(span);
    assert.equal(doc.getElementById("foo"), div, "Original div#foo must be found after removing invalid span#foo");
  });

  specify("issue_335_inline_event_handlers", () => {
    const dom = new JSDOM(`<a onclick="somefunction()">call some function</a>`);
    const a = dom.window.document.getElementsByTagName("a").item(0);
    const onclick = a.getAttribute("onclick");
    assert.notEqual(onclick, null);
    assert.equal(onclick, "somefunction()");
    assert.ok(dom.serialize().indexOf("onclick") > -1);
  });

  specify("issue_338_internal_nodelist_props", () => {
    const doc = (new JSDOM()).window.document;
    const props = Object.keys(doc.body.childNodes);
    assert.equal(props.length, 0, "Internal properties must not be enumerable");
  });

  specify("setting_and_getting_script_element_text", () => {
    const doc = (new JSDOM("<script></script>")).window.document;
    const script = doc.getElementsByTagName("script")[0];
    assert.equal(script.text, "");
    script.text = "const x = 3;";
    assert.equal(script.text, "const x = 3;");
    script.text = "const y = 2;";
    assert.equal(script.text, "const y = 2;");
  });

  specify("issue_361_textarea_value_property", () => {
    const doc = (new JSDOM(`<html><body><textarea id="mytextarea"></textarea></body></html>`)).window.document;

    doc.getElementById("mytextarea").value = "<foo>";
    assert.equal(doc.getElementById("mytextarea").value, "<foo>");
  });

  specify("css_classes_should_be_attached_to_dom", () => {
    const dom = (new JSDOM()).window;

    assert.notEqual(dom.StyleSheet, undefined);
    assert.notEqual(dom.MediaList, undefined);
    assert.notEqual(dom.CSSStyleSheet, undefined);
    assert.notEqual(dom.CSSRule, undefined);
    assert.notEqual(dom.CSSStyleRule, undefined);
    assert.notEqual(dom.CSSMediaRule, undefined);
    assert.notEqual(dom.CSSImportRule, undefined);
    assert.notEqual(dom.CSSStyleDeclaration, undefined);
  });

  specify("issue_530_async_load_events", { async: true }, t => {
    const doc = (new JSDOM("<html><head></head><body></body></html>")).window.document;
    const window = doc.defaultView;

    // Add the load event after the document is already created; it shouldn"t
    // fire until nextTick. The test will fail (with a timeout) if it has
    // already fired.
    window.addEventListener("load", () => {
      assert.ok(true);
      t.done();
    });
  });

  specify("iframe_contents", () => {
    const { document } = (new JSDOM("<iframe></iframe>")).window;
    const iframeDocument = document.querySelector("iframe").contentWindow.document;

    assert.equal(iframeDocument.documentElement.outerHTML, "<html><head></head><body></body></html>");
    assert.ok(iframeDocument.documentElement);
    assert.ok(iframeDocument.head);
    assert.ok(iframeDocument.body);
  });

  specify("addmetatohead", () => {
    const dom = new JSDOM();
    const { window } = dom;
    const meta = window.document.createElement("meta");
    window.document.getElementsByTagName("head").item(0).appendChild(meta);
    const elements = window.document.getElementsByTagName("head").item(0).childNodes;
    assert.strictEqual(elements.item(elements.length - 1), meta, "last element should be the new meta tag");
    assert.ok(dom.serialize().indexOf("<meta>") > -1, "meta should have open tag");
    assert.strictEqual(
      dom.serialize().indexOf("</meta>"),
      -1,
      "meta should not be stringified with a closing tag"
    );
  });

  // these tests require file system access or they start a http server
  describe("node specific tests", { skipIfBrowser: true }, () => {
    specify("ensure_resolution_is_not_thrown_off_by_hrefless_base_tag", { async: true }, t => {
      const html = `<html><head><base target="whatever"></head><body>
                 <span id="test">hello from html</span>
                 <script src="./files/hello.js"></script></body></html>`;

      const { window } = new JSDOM(html, {
        url: toFileUrl(__filename),
        runScripts: "dangerously",
        resources: "usable"
      });

      window.doCheck = () => {
        assert.equal(window.document.getElementById("test").innerHTML, "hello from javascript");
        t.done();
      };
    });

    specify("understand_file_protocol", { async: true }, t => {
      const html = `
        <html>
          <head>
          </head>
          <body>
            <span id="test">hello from html</span>
            <script type="text/javascript" src="` + toFileUrl("files/hello.js") + `"></script>
          </body>
        </html>`;

      const { window } = new JSDOM(html, { resources: "usable", runScripts: "dangerously" });
      window.doCheck = () => {
        assert.equal(
          window.document.getElementById("test").innerHTML,
          "hello from javascript",
          "resource with file protocol should work"
        );
        t.done();
      };
    });

    specify("jquery_val_on_selects", { async: true }, t => {
      const { window } = new JSDOM(``, { resources: "usable", runScripts: "dangerously" });

      const script = window.document.createElement("script");
      script.src = "file:" + path.resolve(__dirname, "../jquery-fixtures/jquery-1.11.0.js");
      script.onload = () => {
        window.$("body").append(`<html><body><select id="foo"><option value="first">f</option>
                                 <option value="last">l</option></select></body></html>`);

        assert.equal(
          window.document.querySelector("[value='first']").selected, true,
          "`selected` property should be `true` for first"
        );
        assert.equal(
          window.document.querySelector("[value='last']").selected, false,
          "`selected` property should be `false` for last"
        );

        assert.equal(window.$("[value='first']").val(), "first", "`val()` on first <option> should return its value");
        assert.equal(window.$("[value='last']").val(), "last", "`val()` on last <option> should return its value");

        const f = window.$("#foo");
        assert.equal(f.val(), "first", "`val()` on <select> should return first <option>'s value");

        window.$("#foo").val("last");
        assert.equal(
          window.document.querySelector("[value='first']").selected, false,
          "`selected` property should be `false` for first"
        );
        assert.equal(
          window.document.querySelector("[value='last']").selected, true,
          "`selected` property should be `true` for last"
        );
        assert.equal(window.$("#foo").val(), "last", "`val()` should return last <option>'s value");
        t.done();
      };
      window.document.body.appendChild(script);
    });

    specify("jquery_attr_mixed_case", { async: true }, t => {
      const { window } = new JSDOM(``, { resources: "usable", runScripts: "dangerously" });

      const script = window.document.createElement("script");
      script.src = "file:" + path.resolve(__dirname, "../jquery-fixtures/jquery-1.11.0.js");
      script.onload = () => {
        const $el = window.$(`<div mixedcase="blah"></div>`);

        assert.equal($el.attr("mixedCase"), "blah");
        t.done();
      };
      window.document.body.appendChild(script);
    });

    specify("Calling show() method in jQuery 1.11.0 (GH-709)", { async: true }, t => {
      const { window } = new JSDOM(``, { resources: "usable", runScripts: "dangerously" });

      const script = window.document.createElement("script");
      script.src = "file:" + path.resolve(__dirname, "../jquery-fixtures/jquery-1.11.0.js");
      script.onload = () => {
        const $el = window.$("<div></div>");

        assert.doesNotThrow(() => {
          $el.show();
        });

        t.done();
      };
      window.document.body.appendChild(script);
    });

    specify("Calling show() method in jQuery 1.11.0, second case (GH-709)", { async: true }, t => {
      const { window } = new JSDOM(``, { resources: "usable", runScripts: "dangerously" });

      const script = window.document.createElement("script");
      script.src = "file:" + path.resolve(__dirname, "../jquery-fixtures/jquery-1.11.0.js");
      script.onload = () => {
        const $el1 = window.$("<div></div>");
        const $el2 = window.$("<span></span>");

        assert.doesNotThrow(() => {
          $el1.show();
          $el2.show();
        });

        t.done();
      };
      window.document.body.appendChild(script);
    });
  }); // describe("node specific tests")
});