Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't test component with Vitest #10

Closed
KaiHuebner opened this issue Mar 14, 2022 · 25 comments
Closed

Can't test component with Vitest #10

KaiHuebner opened this issue Mar 14, 2022 · 25 comments

Comments

@KaiHuebner
Copy link

Hi,

I'm trying to test my SolidJS library with Vitest. But I'm getting the following error:

 FAIL  src/index.test.tsx [ src/index.test.tsx ]
SyntaxError: The requested module 'solid-js/web' does not provide an export named 'hydrate'
 ❯ async src/index.test.tsx:8:31
      4| test('render', async () => {
      5|   await render(() => <div>Hello</div>)
      6| })
       |   ^
      7| 

I'm using:

"solid-js": "^1.3.12"
"vite": "^2.8.6",
"vite-plugin-solid": "^2.2.6",
"vitest": "^0.6.1"
"solid-testing-library": "^0.3.0",

vite.config.js

/// <reference types="vitest" />
/// <reference types="vite/client" />

import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'

export default defineConfig({
  test: {
    environment: 'jsdom',
    transformMode: {
      web: [/\.[jt]sx?$/],
    },
    // solid needs to be inline to work around
    // a resolution issue in vitest:
    deps: {
      inline: [/solid-js/],
    },
    // if you have few tests, try commenting one
    // or both out to improve performance:
    // threads: false,
    // isolate: false,
  },
  plugins: [solid()],
  resolve: {
    conditions: ['development', 'browser'],
  },
})

I'm new to testing. Is my configuration correct or is it bug?
I could not find a lot of documentation on testing SolidJS with Vitest.

Thanks.

@ryansolid
Copy link
Member

Yeah I haven't tried this one yet. What it sounds like is the environment is being mismatched. Like you are getting the server version of Solid and expecting the client. Those export conditions look right. development or browser should get the client build. We have solid condition as well so that it compiles on the fly rather than using precompiled client versions, but I wouldn't expect that to be an issue here.

@atk if you have any ideas?

@atk
Copy link
Collaborator

atk commented Mar 14, 2022

Did you install jsdom alongside vitest? If document is missing, there's no hydrate call.

@KaiHuebner
Copy link
Author

Hi guys, thanks for the quick response.

Yes, I installed jsdom version 19.0.0.

I'm not using any server side rendering, so I actually don't need the hydrate function.

I was thinking it has something to do with the import statement in:
node_modules/solid-testing-library/dist/index.mjs

When I change line 2 to: import { render as solidRender } from "solid-js/web";

Then I get a different error message:
SyntaxError: The requested module 'solid-js/web' does not provide an export named 'render'

@atk
Copy link
Collaborator

atk commented Mar 14, 2022

Together with another user (dp on our discord), I found a potential reason: if you install your project with yarn or npm, it seems to fail with that exact reason, but if you use pnpm, for some reason yet unknown, it works. I'm still investigating why this could possibly be the case. In the meantime, you can try pnpm in order to unblock you and I'll come back to this issue once I know more.

@KaiHuebner
Copy link
Author

Wow, that's a strange issue. Yes, I'm using yarn as package manager.
I haven't looked into package manager for a while, but it seems that pnpm is the better choice anyway.
Everything seems to work after I switched my project to pnpm.

Dankeschön. From a fellow German living in Australia.

@thm76
Copy link

thm76 commented Mar 23, 2022

I've run into the same issue and cannot use pnpm (I'm behind a corporate proxy on Windows, and can't get it to install)

I would appreciate if this could be reopened and further investigated, please

@atk
Copy link
Collaborator

atk commented Mar 24, 2022

I am investigating further, but this issue seems to be on the side of vitest, not solid.

@mathieuprog
Copy link

I had the same issue with the ts/vitest template. npm test would fail. Tests pass with pnpm.

@thislooksfun
Copy link

After hours and hours of debugging I finally figured out the issue! It's been reported as vitest-dev/vitest#1588. In the meantime, there is a workaround:

 /* @file: vite.config.js */
 
 /* snip... */
 export default defineConfig({
   /* snip... */
   test: {
     /* snip... */
     deps: {
       inline: [
        /* snip... */
+       /solid-testing-library/,
      ],
     },
   },
 });

If you do that it will work 100% of the time, regardless of which package management tool you use (npm, yarn, pnpm, etc.)


Want to know why?

If you only care about fixing the issue then you're done with this comment. If, on the other hand, you're curious as to what actually happened, then read on.

