qwik icon indicating copy to clipboard operation
qwik copied to clipboard

[📖] How to do unit testing of components with Vite if the testing env is JSDOM instead of node

Open Nedimko123 opened this issue 2 years ago • 1 comments

Suggestion

If my component has any javascript that needs to be loaded on the client side, the test fails for me like if I wanted to check if window.width > 1024, it wouldnt work with vitest. Everybody suggests using jsdom, so I tried that like this:


import { renderToString } from "@builder.io/qwik/server";
import { test, expect, vi } from "vitest";
import { ExampleTest } from "./example";

test(`[ExampleTest Component]: Click counter +1`, async () => {
  const jsdom = require("jsdom");
  const { JSDOM } = jsdom;

  const screen = await renderToString(
    <body>
      <ExampleTest flag={false} />
    </body>
  );
  const dom = new JSDOM(screen.html);

  const button = dom.window.document.querySelector("button.btn-counter");

  expect(dom.window.document.querySelector("span").textContent).toBe("Count:0");
  expect(dom.window.document.querySelector("div.icon").textContent).toBe(
    "Flag: 💣"
  );
  button.addEventListener("click", () => {
    // const countElement = dom.window.document.querySelector("span");
    // const count = parseInt(countElement.textContent.replace("Count:", ""), 10);
    // countElement.textContent = `Count:${count + 1}`;
  });

  console.log(button.click());

  console.log(dom.window.document.querySelector("span").textContent);
});

This isn't that native and button clicks do not work for the example component, only if I add the event listener and I do something in that event listener - that will happen, but the onClick function will not.

Nedimko123 avatar Mar 30 '23 06:03 Nedimko123

I think Qwik has it's own DOM implementation called qwik-dom, which appears to be a fork of domino.

As an aside, Vitest has the ability to customize the environment which the tests are run in, with examples of how to add JSDOM or happy-dom, see more here. However, looking in the vite.config.ts generated by Qwik I do not see qwik-dom set as the environment.

Instead, it looks like Qwik tests use an alternative approach to exposing their mocked DOM environment via their createDOM method:

import { createDOM } from '@builder.io/qwik/testing';
...

const { screen, render, userEvent } = await createDOM();
await render(<ExampleTest flag={true} />);

I believe the expectation at this point is that you need to use the userEvent, exposed by createDOM, to trigger events, screen to query elements, and then make assertions about the state of those elements.

The example component and test created by npm run qwik add vitest, per the docs, has a good example of this approach:

test(`[ExampleTest Component]: Click counter +1`, async () => {
  const { screen, render, userEvent } = await createDOM();
  await render(<ExampleTest flag={true} />);

  expect(screen.outerHTML).toContain("Count:0");

  const spanBefore = screen.querySelector("span") as HTMLDivElement;
  await userEvent(".btn-counter", "click");
  expect(spanBefore.innerHTML).toEqual("Count:1");

  const spanAfter = screen.querySelector("span") as HTMLDivElement;
  await userEvent("button", "click");
  expect(spanAfter.innerHTML).toEqual("Count:2");
});

Digging deeper in createDOM and userEvent I see the following:

createDOM uses an ElementFixture to create the window, document, and the following element structure, all using qwik-dom:

window
  document 
    super-parent
      parent
        host
          child

It then uses the host, from the structure above, when you call the render method returned by createDOM, which internally passes the host as the root element to qwik.render:

const host = new ElementFixture().host;
...
render: function (jsxElement: JSXNode) {
  return qwik.render(host, jsxElement);
},

The screen object returned by createDOM is reference to the host element, not the document. This is different from something like testing-library/dom-testing-library where the screen is more analogous to the body (see here).

adamayres avatar Jul 31 '23 01:07 adamayres