Error in vitest config with NextJS13: failed to resolve import - reactjs

I'm integrating vitest with a NextJS13 app, but running into problems with a simple test run.
Not sure what the problem is, I tried to do some tweaking with the vitest.config.ts but no luck. I tried adding the dir option, modified the include option to grab files from the source file but no luck.
I thought maybe it had to do with the tsconfig.json file, but it's still outputting the error.
This is the directory of the file
Here are the files in question:
vitest.config.ts
/// <reference types="vitest" />
import { defineConfig } from 'vitest/config'
import react from '#vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
setupFiles: 'setupTests.ts',
// dir: './src'
// includeSource: ['src/**/*.{js,ts,tsx}'],
},
});
tsconfig.json
{
"compilerOptions": {
"target": "ES2017",
"lib": ["es6", "dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "ESNEXT",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
"incremental": true,
// "paths": {
// "src": ["./src/*"]
// }
},
"exclude": ["node_modules"],
"include": ["vitest.config.ts","**/*.ts", "**/*.tsx", "next-env.d.ts",
"next.config.js"]
}
DataTable.test.tsx - src/common/components/DataTable/DataTable.test.tsx
// components
import DataTable from 'src/common/components/DataTable';
// dependencies
import {describe, it} from 'vitest'
import {screen, render} from '#testing-library/react'
describe('DataTable test', () => {
it('render the app', () => {
// arrange
render(<DataTable />)
// act
const assetText = screen.getByText("asset")
// assert
// expect(assetText).toBeInTheDocument()
})
})
DataTable component - src/common/components/DataTable/DataTable.tsx
export const DataTable = () => {
return (
<div>
<h1>assets</h1>
</div>
);
};
Index.tsx - src/common/components/DataTable/index.tsx
import { DataTable } from 'src/common/components/DataTable/DataTable';
export default DataTable;
I'm new to vitest and nextjs, your help/guidance will be appreciated.

There are two things needed here to make the import DataTable from 'src/common/components/DataTable'; import work:
TypeScript needs the paths compilerOption set.
Vite needs to have the same alias set.
The "paths" compilerOption in TypeScript will need a /* on the end of the "src" key to be able to resolve paths underneath the "src" directory (see tsconfig.json reference):
{
"compilerOptions": {
"paths": {
"src/*": ["./src/*"]
}
}
}
Vite/Vitest will also need to know how to resolve "src/common/components/DataTable", and that would usually be done with the resolve.alias setting, but rather than duplicating the alias here, you could also use a plugin, like vite-tsconfig-paths, to add the path aliases if finds in relevant tsconfig.json files:
import react from "#vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
export default {
plugins: [tsconfigPaths(), react()],
};

Related

RTL Error cannot find module #assets/logo-filled.png from src/component/index.tsx

Getting below error
cannot find module #assets/logo-filled.png from src/component/index.tsx
if I replace import logo from #assets/logo-filled.png with import logo from ../../../assets/logo-filled.png
This line of import is working fine while running the application. But while running the test scripts npm test -> react-scripts test the above error is popping up
test.test.tsx
import React from "react";
import { render } from "#testing-library/react";
import {Logo} from "../components/logo";
describe('Describe...', () => {
test('test ...', () => {
console.log('the test');
render(<Logo withoutText={true}/>);
expect(1).toBe(1)
});
});
Logo.tsx
import React from 'react';
import { Stack, Image, Text } from '#fluentui/react';
import { logoContainerStyles, textStyles } from './styles';
// ERROR thrown by below line
import logo from '#assets/icon-16.png';
// Works fine with below commented line
/** import logo from '../../../assets/icon-16.png'; */
export interface LogoProps {
withoutText?: boolean;
}
export const Logo: React.FC<LogoProps> = props => {
const { withoutText } = props;
return (
<Stack styles={logoContainerStyles} horizontalAlign="center">
<Image src={logo} alt="logo" width={64} shouldFadeIn={false} />
<Text styles={textStyles} variant="xLarge">
Hello StackOverflow
</Text>
</Stack>
);
};
ts.config.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"moduleResolution": "node",
"allowJs": true,
"jsx": "react",
"lib": ["esnext", "dom", "dom.iterable"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"outDir": "dist",
"noImplicitReturns": true,
"pretty": true,
"typeRoots": ["node_modules/#types"],
"baseUrl": "src",
"paths": {
"#assets/*": ["../assets/*"]
}
},
"include": ["src"]
}
Any kind of help will much be appreciated.
We need a little bit more details on your setup. But it has to do with tsconfig.json and it's paths property. Also take a closer look at jest property in package.json, it is used to define aliases for tests.
In the jest.config.js use ,
moduleNameMapper: {
// Handle other modules
// Handle module aliases
"^#/assets/(.*)$": "<rootDir>/assets/$1",
},
If you want use module aliases with TypeScript
"paths": {
"#/assets/*": ["assets/*"],
}

