denoland / fresh

The next-gen web framework.
https://fresh.deno.dev
MIT License
12.09k stars 614 forks source link

Testing Fresh Components #427

Open TheMagicNacho opened 2 years ago

TheMagicNacho commented 2 years ago

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.

audrow commented 2 years ago

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

TheMagicNacho commented 2 years ago

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/preact-render-to-string@5.2.1";
import { DOMParser } from "https://deno.land/x/deno_dom@v0.1.32-alpha/deno-dom-wasm.ts";
import { describe, it } from "https://deno.land/std@0.148.0/testing/bdd.ts";
import { assertEquals, assertExists } from "https://deno.land/std@0.148.0/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...')
  });
});
trungthecelestial commented 1 year ago

Any update on this issue?

digitaldesigndj commented 1 year ago

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.

MoaathAlattas commented 1 year ago

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

import { DOMParser } from "https://esm.sh/linkedom@0.14.14"
import { render } from "https://esm.sh/@testing-library/preact@3.2.2?deps=preact@10.10.6"; // make sure to specify preact version
import {describe, beforeEach, it} from "https://deno.land/std@0.152.0/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"));
  });
})
sigmaSd commented 1 year ago

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

MoaathAlattas commented 1 year ago

@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)
sigmaSd commented 1 year ago

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)

Industrial commented 1 year ago

@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

sigmaSd commented 1 year ago

Nope I got the same errors

christian-bromann commented 1 year ago

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?

sigmaSd commented 1 year ago

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

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

image

christian-bromann commented 1 year ago

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.

radicand commented 1 year ago

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;
};
Mrashes commented 3 weeks ago

Coming to this a bit late but is there an approved way of doing this?

It would be great to have examples of how to test components in Fresh 2.0 docs. Working on my first fresh project and feeling the burn of not being able to test my components easily.