solid-testing-library: Can't test component with Vitest
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.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 25 (1 by maintainers)
Commits related to this issue
- fix(js-vitest,ts-vitest): force vitest to transform solid-testing-library For why this is necessary, see solidjs/solid-testing-library#10. — committed to thislooksfun/templates by thislooksfun 2 years ago
- Force vitest to transform solid-testing-library For why this is necessary, see solidjs/solid-testing-library#10. — committed to thislooksfun/templates by thislooksfun 2 years ago
After hours and hours of debugging I finally figured out the issue! It’s been reported as https://github.com/vitest-dev/vitest/issues/1588. In the meantime, there is a workaround:
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 theexports.browser.development.import
path. When runningvitest
, however, the wrong package gets imported. Instead ofexports.browser.development.import
, the system instead importsexports.node.import
. Since this is a server file it has no need for any client-side APIs, and thus doesn’t exporthydrate
orrender
.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 thedepsExternal
patterns, then use Node’s defaultimport()
rather than transforming the file ourselves”. The problem is that the defaultdepsExternal
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 namedsolid-testing-library/dist/index.mjs
, which importshydrate
andrender
fromsolid-js/web
. When Vitest gets to this file it matches against thedepsExternal
pattern which tells it to use Node’simport
. Node then tries to resolve the import tosolid-js/web
the way it knows how: by importing the file defined in theexports.node.import
key. Since this is a server file, it doesn’t contain an export namedhydrate
, 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 calleddeps
which allows you to override which modules should and should not be externalized. The second piece of the puzzle is again thets-vitest
template, which setsdeps.internalize
to[/solid-js/]
. This is intended to forcesolid-js
to be transformed, rather than going through Node’simport
, which is does, but only whensolid-js
is itself imported from a transformed file. The reason it works with pnpm though is because of the install paths. When usingnpm
,solid-testing-library
is imported fromnode_modules/solid-testing-library/dist/index.mjs
. But when usingpnpm
it’s imported fromnode_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 vianpm
, the path doesn’t containsolid-js
, and thus doesn’t get told to externalize.This is why the short fix is to add
/solid-testing-library/
to thedeps.internalize
array fixes the issue, it tellsvitest
to always internalizesolid-testing-library
, thus ensuring it reads the right exports and thus finds thehydrate
and/orrender
functions it needs.@atk Not quite. In order for this to work we need
_shouldExternalize(id)
to returnfalse
. There are only 3 cases where that happens:id
matchesdeps.inline
id
matchesdefaultInline
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:isNodeBuiltin(id)
must befalse
– this is easy, since it’s not a node builtin.id
must NOT be adata:
url – also easy, it’s a fileid
must NOT matchdeps.external
– again, easyid
must NOT match thedepsExternal
pattern – this means the file can’t end in.cjs.js
or.mjs
id
MUST NOT be in**/dist/**
(easy) OR**/node_modules/**
(impossible)isValidNodeImport(id)
must returnfalse
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 thatisValidNodeImport("path/to/solid-testing-library/index/file")
returnsfalse
. So lets look at when it does so:id
has a protocol that isn’t in the list of allowed protocols (defaults tonode:
,file:
, ordata:
).js
.es.js
or.esm.js
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 forpackage.type === "module"
, which would cause it to returntrue
.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 withvitest
(specifydeps.inline
either to includesolid-testing library
, to include all ofnode_modules
, or to just betrue
which inlines everything), and hope that thevitest
team comes up with some solution on their end for this to be nicer in the future.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
I had the same issue with the ts/vitest template.
npm test
would fail. Tests pass with pnpm.I am investigating further, but this issue seems to be on the side of vitest, not solid.
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'