in

Testing your Solid.js code – DEV Community


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"
  ]
}
Enter fullscreen mode

Exit fullscreen mode

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
  }
}
Enter fullscreen mode

Exit fullscreen mode



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"
    }
  }
}
Enter fullscreen mode

Exit fullscreen mode



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(' ');
  });
};
Enter fullscreen mode

Exit fullscreen mode

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);
  }
);
Enter fullscreen mode

Exit fullscreen mode

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;
Enter fullscreen mode

Exit fullscreen mode

and is used like this:

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

Exit fullscreen mode

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'
Enter fullscreen mode

Exit fullscreen mode

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>;
};
Enter fullscreen mode

Exit fullscreen mode

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();
});
Enter fullscreen mode

Exit fullscreen mode

Let’s run this:

> jest

 PASS  src/testing.test.tsx
  ✓ changes text on click (53 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        4.012 s
Ran all test suites.
Enter fullscreen mode

Exit fullscreen mode

May your tests catch all the bugs!



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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

GIPHY App Key not set. Please check settings

Go netflow, capture process in/out traffic, similar to c Nethogs

OpenBSD 7.0 Released – LowEndBox