Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
October 14, 2021 08:53 pm GMT

Testing your Solid.js code

Solid.js logo

So, you have started to write a nice app or library in Solid.js and TypeScript - good choice, by the way - but now you want to unit test everything to make sure nothing breaks.

Jest logo

Jest is currently one of the best options for front end testing, but it requires some setup to play nicely with Solid.js. There are two options:

  • solid-jest - a preset to setup jest transpile solid using babel, given a working babel configuration; won't type-check, but is faster
  • ts-jest - a module to use TypeScript with jest that needs to be coerced a bit to work with Solid.js

If you are using TypeScript, choosing solid-jest might save you time when running the tests at the expense of missing type checks, but those can be run separately. ts-jest will check the types for you, but will take a bit longer. Both choices are valid, so decide for yourself.

Configuration

Regardless if you use solid-jest or ts-jest, you will need a babel config that supports Solid.js - the main difference is where to put it. It looks like this:

{  "presets": [    "@babel/preset-env",    "babel-preset-solid",    // only if you use TS with solid-jest    "@babel/preset-typescript"  ]}

To be able to run it, you need to add the dev dependencies @babel/core, @babel/preset-env and optionally @babel/preset-typescript depending on if you use TypeScript or not.

If you decided to use solid-jest, then put the babel config in your .babelrc file at the project root; otherwise it goes into the jest config section e.g. in package.json/jest as shown in the upcoming ts-jest section.

solid-jest

For solid-jest, the jest config that requires .babelrc to run looks like this (without the comment):

{  "jest" : {    "preset": "solid-jest/preset/browser",    // insert setupFiles and other config  }}

ts-jest

If you use ts-jest instead, it looks like this (without the comments):

{   "jest": {    "preset": "ts-jest",    "globals": {      "ts-jest": {        "tsconfig": "tsconfig.json",        "babelConfig": {          "presets": [            "babel-preset-solid",            "@babel/preset-env"          ]        }      }    },    // insert setupFiles and other config    // you probably want to test in browser mode:    "testEnvironment": "jsdom",    // unfortunately, solid cannot detect browser mode here,    // so we need to manually point it to the right versions:    "moduleNameMapper": {      "solid-js/web": "<rootDir>/node_modules/solid-js/web/dist/web.cjs",      "solid-js": "<rootDir>/node_modules/solid-js/dist/solid.cjs"    }  }}

What now?

I assume you already know your way around jest. If not, let me point you at this article series about jest. So why is here more text, you ask? There are a few more considerations when testing Solid.js code:

  • effects and memos only work inside a reactive root
  • components output HTMLElements or functions that returns them (either a single one or an array).

That means you can use a few shortcuts instead of actual rendering everything and look at it from a DOM perspective. Let's look at the available shortcuts:

Testing custom primitives ("hooks")

A powerful way to make functionality reusable is to put it into a separate primitive function. Let's have a naive implementation of a function that returns a variable number of words of "Lorem ipsum" text:

const loremIpsumWords = 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'.split(/\s+);const createLorem = (words: Accessor<number> | number) => {  return createMemo(() => {    const output = [],      len = typeof words === 'function' ? words() : words;    while (output.length <= len) {      output.push(...loremIpsumWords);    }    return output.slice(0, len).join(' ');  });};

If we use words as an Accessor, it will only ever be updated inside a reactive root and the updates only available inside an effect. Luckily, jest will happily wait for async functions, so we can use a promise to collect the output that will finally be evaluated.

test(  'it updates the result when words update',  async () => {    const input = [3, 2, 5],      expectedOutput = [        'Lorem ipsum dolor',        'Lorem ipsum',        'Lorem ipsum dolor sit amet'      ];    const actualOutput = await new Promise<string[]>(resolve => createRoot(dispose => {      const [words, setWords] = createSignal(input.shift() ?? 3);      const lorem = createLorem(words);      const output: string[] = [];      createEffect(() => {        // effects are batched, so the escape condition needs        // to run after the output is complete:        if (input.length === 0) {          dispose();          resolve(output);        }        output.push(lorem());        setWords(input.shift() ?? 0);      });    }));    expect(actualOutput).toEqual(expectedOutput);  });

This way, we don't even need to render the output and are very flexible with the test cases.

Testing directives (use:...)

A custom directive is merely a primitive that receives a DOM reference and an Accessor with arguments, if there are any. Let's consider a directive that abstracts the Fullscreen API. It would have the following signature:

export type FullscreenDirective = (  ref: HTMLElement,  active: Accessor<boolean | FullscreenOptions>) => void;

and is used like this:

const [fs, setFs] = createSignal(false);return <div use:FullscreenDirective={fs}>...</div>;

Do we need to render now to test the directive? No, we don't! We can just create our HTMLElement by using document.createElement('div') and test it like our previous primitive.

While you could argue that you want to test things from the user's perspective, the user of a directive is still a developer that uses it in the component - and you don't need to test if Solid.js actually works, that's already done for you by the maintainers.

You can have a look at an actual example at the fullscreen primitive from solid-primitives written by yours truly.

Testing components

Finally, we get to use render!? No? Don't tell me you're testing the returned DOM elements without ever rendering them!?

Well, you certainly could do that, but not even I'm suggesting you should, because components are usually used in a DOM context and not solely in a component context, as rendering could introduce side effects. Instead, I want to point you at Solid's testing library.

So let's add solid-testing-library to our project, together with @testing-library/dom and configure it:

// in package.json{  // ...  "jest": {    "preset": "solid-jest/preset/browser",    "setupFilesAfterEnv": ["./src/setupTests.ts"]  }}// and in ./src/setupTests.ts:import "regenerator-runtime/runtime";import '@testing-library/jest-dom'

Our project contains the following nonsensical component that we want be tested:

import { createSignal, Component, JSX } from 'solid-js';export const MyComponent: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {  const [clicked, setClicked] = createSignal(false);  return <div {...props} role="button" onClick={() => setClicked(true)}>    {clicked() ? 'Test this!' : 'Click me!'}  </div>;};

Now let's write a simple test using the testing library:

import { screen, render, fireEvent } from 'solid-testing-library';import { MyComponent } from './my-component';test('changes text on click', async () => {  await render(() => <MyComponent />);  const component = await screen.findByRole('button', { name: 'Click me!' });  expect(component).toBeInTheDocument();  fireEvent.click(component);  expect(await screen.findByRole('button', { name: 'Test this!' })).toBeInTheDocument();});

Let's run this:

> jest PASS  src/testing.test.tsx   changes text on click (53 ms)Test Suites: 1 passed, 1 totalTests:       1 passed, 1 totalSnapshots:   0 totalTime:        4.012 sRan all test suites.

May your tests catch all the bugs!


Original Link: https://dev.to/lexlohr/testing-your-solidjs-code-2gfh

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To