React component is imported as an object in test environments

I am writing a React application using Vite and Typescript, with automated tests using Vitest. One of my components uses react-viewer, and looks something like this
import Viewer from "react-viewer";
import { useState } from "react";
type MyComponentProps = {
url: string
};
export const MyComponent = ({ url }: MyComponentProps) => {
const [isVisible, setVisible] = useState(false);
return (
<div>
/* Some other components above which toggle the viewer */
<Viewer
visible={isVisible}
onClose={() => setVisible(false)}
images={[{ src: url, alt: "", downloadUrl: url }]}
noNavbar
/>
</div>
);
};
This works when I run the application with the development server. However, in the automated tests, I get this error:
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.
When I logged Viewer when running the tests, I got this
Object [Module] { default: [Function (anonymous)] }
But when I looked at the same log statement when running it in development, I got this
function default(A3)
It looks like in development mode, Viewer is imported as a React component (as I'd expect). But in the test environment, it's incorrectly imported as an object.
I found this issue on Vite's Github, which has a workaround. I tried following the same pattern, which works
import React from "react";
import Viewer from "react-viewer";
import ViewerProps from "react-viewer/lib/ViewerProps";
type PictureViewerComponent = (props: ViewerProps) => React.ReactPortal;
type ViewerComponent = PictureViewerComponent & {
default?: PictureViewerComponent;
};
export const PictureViewer: PictureViewerComponent =
(Viewer as ViewerComponent)?.default ?? Viewer;
But I'm a bit worried that I'll see this issues with other components that use default imports. It feels like there is an underlying issue with either my tsconfig.json or vite.config.ts.
Here is the tsconfig.json
{
"exclude": [
"node_modules",
"build",
"coverage",
"storybook-static",
"public"
],
"compilerOptions": {
"types": ["vite/client", "vitest/globals"],
"paths": {
"#acme/*": ["./src/*"]
},
"allowJs": true,
"baseUrl": ".",
"typeRoots": ["./node_modules/#types"],
"target": "ES6",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "ES6",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noImplicitAny": true,
"noEmit": true,
"jsx": "react-jsx"
}
}
And here is the vite.config.ts
/// <reference types="vitest" />
import * as path from "path";
import react from "#vitejs/plugin-react";
import { defineConfig, splitVendorChunkPlugin } from "vite";
import eslint from "vite-plugin-eslint";
const config = () => {
return defineConfig({
base: "/acme/",
plugins: [react(), eslint(), splitVendorChunkPlugin()],
resolve: {
alias: [
{
find: /~(.+)/,
replacement: path.join(process.cwd(), "node_modules/$1")
},
{
find: /#acme\//,
replacement: path.join(process.cwd(), "./src") + "/"
},
{
// Workaround due to https://github.com/vitejs/vite/issues/9200
find: "path",
replacement: "path-browserify"
}
]
},
server: {
host: "0.0.0.0",
port: 80,
base: "/"
},
esbuild: {
loader: "tsx",
logOverride: { "this-is-undefined-in-esm": "silent" }
},
build: {
outDir: "build"
},
test: {
globals: true,
environment: "jsdom",
mockReset: true,
restoreMocks: true,
clearMocks: true,
setupFiles: "./src/setupTests.js",
coverage: {
reporter: ["text", "html"],
exclude: [
"node_modules/",
"src/setupTests.js",
"**/{__tests__,__stories__,test,storybook}/"
]
}
}
});
};
export default config;
I tried toggling the moduleResolution, esModuleInterop, and isolatedModules options in tsconfig.json, without success. I also tried setting test.deps.interopDefault to false in vite.config.ts, also without success.
I also tried importing the component like this, also without success
import { default as Viewer } from "react-viewer";
Any help would be appreciated! Thank you in advance

React TypeScript - Exporting more than one component

Given this folder structure:
├──components/
| └─Elements/
| └─Button/
| └─Input/
| └─index.ts
| └─Home/
| └─home.tsx
I would like to export Button and Input so I can access them from the home component by doing:
home.tsx
import { Button, Input } from '#elements/'
I have tried this:
index.ts (in Elements folder)
export { Button } from './Button/button';
export { Input } from './Input/input';
But it does not work, I get: Cannot find module '#elements/' or its corresponding type declarations. even thou the resolve alias does work.
tsconfig.json
{
"compilerOptions": {
"outDir": "../public",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"baseUrl": "./",
"rootDir": "./",
"typeRoots": ["./node_modules/#types", "./src/#types"],
"paths": {
"#src/*": ["src/*"],
"#images/*": ["src/images/*"],
"#styles/*": ["src/styles/*"],
"#components/*": ["src/components/*"],
"#elements/*": ["src/components/Elements/*"],
},
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"importsNotUsedAsValues": "preserve"
},
"exclude": ["node_modules", "webpack"]
}
webpack.config.babel.js
...
resolve: {
alias: [
'#src': join(rootDir, '/src'),
'#images': join(rootDir, '/src/images'),
'#assets': join(rootDir, '/src/assets'),
'#styles': join(rootDir, '/src/styles'),
'#components': join(rootDir, '/src/components'),
'#elements': join(rootDir, '/src/components/Elements')
]
extensions: ['.tsx', '.ts', '.js', '.jsx']
},
I found the solution to be removing the * from the path declaration on tsconfig.json
This
"#elements/*": ["src/components/Elements/"]
Instead of this
"#elements/*": ["src/components/Elements/*"]
And then importing it by using
import { Button, Input } from '#elements/'
or
"#elements": ["src/components/Elements"]
import { Button, Input } from '#elements'
import { Button } from './Button/button';
import { Input } from './Input/input';
should probably be
export { Button } from './Button/button';
export { Input } from './Input/input';
If they are default exports, you have to do this.
export { default as Button } from './Button/button';
export { default as Input } from './Input/input';
Module resolution in Typescript is the process that the compiler uses to figure out what an import refers to. You can read more on module resolution in the Typescript Handbook.
import { Button } from './Button/button';
This type of import is called a relative import. A relative import is one that starts with /, ./ or ../.
Any other import is considered non-relative. Some examples include:
import * as $ from "jquery";
import { Component } from "#angular/core";
There is two module resolution strategy in typescript which is node and classic. By default module resolution is set to classic.
Set the module resolution to node would solve your problem.
"moduleResoution": "node"
Note: node module resolution is the most-commonly used in the TypeScript community and is recommended for most projects. If you are having resolution problems with imports and exports in TypeScript, try setting moduleResolution: "node" to see if it fixes the issue.

configure storybook docs addon to display theme.dark

I'm trying to configure storybook to display a dark theme, and so far i didn't find any solution to this problem.
so i followed the storybook docs,
and i'v setup the manager.js file like so:
// .storybook/manager.js
import { addons } from '#storybook/addons';
import { themes } from '#storybook/theming';
addons.setConfig({
theme: themes.dark,
});
i'v also printed the theme to the console so i see it arrives:
it may be worth mentioning that when the browser reload this file is read,
but if i change the source code and save the hot-reload don't work..
Here's how i specified the same theme for docs in .storybook/preview.js:
// .storybook/preview.js
import React from "react";
import { appTheme } from "../src/Common/theme";
import { ThemeProvider } from "styled-components";
import { makeDecorator } from "#storybook/addons";
import { addParameters, addDecorator } from "#storybook/react";
import defaultNotes from "./general-docs.md";
import { themes } from "#storybook/theming";
export const parameters = {
docs: {
theme: themes.dark
}
};
addParameters({
notes: defaultNotes,
options: {
showRoots: true
}
});
const withStyledTheme = storyFn => {
return <ThemeProvider theme={appTheme}>{storyFn()}</ThemeProvider>;
};
const styledThemed = makeDecorator({
name: "styled-theme",
wrapper: withStyledTheme
});
addDecorator(styledThemed);
addParameters(parameters);
this is how the main.js file looks like:
module.exports = {
stories: ["../src/**/*.stories.(ts|tsx|js|jsx|mdx)"],
addons: [
"#storybook/preset-create-react-app",
"#storybook/addon-actions",
"#storybook/addon-links",
"#storybook/addon-actions/register",
"#storybook/addon-knobs/register",
"#storybook/addon-notes/register-panel",
"storybook-addon-designs",
"#storybook/addon-docs/preset"
]
};
i work with typescript in this project,
so here is the tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"baseUrl": "src"
},
"include": ["src"]
}
What am i missing here ?
I've been stuck on the same problem for a while, but the solution was in the doc all along:
//.storybook/preview.js
import { themes } from '#storybook/theming';
export const parameters = {
docs: {
theme: themes.dark,
},
};
So yeah, you have to specify the theme twice, in two different files

