So very new to the community, because I usually find the answer to my questions just by searching the community. But this one I can't figure out.
My Setup
I am using NextJS just as a framework application, so I don't use any of its API abilities. So far no tests implemented, but now I got more time on my hands. I want to implement tests using Jest and later on implementing some e2e such as playwright. But for now Jest.
The Problem
So I am implementing Jest in my application, but somehow cannot run the test because I am using a Dynamic Icon Component within my application.
So what is this icon component I am talking about. I got tired of importing icons one by one in each page. So decided to create one component that has the name attribute, so I can pass it any SVG name and it would just render it on my screen.
// Icon.jsx
import Company from './icons/company.svg';
const iconTypes = {
'company': Company,
}
const Icon = ({ name, ...props }) => {
let Icon = iconTypes[name];
return <Icon {...props} />;
};
export default Icon;
// next.config.json
module.exports = {
webpack(config) {
config.resolve.fallback = { fs: false };
config.module.rules.push({
test: /\.svg$/,
use: ["#svgr/webpack"]
});
return config;
},
eslint: {
// Warning: This allows production builds to successfully complete even if
// your project has ESLint errors.
ignoreDuringBuilds: true,
},
reactStrictMode: true,
}
So this pretty much allows me to import an Icon by simply saying on anypage:
<Icon name="company" />
Now when implementing Jest, this component causes a lot of troubles with running my tests. I get the following error, when running my test:
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check your code at Icon.jsx:119.
...
117 | console.log(1111111, name)
118 | let Icon = iconTypes[name];
> 119 | return <Icon {...props} />;
| ^
120 | };
121 |
122 | export default Icon;
at printWarning (node_modules/react/cjs/react.development.js:220:30)
at error (node_modules/react/cjs/react.development.js:196:5)
at createElementWithValidation (node_modules/react/cjs/react.development.js:2215:7)
at Icon (res/images/Icon.jsx:119:10)
at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:14985:18)
at mountIndeterminateComponent (node_modules/react-dom/cjs/react-dom.development.js:17811:13)
at beginWork (node_modules/react-dom/cjs/react-dom.development.js:19049:16)
So this error message I usually get when I pass in a name of an icon that does not exist.
So at this point I'm a bit confused on how to handle this? Is there a way to ignore this component when running the test? Or run it somehow.
Some things I already tried
//jest.config.js
module.exports = {
"transform": {
"^.+\\.jsx?$": "babel-jest",
"^.+\\.svg$": "<rootDir>/svgTransform.js"
},
setupFilesAfterEnv: ["./jest.setup.js"],
moduleNameMapper: {
"^#/libs(.*)$": "<rootDir>/libs$1",
"^#/res(.*)$": "<rootDir>/res$1",
"^#/store(.*)$": "<rootDir>/store$1",
"^#/pages(.*)$": "<rootDir>/pages$1",
'^.+\\.(css|less|scss)$': '<rootDir>/styleMock.js'
},
};
// svgTransform.js
module.exports = {
process() {
return 'module.exports = {};';
},
getCacheKey() {
// The output is always the same.
return 'svgTransform';
},
};
Solution
Let me share my solution, so others can reference it.
So the issue was still related to svg importing in Jest. Seems like my svgTransform.js was just not doing what it was supposed to do. So I replaced it with a package called jest-transformer-svg and managed to solve it.
https://www.npmjs.com/package/jest-transformer-svg
Related
I use the the IDE PhpStorm from JetBrains, which also includes WebStorm.
When coding a React/Next.js app it's offen necessary to unpack object properties of function parameters.
But currently the IDE messes up the formation of the properties, if the are in multiple lines.
The desired output:
export default function SomeComponent({
children,
home,
}: SomeComponentProps) {
return (
<>Something...</>
);
}
But thas the actual outputted, if I use reformat:
export default function SomeComponent({
children,
home,
}: SomeComponentProps) {
return (
<>Something...</>
);
}
Within the IDE settings I use the Google JavaScript Style Guide for JavaScript and TypeScript:
Within a Next.js project I tried a combination of ESLint and prettier:
{
"extends": [
"eslint:recommended",
"google",
"next/core-web-vitals",
"prettier"
],
"rules": {
"require-jsdoc": "off"
}
}
Is there anyway to archive the desired output?
please try disabling Align when multiline for Function declaration parameters in Settings | Editor | Code Style | JavaScript | Wrapping and Braces
I want to make a bilingual form on my website and followed this tutorial. I'm not sure what is undefined here, although I think it has something to do with gatsby-plugin-intl (I thought maybe it's because I don't have a json file for my non-English content, but I added a zh-TW.json and still get the same error. I also get the same error when calling localhost:8000 instead of localhost:8000/contact). The errors only came up after running the npm run extract -- 'src/**/*.js*' --out-file src/intl/en.json --format simple command.
gatsby-config.js
const supportedLanguages = [
{ id: 'en', label: 'English' },
{ id: 'zh-TW', label: '中文 (繁體)' },
]
const defaultLanguage = 'en'
module.exports = {
siteMetadata: {
title: `Gatsby Markdown Blog`,
description: `Learn how to make a blog with Gatsby and Markdown posts.`,
},
plugins: [
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.mdx`, `.md`],
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/posts`,
},
},
{
resolve: `gatsby-plugin-intl`,
options: {
path: `${__dirname}/src/intl`,
languages: supportedLanguages,
defaultLanguage: defaultLanguage,
redirect: true, // switch to false when zh content ready to prevent gatsby-plugin-intl from auto-redirecting to default language versions
},
},
],
}
browser
React components in Gatsby must render successfully in the browser and in a node.js environment. When we tried to render your page component in node.js, it errored.
URL path: /contact/
File path: undefined
terminal
warn The path "/contact/" errored during SSR.
Edit its component undefined to resolve the error.
ERROR
Cannot read property 'split' of undefined
TypeError: Cannot read property 'split' of undefined
- render-dev-html-child.js:65 parseError
[blog]/[gatsby]/src/utils/dev-ssr/render-dev-html-child.js:65:26
- render-dev-html-child.js:166
[blog]/[gatsby]/src/utils/dev-ssr/render-dev-html-child.js:166:23
- new Promise
- render-dev-html-child.js:135 Object.exports.renderHTML
[blog]/[gatsby]/src/utils/dev-ssr/render-dev-html-child.js:135:3
- processChild.js:155 execFunction
[blog]/[jest-worker]/build/workers/processChild.js:155:17
- processChild.js:139 execHelper
[blog]/[jest-worker]/build/workers/processChild.js:139:5
- processChild.js:143 execMethod
[blog]/[jest-worker]/build/workers/processChild.js:143:5
- processChild.js:64 process.<anonymous>
[blog]/[jest-worker]/build/workers/processChild.js:64:7
- node:events:369 process.emit
node:events:369:20
- source-map-support.js:495 process.emit
[blog]/[source-map-support]/source-map-support.js:495:21
src/components/Form.js
import React from "react";
import { FormattedMessage, useIntl } from "gatsby-plugin-intl";
const Form = () => {
const intl = useIntl(); // hook; pass in object that contains id and default message to ensure that
return (
<div>
<label for="email">
<FormattedMessage id="email_label" defaultMessage="Email address" /> {/* pass id and defaultMessage down as props to render new string inside of a react fragment child element */}
</label>
<input
type="email"
id="email"
placeholder={intl.formattedMessage({
id: "email_input",
defaultMessage: "Email address",
})}
/>
<label for="Password">
<FormattedMessage id="password_label" defaultMessage="Password" />
</label>
<input type="password" id="password" />
<button type="submit" onSubmit={this.handleSubmit}>
<FormattedMessage id="submit_button" defaultMessage="submit" />
</button>
</div>
);
};
export default Form;
src/intl/en.json
{
"email_label": "Email address",
"password_label": "Password",
"submit_button": "submit"
}
src/intl/zh-TW.json
{
"email_label": "電郵地址",
"password_label": "密碼",
"submit_button": "提交"
}
src/pages/contact.js
import React from 'react'
import styled from 'styled-components'
import Navbar from '../components/Navbar.js'
import Form from '../components/Form.js'
const Body = styled.body`
margin-left: 6%;
`
export default function Contact() {
return (
<div>
<Navbar/>
<Body>
<Form/>
</Body>
</div>
)
}
Errors while building static HTML files (the build-time React SSR process) generally happen for one of the following reasons:
Some of your code references “browser globals” like window or document that aren’t available in Node.js. If this is your problem you should see an error above like “window is not defined”. To fix this, find the offending code and either a) check before calling the code if window is defined so the code doesn’t run while Gatsby is building (see code sample below) or b) if the code is in the render function of a React.js component, move that code into a componentDidMount lifecycle or into a useEffect hook, which ensures the code doesn’t run unless it’s in the browser.
Check that each of your JS files listed in your pages directory (and any sub-directories) are exporting either a React component or string. Gatsby treats any JS file listed under the pages dir as a page component, so it must have a default export that’s a component or string.
You mix up import and require calls in the same file. This might lead to “WebpackError: Invariant Violation: Minified React error #130” since webpack 4 is stricter than v3. The solution is to only use import and this also extends to gatsby-ssr and gatsby-browser files.
Your app doesn’t correctly hydrate in the client, which results in gatsby develop and gatsby build being inconsistent. It’s possible that a change in a file like gatsby-ssr or gatsby-browser has a structure that is not reflected in the other file, meaning that there is a mismatch between client and server output.
Some other reason :-) #1 is the most common reason building static files fail. If it’s another reason, you have to be a bit more creative in figuring out the problem.
How to check if window is defined
import * as React from "react"
// Check if window is defined (so if in the browser or in node.js).
const isBrowser = typeof window !== "undefined"
export default function MyComponent() {
let loggedIn = false
if (isBrowser) {
window.localstorage.getItem("isLoggedIn") === "true"
}
return <div>Am I logged in? {loggedIn}</div>
}
So I have this import statement in a module that I'm trying to test using jest 25.1 running on node 11.1.0. The import statement is for a module that is only available when running on the jvm's nashorn runtime, so I'm using jest's virtual mock to control the behavior in the unit tests. Here's what the import statement looks like in the module under test:
import RequestBuilder from 'nashorn-js/request_builder'
...and after the other lines in the import block, this:
const request = RequestBuilder.create('some-string')
.sendTo('some-other-string')
.withAction('yet-another-string')
.getResultWith( consumer => consumer.result( consumer.message().body() ) )
export const functionA = () => {...} // uses 'request' variable
export const functionB = () => {...} // uses 'request' variable
In the corresponding .spec file, I have this virtual mock setup:
const mockGetResultWith = {
getResultWith: jest.fn()
}
const mockWithAction = {
withAction: jest.fn().mockImplementation(() => mockGetResultWith)
}
const mockSendTo = {
sendTo: jest.fn().mockImplementation(() => mockWithAction)
}
const mockBuilder = {
create: jest.fn().mockImplementation(() => mockSendTo)
}
jest.mock(
'nashorn-js/request_builder',
() => mockBuilder,
{ virtual: true }
)
require('nashorn-js/request_builder')
import { functionA, functionB } from './module-under-test'
I have been trying unsuccessfully to get past this failure from jest:
● Test suite failed to run
TypeError: Cannot read property 'create' of undefined
35 | }
36 |
> 37 | const verify = RequestBuilder.create('some-string')
| ^
38 | .sendTo('some-other-string')
39 | .withAction('yet-another-string')
40 | .getResultWith( consumer => consumer.result( consumer.message().body() ) )
I've tried all kinds of different mock structures, using require vs import, etc, but haven't found the magic bullet.
As near as I can tell, it does not appear that the RequestBuilder import statement is even invoking the virtual mock. Or at least, if I add console.log() statements to the virtual mock factory function, I never see those log messages in the output.
Anybody have any idea what I'm missing or what else to try? I have pretty much the same pattern in use in other parts of the code, where this setup works, but for some mystical reason with this module, I can't get the virtual mock working. Any help is greatly appreciated.
So, the problem here turned out to be jest's implementation of import vs require in the .spec file.
By changing this line:
import { functionA, functionB } from './module-under-test'
To this:
const module = require('./module-under-test')
const functionA = module.functionA
const functionB = module.functionB
The module under test now loads successfully, and the tests run as expected.
I have no explanation for this, and haven't been able to find jest documentation describing why I'd get any different behavior between require vs import. In fact, I have the mock configuration setup before any import statements as described here:
https://github.com/kentcdodds/how-jest-mocking-works
If anybody out there understands what's going on with this import behavior, or has a link describing what I'm missing, I'd sure appreciate the info.
Can not seem to run NextJs in development (next dev) when using Lerna. I can get other files to pull in just fine (e.g images) but it doens't seem to process TS on the fly using this method. Anybody have any thoughts on this?
ModuleParseError: Module parse failed: The keyword 'interface' is reserved (3:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| import React from "react";
|
> interface Props {
| name: string;
| }
packages/shared/components/Example.tsx
import React from "react";
interface Props {
name: string;
}
export const Example = ({ name }: Props) => {
return <div>Name: {name}</div>;
};
packages/web/pages/index.tsx
import { Example } from "shared/components/Example";
This is something that is being worked on/considered and supposedly might actually work already in some cases, but I was not able to make baseUrl work for me.
In the mean time per Jacob Rask's suggestion you should use next-transpile-modules. I was struggling with this issue myself, and this finally worked!
I have a React project set up like this:
It is a simple application. The Dashboard has a UserListContainer, containing a UserList, which lists four users with their ID and name. The UserList gets the Users from Data.ts
The application itself works just fine and displays the four users. But as soon as I try to test the UserList with enzymes shallow rendering, the tests give me the following error message:
Invariant Violation: You must pass a component to the function returned by connect. Instead received undefined
at invariant (node_modules/invariant/invariant.js:40:15)
at wrapWithConnect (node_modules/react-redux/lib/components/connectAdvanced.js:97:33)
at Object.<anonymous> (src/Users/UserListContainer.tsx:4:34)
at Object.<anonymous> (src/Users/index.ts:1:1)
at Object.<anonymous> (src/Dashboard/Dashboard.tsx:2:1)
at Object.<anonymous> (src/Dashboard/index.ts:1:1)
at Object.<anonymous> (src/Users/UserList.tsx:2:1)
at Object.<anonymous> (src/Users/__tests__/UserList.test.tsx:3:1)
The problem is basically that, even though we don't use the Dashboard when rendering the UserList shallowly, React still tries to build it. I guess that happens because we access Data through the Dashboard index, so React will also try to resolve Dashboard and its imports, namely UserListContainer, because they are exported through the same index file. When I import the users directly instead of through the index, the problem disappears.
We fixed this issue by breaking the cyclic dependency but if I encounter the error again, I want to know other ways to fix it. I would also like to understand why the web application still seems to be working just fine, while the tests fail.
Also, is there a way to prevent React from resolving the imports and exports when using enzymes shallow rendering?
Users/__tests__/UserList.test.tsx
test("reproduce the problem", () => {
const wrapper = shallow(<UserList />)
console.log(wrapper)
expect(1).toBe(1)
})
Users/UserList.tsx
import { Data } from "../Dashboard"
export const UserList: React.FC = () => (
<React.Fragment>
{Data.users.map(user => (
<div>
<code>{user.id} - </code>
<code>{user.name}</code>
</div>
))}
</React.Fragment>
)
Dashboard/index.ts
export { Dashboard } from "./Dashboard" // not used but still resolved
export { Data } from "./Data" // actually used
Dashboard/Data.ts
export const Data = {
users: [
{ id: "user1", name: "Albert" },
{ id: "user2", name: "Bertha" },
{ id: "user3", name: "Chloe" },
{ id: "user4", name: "Doug" }
]
}
Dashboard/Dashboard.tsx
import { UserListContainer } from "../Users"
export const Dashboard: React.FC = () => {
return <UserListContainer />
}
Users/UserListContainer.tsx
import { UserList } from "./UserList"
export const UserListContainer = connect()(UserList)
One way of fixing it would be to reorder the imports in your dashboard file:
export { Data } from "./Data" // actually used
export { Dashboard } from "./Dashboard" // not used but still resolved
Web application will work in most cases because it will start resolving from your index.tsx file (or whatever your entry file name is) and go from there. Jest on the other hand starts from your test file and only resolves those imports (you can find a nice explanation why this happens here: https://railsware.com/blog/how-to-analyze-circular-dependencies-in-es6/).
We had similar problems in our projects and unfortunatelly except reordering imports and better structuring your files there is no other solution.
One "hack" that you can also do is to add:
import 'problematic_module'
to your jest setupFilesAfterEnv. That way module will be resolved before each test (but i recommend this only as a last resort).