fresh icon indicating copy to clipboard operation
fresh copied to clipboard

Testing Fresh Components

Open TheMagicNacho opened this issue 2 years ago • 15 comments

Issue: Deno testing suit documentation does not elaborate on how to render and test components.

Discussion: I have been having trouble finding ways to conduct unit tests for Fresh components. When using react-testing-library, we can use render(<MyComponent newProp={'myProp') />) to render the component and test. However, this feature is missing with the current testing suit on Deno.

To "hack" a solution together I had tried using dom-testing-library and I had tried using preact render, however neither of these solutions allowed me to test if the component is working the way I expect it to. I had also tried importing react-testing-libaray as a "hail Mary".

I understand Cypress is looking into how to perform integrated tests on Deno right now, however, finding a way to implement unit tests in fresh will allow us to improve TDD within this framework.

Recommendation: Documentation should be upgraded which describes how to render a component for testing. I do not think we need a new API or a new library to accomplish said means. That said, I do not have a solution for this problem yet.

TheMagicNacho avatar Jul 05 '22 13:07 TheMagicNacho

I haven't yet tried this, but maybe the answer is to use the testing that comes with Preact. https://preactjs.com/guide/v10/preact-testing-library

audrow avatar Jul 13 '22 15:07 audrow

Bottom Line Up Front: I found a solution by rendering the components through preact-render-to-string and using Demo DOM.

Prelude

Unfortunately, preact-testing-library does not work with Deno and fresh because preact-testing-library needs to be called within a DOM environment.

To overcome this limitation, I tried to update the Deno.json's compiler options to include "dom" per the Deno manual for jsdom but that didn't fix the problem.

Solution

At the top of the test file, we need to reference dom, deno.ns, and esnext.

We can render the Preact component using react to string then pass the string into the Deno DOMParser. From there, we can query the components we want to test then assert the results.


/** @jsx h */   
/// <reference no-default-lib="true"/>
/// <reference lib="dom" /> 
/// <reference lib="deno.ns" />
/// <reference lib="esnext" /> 

import { h } from 'preact';

import render from "https://esm.sh/[email protected]";
import { DOMParser } from "https://deno.land/x/[email protected]/deno-dom-wasm.ts";
import { describe, it } from "https://deno.land/[email protected]/testing/bdd.ts";
import { assertEquals, assertExists } from "https://deno.land/[email protected]/testing/asserts.ts";

import Card from '../components/Card.tsx';

describe('Card component', () => {
  it('should exists.', () => {
    const compAsString  = render(<Card contentTitle={'Test Title'} contentBody={'Lorem ispum...'} />)
    const doc = new DOMParser().parseFromString(compAsString, 'text/html');
  
    assertExists(doc);
  });

  it('should have a title and body.', () => {
    const compAsString  = render(<Card contentTitle={'Test Title'} contentBody={'Lorem ispum...'} />)
    const doc = new DOMParser().parseFromString(compAsString, 'text/html');

    const title = doc?.querySelector('h1');
    const body = doc?.querySelector('div');

    assertEquals(title?.textContent, 'Test Title')
    assertEquals(body?.textContent, 'Lorem ispum...')
  });
});

TheMagicNacho avatar Jul 24 '22 01:07 TheMagicNacho

Any update on this issue?

trungthecelestial avatar Sep 01 '22 12:09 trungthecelestial

This example is great, but when I render a component that uses asset() I get a lot of errors in the output:

Failed to create asset() URL, falling back to regular path ('/svg/andbounds-logo-white.svg'): ReferenceError: __FRSH_BUILD_ID is not defined

Obviously there's a better way to do this.

digitaldesigndj avatar Sep 01 '22 19:09 digitaldesigndj

@TheMagicNacho I was able to use preact-testing-library by defining a global document as mentioned here.