TypeScript - Module '"*.svg"' has no exported member 'ReactComponent

I'm trying to import an .svg file as a React component with TypeScript.
According to the React docs, this is done like so:
import { ReactComponent as Icon } from './Icon.svg';
Following the TypeScript docs, I've added this:
// custom.ts.d
declare module '*.svg' {
const content: any;
export default content;
}
and
// tsconfig.json
{
...
"files": [
"custom.d.ts"
]
}
The SVG is rendering. But I'm getting a TypeScript error:
[ts] Module '"*.svg"' has no exported member 'ReactComponent'. [2305]
Here is my full tsconfig file if that helps:
{
"compileOnSave": false,
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"outDir": "./dist",
"strict": true,
"jsx": "react",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"removeComments": false,
"preserveConstEnums": true,
"sourceMap": true,
"skipLibCheck": true,
"esModuleInterop": true,
"baseUrl": ".",
"plugins": [
{
"name": "typescript-styled-plugin"
}
],
"typeRoots": ["./node_modules/#types"],
"lib": ["dom", "es2015", "es2017"]
},
"exclude": ["node_modules", "dist"],
"files": [
"custom.d.ts"
]
}
Thank you!
You have export default content; But you are doing a named import (not a default import).
Fix
Change declaration to export the name you are importing:
declare module '*.svg' {
import React = require('react');
export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
const src: string;
export default src;
}
Additional
Recommend not using files in tsconfig.json. Instead just use include
2019 In addition to #basarat's answer: React.SFC is depracated now consider using React.FunctionComponent like:
declare module '*.svg' {
import React = require('react');
export const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
const src: string;
export default src;
}
You're naming the custom types as custom.ts.d and it should call custom.d.ts once the ts compiler is configured to process ts|tsx extensions.
After that, you should be able to reference it into your tsconfig.json include section just like this:
"include": ["custom.d.ts"]
Pull request link
declare module '*.svg' {
import React = require('react');
export const ReactComponent: React.SFC<React.SVGProps<SVGSVGElement>>;
const src: string;
export default src;
}
Just because it was helpful to me and somewhat related. If you're getting an error with Jest, check this out

Resources