Why the error 'solid-js/web' does not provide an export named 'hydrate'?

First, some context. Vite, like many bundlers, supports a system called "conditional exports", which allows you to define a set of exports from your package that should be used in different situations. solid-js/web uses this system to export different files based on whether you are on the server or the client. This is useful for keeping bundle size down without having to manually specify different imports for every place the code will be used (especially problematic in ssr environments, where the same code runs on the client and the server), and ensuring that the code actually runs (client bundles can include browser APIs and polyfills, for example, while server code doesn't need it).

Solid's ts-vitest template defines the resolve conditions to be ["development", "browser"], which should import Solid via the exports.browser.development.import path. When running vitest, however, the wrong package gets imported. Instead of exports.browser.development.import, the system instead imports exports.node.import. Since this is a server file it has no need for any client-side APIs, and thus doesn't export hydrate or render.

Why did it work with pnpm then?

Ah, what an excellent question. Deep inside of Vitest's module resolution code is a seemingly simple function: _shouldExternalize(id). This function answers a simple question: do we need to transform the file before exporting it, or can we "externalize" the import (ask Node to do it for us). The logic in there isn't too complex, but the part we care about is this line: if (matchExternalizePattern(id, depsExternal)) return id. This says "if the id (which is a filepath) matches any of the depsExternal patterns, then use Node's default import() rather than transforming the file ourselves". The problem is that the default depsExternal patterns includes a match for /\.mjs$/, which means that any file that ends with .mjs is handed off to Node to resolve. This is a problem because this library, as @KaiHuebner guessed, exports a file named solid-testing-library/dist/index.mjs, which imports hydrate and render from solid-js/web. When Vitest gets to this file it matches against the depsExternal pattern which tells it to use Node's import. Node then tries to resolve the import to solid-js/web the way it knows how: by importing the file defined in the exports.node.import key. Since this is a server file, it doesn't contain an export named hydrate, and so the import fails.

The reason it works when installed via pnpm is because of another check in earlier in _shouldExternalize(). Vitest provides an option called deps which allows you to override which modules should and should not be externalized. The second piece of the puzzle is again the ts-vitest template, which sets deps.internalize to [/solid-js/]. This is intended to force solid-js to be transformed, rather than going through Node's import, which is does, but only when solid-js is itself imported from a transformed file. The reason it works with pnpm though is because of the install paths. When using npm, solid-testing-library is imported from node_modules/solid-testing-library/dist/index.mjs. But when using pnpm it's imported from node_modules/.pnpm/solid-testing-library@0.3.0_solid-js@1.4.5/node_modules/solid-testing-library/dist/index.mjs. If you look closely, you can see that path contains "solid-js", which means that it is caught by the /solid-js/ regex and told to internalize. When installed via npm, the path doesn't contain solid-js, and thus doesn't get told to externalize.

This is why the short fix is to add /solid-testing-library/ to the deps.internalize array fixes the issue, it tells vitest to always internalize solid-testing-library, thus ensuring it reads the right exports and thus finds the hydrate and/or render functions it needs.

thislooksfun added a commit to thislooksfun/templates that referenced this issue Jul 4, 2022
thislooksfun added a commit to thislooksfun/templates that referenced this issue Jul 4, 2022
@lcmen
Copy link

lcmen commented Jul 4, 2022

Thank you @thislooksfun for providing a fix and documenting the whole process 🙇‍♂️ . It was very educational for me and helped me better understand how Vite works intensely.

@atk
Copy link
Collaborator

atk commented Jul 4, 2022

So basically, it should work if we change the export to dist/esm/index.js?

@thislooksfun
Copy link

thislooksfun commented Jul 5, 2022

@atk Not quite. In order for this to work we need _shouldExternalize(id) to return false. There are only 3 cases where that happens:

  1. If the id matches deps.inline
  2. If the id matches defaultInline
  3. If all other checks fail

Option 1 is the quick fix proposed above, but it requires manual configuration. Option 2 is out because we can't influence defaultInline, nor can we make the import be one of those options. So we're left with option 3: bypass all the other checks.

In order to reach the end of _shouldExternalize() we need the following:

  1. isNodeBuiltin(id) must be false -- this is easy, since it's not a node builtin.
  2. id must NOT be a data: url -- also easy, it's a file
  3. id must NOT match deps.external -- again, easy
  4. id must NOT match the depsExternal pattern -- this means the file can't end in .cjs.js or .mjs
  5. EITHER:
    • id MUST NOT be in **/dist/** (easy) OR **/node_modules/** (impossible)
    • OR isValidNodeImport(id) must return false

We can control everything up to the final check, but since the file will always be inside of node_modules we are forced to go a step further: ensure that isValidNodeImport("path/to/solid-testing-library/index/file") returns false. So lets look at when it does so:

  1. If id has a protocol that isn't in the list of allowed protocols (defaults to node:, file:, or data:)
  2. If the extension is anything other than .js
  3. If the path ends with .es.js or .esm.js
  4. If the actual code has signs of ESM syntax but NOT signs of CJS syntax

Option 1 is ruled out because we can't control the protocol. Option 2 is possible, but the only extensions options that I know of are .ts, .tsx, .js, .jsx, .cjs and .esm, and of those all but .cjs and .js are already ruled out, and you can't (or at least really shouldn't) put ESM code in a .cjs file, so that leaves just .js. Option 4 is also ruled out because just before it is a check for package.type === "module", which would cause it to return true.

All of this comes together to mean that the only way I can see to get around this without needing to set deps.inline manually is to have the file end in .esm.js. However I can't comfortably recommend this solution. That behavior is several dependencies deep and is NOT one of the API guarantees of either package, so while it should work for now, I don't feel confident that it will continue working in the future.

I think the most reliable way forwards is to have prominent documentation here in solid-testing-library showing how to set it up with vitest (specify deps.inline either to include solid-testing library, to include all of node_modules, or to just be true which inlines everything), and hope that the vitest team comes up with some solution on their end for this to be nicer in the future.

@atk
Copy link
Collaborator

atk commented Jul 5, 2022

I have asked in the vitest discord if there's some way to expose the setting in a more direct manner, which would be allowing for a reasonable preset configuration.

@Ladvace
Copy link

Ladvace commented Oct 10, 2022

I still get this error and unfortunately I didn't fix it with @thislooksfun's solution

@mathieuprog
Copy link

@Ladvace how if you just remove deps.inline from vite.config.ts?

@Ladvace
Copy link

Ladvace commented Oct 13, 2022

@Ladvace how if you just remove deps.inline from vite.config.ts?

I get the same error

@mathieuprog
Copy link

@Ladvace can you compare with my config:
#18

@Ladvace
Copy link

Ladvace commented Oct 13, 2022

this is my config:

export default defineConfig({
  mode: process.env.NODE_ENV,
  root: __dirname,
  plugins: [solidPlugin(), Unocss()],
  envDir: resolve(__dirname, "../../../../"),
  base: "./",
  build: {
    target: "esnext",
    emptyOutDir: true,
    outDir: "../../dist/mainWindow",
    sourcemap: true,
  },
  resolve: {
    alias: {
      "@": join(__dirname, "src"),
    },
    conditions: ['development', 'browser'],
  },
  server: {
    port: pkg.env.PORT,
  },
  test: {
    environment: "jsdom",
    transformMode: {
      web: [/.[jt]sx?/],
    },
    globals: true,
    threads: false,
    isolate: false,
  },

you are using happy-dom, I'm using jsdom

@mathieuprog
Copy link

mathieuprog commented Oct 13, 2022

Can you try to switch to happy dom and see if that resolves that particular error?

@Ladvace
Copy link

Ladvace commented Oct 13, 2022

I did and unfortunately, nothing changed, do I need only download it and add the string "happy-dom" or is there any other config?

@mathieuprog
Copy link

You can check my repo. It's a dependency to add. If the error persists, then it's not about jsdom and look further into the differences in config.

@mathieuprog
Copy link

Make also sure to update vitest and vite to latest version

@Ladvace
Copy link

Ladvace commented Oct 13, 2022

yeah I'm giving it a look and I'm checking, I don't if this could related to the fact that I'm using pnpm instead of npm. I'll check, thanks

@Ladvace
Copy link

Ladvace commented Oct 13, 2022

After some digging, I found the problem but I didn't really understand why it was not working, basically, I tried the same configurations but in a new plain solid project, and I saw that when you run a test, in the test folder a __snapshots__ folder appear with some files (depending on how many test you have) as in the example below

image

in my case for some reason (I still didn't get why) this folder it's not created, but if I copy this folder manually from the plain works, the test runs correctly.

@mathieuprog
Copy link

I don't see any snapshots on my side though

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants