I am having problems getting React and Jest to work together which seems odd as I thought they both came from a similar origin. My issue is around the way that the class I'm testing is being exported.
I have an ArticleService class which I could use happily in React when it was exporting as default:
class ArticleService {
constructor(articles) {
this.articles = articles;
if (!this.articles) {
this.articles =
[//set up default data....
];
}
}
getAll(){
return this.articles;
}
}
//module.exports = ArticleService;//Need this for jest testing, but React won't load it
export default ArticleService;// Can't test with this but React will load it.
Here is how it is being called in my React app (from my HomeComponent):
import ArticleService from './services/xArticleService';
and is being used happily as
const articles = (new ArticleService()).getAll();
However my tests will not run. Here is my test with an import of the class file:
import ArticleService from "../services/xArticleService";
it('correctly gets all summaries', () => {
var summaries = getFakeSummaryList();
var testSubject = new ArticleService(summaries);
var actual = testSubject.getAll();
expect(actual.length).toEqual(10);
});
and I get
FAIL src/tests/ArticleService.test.js
Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
U:\...\src\tests\ArticleService.test.js:2
import ArticleService from "../services/xArticleService";
^^^^^^
SyntaxError: Unexpected token import
at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:403:17)
If (in the test) I swop
import ArticleService from "../services/xArticleService";
for
const ArticleService = require('../services/xArticleService');
and edit the export in xArticleService.js to
module.exports = ArticleService;//Need this for jest testing, but React won't load it
then the tests are executed but React will not load it:
Attempted import error: './services/xArticleService' does not contain a default export (imported as 'ArticleService').
I have a default set up, creating using create-react-app. I've not changed any .babelrc.
Can anyone see where I am going wrong?
Thanks
UPDATE:
I have made the changes to .babelrc suggested in the accepted answer to the possible duplicate of this but this has made no change to the output.
Related
I'm using jest for the first time to test this App component of a React app:
// External deps
import React from "react";
import ReactDOM from "react-dom";
// Project-specific deps
import "../index.css";
import App from "../../App"; // Top-level component to be rendered
import {constants} from "../constants.mjs"; // Get general constants of project
describe("Pass props properly", () => {
test("Check each props mapping.", () => {
const root = document.createElement("div");
var props = {
now: new Date(), // Current date and time
}
ReactDOM.render(<App {...props}/>, root);
expect(App).toHaveBeenCalledWith(props);
});
});
When I run a custom test, it fails and Jest throws a SyntaxError saying
Support for the experimental syntax 'jsx' isn't currently enabled and suggests to Add #babel/plugin-transform-react-jsx (https://git.io/vb4yd) to the 'plugins' section of your Babel config to enable transformation.
However, despite installing the #babel/plugin-transform-react-jsx plugin with node & yarn—both as normal and dev dependencies—the error persists. What else I've tried:
Creating this babel.config.json at the project root (I followed Babel's config instructions, except for step #2, here)
{ "presets": [["#babel/preset-env", { "targets": { "node": "current" } }]] }
Could it be an improper file hierarchy? Too many installs of the plugin messing with each other? Is there a good way to ensure Babel is enabled/working? Is the issue with Jest's config?
I am developing an application using Svelte with Rollup and I ran into such a problem that when I compile it gives me a warning about the absence of App:
No name was provided for external module 'C:\...path...\App.svelte' in output.globals – guessing 'App'
The console displays at startup:
Uncaught ReferenceError: App is not defined
at main.js:5
My Rollup config:
Pastebin
main.js:
import App from './App.svelte';
const app = new App({
target: document.body
});
export default app;
Thanks for answers!
The issue is with the external setting in your Rollup config. You are passing a builtins function from rollup-plugin-node-builtins. Since this function returns a truthy value, Rollup assumes that every module you import is external.
The external key accepts either an array of module names, or a function which takes the module name and returns true if it should be treated as external.
See the Rollup documentation on external.
You need to pass an array here with the modules you want treated as external. Are you intending to pass a builtin-modules import here instead? I found this example in the rollup-plugin-node-resolve README.
import resolve from 'rollup-plugin-node-resolve';
import builtins from 'builtin-modules'
export default ({
input: ...,
plugins: [resolve()],
external: builtins,
output: ...
})
I'm using React Intl for x number of languages (example below) and at the moment Im importing the following where I setup my App:
import { addLocaleData } from 'react-intl';
import locale_en from 'react-intl/locale-data/en';
import locale_de from 'react-intl/locale-data/de';
import messages_en from './translations/en.json';
import messages_de from './translations/de.json';
addLocaleData([...locale_en, ...locale_de]);
...
export const messages = {
en: messages_en,
de: messages_de
}
Since these language files are being imported no matter which language is being used my main bundle js file is getting pretty big, especially from the .json files.
How can I with Webpack split these language files (or copy them to my dist folder using CopyWebpackPlugin) and then dynamically import them based on the language being used at the moment?
The app is isomorphic so this same code is being run on the server.
I've been working on something like this lately, although I don't need SSR for my project. I found that pairing dynamic import syntax with React's Suspense component achieves the desired result. Here's a rough overview of what I found to work, at least in my case, which doesn't include SSR:
// wrap this around your JSX in App.js:
<React.Suspense fallback={<SomeLoadingComponent />}>
<AsyncIntlProvider>
{/* app child components go here */}
</AsyncIntlProvider>
</React.Suspense>
// the rest is in support of this
// can be placed in another file
// simply import AsyncIntlProvider in App.js
const messagesCache = {};
const AsyncIntlProvider = ({ children }) => {
// replace with your app's locale getting logic
// if based on a hook like useState, should kick off re-render and load new message bundle when locale changes (but I haven't tested this yet)
const locale = getLocale();
const messages = getMessages(locale);
return (
<IntlProvider locale={locale} messages={messages}>
{children}
</IntlProvider>
);
};
function getMessages(locale) {
if (messagesCache[locale]) {
return messagesCache[locale];
}
// Suspense is based on ErrorBoundary
// throwing a promise will cause <SomeLoadingComponent /> to render until the promise resolves
throw loadMessages(locale);
}
async function loadMessages(locale) {
// dynamic import syntax tells webpack to split this module into its own chunk
const messages = await import('./path/to/${locale}.json`);
messagesCache[locale] = messages;
return messages;
}
Webpack should split each locale JSON file into its own chunk. If it doesn't, something is likely transpiling the dynamic import syntax to a different module system (require, etc) before it reaches webpack. For example: if using Typescript, tsconfig needs "module": "esnext" to preserve import() syntax. If using Babel, it may try to do module transpilation too.
The chunk output for a single locale will look something like this; definitely more than would be achieved via CopyWebpackPlugin:
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
/***/ "./path/to/en-US.json":
/*!*************************************!*\
!*** ./path/to/en-US.json ***!
\*************************************/
/*! exports provided: message.id, default */
/***/ (function(module) {
eval("module.exports = JSON.parse(\"{\\\"message.id\\\":\\\"Localized message text\\\"}\");//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvbG9jYWxpemF0aW9uL2VuLVVTLmpzb24uanMiLCJzb3VyY2VzIjpbXSwibWFwcGluZ3MiOiIiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./path/to/en-US.json\n");
/***/ })
}]);
Hopefully, this is a good starting point and either works with SSR or can be modified to work with SSR. Please report back with your findings on that subject. 🙂
I've looked at other answers with this problem, and it seems to be caused by trying to import vue-router into the test. This however, is not the case for my problem. Here is my test code:
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import ListDetails from '../components/homepage/ListDetails'
import EntityList from '../components/homepage/EntityList'
import BootstrapVue from 'bootstrap-vue'
import Vuex from 'vuex'
import faker from 'faker'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(BootstrapVue)
describe('ListDetails.vue', () => {
it('gets the correct page list when router list id param present', () => {
const selected_list = {
id: faker.random.number(),
name: faker.lorem.words(),
entries: []
}
testRouteListIdParam(selected_list)
})
})
Then in testRouteListIdParam, I have:
function testRouteListIdParam(selected_list) {
// just assume this sets up a mocked store properly.
const store = setUpStore(selected_list, true)
const $route = {
path: `/homepage/lists/${selected_list.id}`
}
const wrapper = mount(ListDetails, {
mocks: {
$route
},
store,
localVue
})
}
As soon as mount() happens, I get the error:
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property as a read-only value
Any ideas why this would be happening? Again, I'm not using VueRouter anywhere in the unit tests, so I'm not sure why I'm getting the error. Could it be BootstrapVue or Vuex that are messing things up?
So this is a bug with vue-test-utils. If you are using VueRouter anywhere (even if it's not used in any unit test), you will get the above error.
A work around is to use process.env.NODE_ENV in your unit tests and set it to 'test', and wherever you're using VueRouter, check process.env.NODE_ENV like so:
if (!process || process.env.NODE_ENV !== 'test') {
Vue.use(VueRouter)
}
at least until vue-test-utils bug is fixed, this should fix this problem
I think these docs are relevant to your situation:
Common gotchas
Installing Vue Router adds $route and $router as read-only properties on Vue prototype.
This means any future tests that try to mock $route or $router will fail.
To avoid this, never install Vue Router globally when you're running tests; use a localVue as detailed above.
The error you're seeing indicates that one of your components (and outside your test code) is installing VueRouter (e.g., Vue.use(VueRouter)).
To address the issue, search for the VueRouter installation in your component code path, including any of their imports, and refactor it so that the import is not required there. Typically, the installation of VueRouter exists only in main.js or its imports.
GitHub demo
I encountered this, and it was because I was importing vueRouter into a controller, outside of a vueComponent, where this.$router wasn't defined.
import router from '#/router';
router.push('/foo')
janedoe's answer can work but it's often risky to modify production code just to make some tests pass. I prefer to bypass the bug by doing this:
Run your test in watch mode
npx jest --watch src/components/Foo.test.ts
Locate Vue.use(VueRouter) in your code and diagnose what is the chain of components indirectly running the code by adding this just above
const error = new Error();
console.log(
error.stack
?.split('\n')
.filter((line) => line.includes('.vue'))
.join('\n'),
);
This logs a list of file path like
console.log
at Object.<anonymous> (/path/to/components/Bar.vue:1:1)
at Object.<anonymous> (/path/to/components/Foo.vue:1:1)
Chose a component in this list and, in your test file, mock it
jest.mock('/path/to/components/Bar.vue');
I am trying to manually include the #material/drawer npm package into my Ember app. I tried following this guide but I'm running into some weird errors in my Chrome dev console:
Uncaught SyntaxError: Unexpected token *
Uncaught ReferenceError: define is not defined
The first is from the imported node_modules/#material/drawer/index.js file and the second is from my generated shim.
My component code:
import Component from '#ember/component';
import { MDCTemporaryDrawer, MDCTemporaryDrawerFoundation, util } from '#material/drawer';
export default Component.extend({
init() {
this._super(...arguments);
const drawer = new MDCTemporaryDrawer(document.querySelector('.mdc-drawer--temporary'));
document.querySelector('.menu').addEventListener('click', () => drawer.open = true);
}
});
In my ember-cli-build.js:
app.import('node_modules/#material/drawer/index.js');
app.import('vendor/shims/#material/drawer.js');
My generated shim:
(function() {
function vendorModule() {
'use strict';
return {
'default': self['#material/drawer'],
__esModule: true,
};
}
define('#material/drawer', [], vendorModule);
})();
What exactly am I doing wrong? It almost seems as though raw ES6 code got imported rather than compiled into my JS build output.
I also read this SO post but there are too many answers and I'm not sure which to do. It seems this specific answer is what I'm trying to do but not verbatim enough.
Creating a shim only ensures that ember-cli gets an AMD module, which you then can import in your app files.
If the npm package needs a build or transpiling step beforhand, this won't work.
You need a way to get the package build within the ember-cli build pipeline.
Luckily there are addons which can take care of this for you: ember-auto-import and ember-cli-cjs-transform.
You may have also heard of ember-browserify, which does the same thing, but it's deprectaed in favor of ember-auto-import.
I'd suggest you try ember-auto-import:
ember install ember-auto-import
You then should be able to import as you tried:
import { MDCTemporaryDrawer, MDCTemporaryDrawerFoundation, util } from '#material/drawer';
No shim or app.import needed, as ember-auto-import will take care of this for you.