import { DOMParser } from "https://esm.sh/[email protected]"
import { render } from "https://esm.sh/@testing-library/[email protected][email protected]"; // make sure to specify preact version
import {describe, beforeEach, it} from "https://deno.land/[email protected]/testing/bdd.ts";
import { Nav } from "./nav.tsx";

describe("components/nav", () => {
  beforeEach(() => {
      window.document = new DOMParser().parseFromString("", "text/html") as any;
  });

  it("test example", () => {
    const { container } = render(<Nav />);
    assert(container.innerHTML.includes("Login"));
  });
})

MoaathAlattas avatar Sep 18 '22 06:09 MoaathAlattas

Is it possible to run the examples here https://preactjs.com/guide/v10/preact-testing-library/ using this trick? I tested a bit screen api seems to fail. and also I had to use npm jsdom because deno dom is missing style support

sigmaSd avatar Sep 18 '22 08:09 sigmaSd

@sigmaSd could you show an example of how you are using JSDOM? Whenever i use JSDOM, the test runner never ends the process so I am using LinkeDOM for now.

Instead of using screen, same methods are available out of the render method:

const { getByText } = render(<Counter initialCount={5}/>);

It seems like screen doesn't recognize the global document.body and haven't dug deeper into this yet. https://github.com/testing-library/dom-testing-library/blob/main/src/screen.ts

export const screen =
  typeof document !== 'undefined' && document.body // eslint-disable-line @typescript-eslint/no-unnecessary-condition
    ? getQueriesForElement(document.body, queries, initialValue)
    : Object.keys(queries).reduce((helpers, key) => {
        // `key` is for all intents and purposes the type of keyof `helpers`, which itself is the type of `initialValue` plus incoming properties from `queries`
        // if `Object.keys(something)` returned Array<keyof typeof something> this explicit type assertion would not be necessary
        // see https://stackoverflow.com/questions/55012174/why-doesnt-object-keys-return-a-keyof-type-in-typescript
        helpers[key as keyof typeof initialValue] = () => {
          throw new TypeError(
            'For queries bound to document.body a global document has to be available... Learn more: https://testing-library.com/s/screen-global-error',
          )
        }
        return helpers
      }, initialValue)

MoaathAlattas avatar Sep 19 '22 12:09 MoaathAlattas

for jsdom I imported it with npm:jsdom and I had to patch vm.isContext to return false (https://github.com/denoland/deno/issues/18315)

sigmaSd avatar Sep 20 '22 06:09 sigmaSd

@sigmaSd Did you get tests to run? I Can't get it to work at all.

https://github.com/testing-library/react-testing-library/issues/669

Industrial avatar Oct 04 '22 19:10 Industrial

Nope I got the same errors

sigmaSd avatar Oct 04 '22 20:10 sigmaSd

for jsdom I imported it with npm:jsdom and I had to patch vm.isContext to return false (denoland/deno#18315)

@sigmaSd how did you patch it?

christian-bromann avatar Oct 13 '22 17:10 christian-bromann

I did it in a hacky way but I saw Industrial has a better method

https://discord.com/channels/684898665143206084/1022163295895027722/threads/1025572387695108187

image

sigmaSd avatar Oct 13 '22 17:10 sigmaSd

Yup, ended up doing the same, the only problem with JSDom is that it tries to access canvas primitives when trying to render images. I ended up having to replace image tags with div tags.

christian-bromann avatar Oct 13 '22 18:10 christian-bromann

For those using the method suggested by @MoaathAlattas , note that LinkeDOM and deno-dom do not accept empty strings for DOMParser.parseFromString(). I ran into weird errors until I came across https://github.com/WebReflection/linkedom/issues/157 and fixed it by just initializing it with something.

export const initEnv = () => {
    globalThis.document = new DOMParser().parseFromString(
        "<html></html>", // this the main change
        "text/html"
    ) as unknown as Document;
    window.document = globalThis.document;
};

radicand avatar Apr 07 '23 20:04 radicand