I am writing a test for a React component that uses react-router. I am using Mocha with Chai and Chai-jQuery.
My tests work fine, until I import a component from react-router into a component (e.g. Link). At this point, I get the following error:
ReferenceError: navigator is not defined
at Object.supportsHistory (/Users/nico/google-drive/code/agathos/client/node_modules/history/lib/DOMUtils.js:61:12)
I used to get a similar error with react-bootstrap until I updated to react-bootstrap v0.29.3. I have the most recent version of react-router v2.4.0 (and history v2.1.1). But the problem persists.
The only solution I have found is to change node_modules/history/lib/DOMUtils: navigator into window.navigator. This is a hack though, and not a good solution.
I think the problem is with react-router, but I don't have a solution.
Just in case, here is my test-helper.js.
import jquery from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import jsdom from 'jsdom';
import chai, { expect } from 'chai';
import chaiJquery from 'chai-jquery';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducers from './reducers';
// set up a testing environment to run like a browser in the command line
// create a fake browser and html doc
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.defaultView;
// prevent jquery defaulting to the dom by giving it access to the global.window
const $ = jquery(window);
// build renderComponent function that should render a React class
function renderComponent(ComponentClass, props = {}, appState = {}) {
const componentInstance = TestUtils.renderIntoDocument(
<Provider store={createStore(reducers, appState)}>
<ComponentClass {...props} />
</Provider>
);
// produces html
return $(ReactDOM.findDOMNode(componentInstance));
}
//build a helper for simulating events
// $.fn allows you to add a custom function to your jquery library
$.fn.simulate = function(eventName, value) {
if (value) {
// `this` allows you to access the object appended to
// `val()` is a jquery function that sets the value of selected html element
this.val(value);
}
// the [] are object method selectors, which allow you to access e.g. Simulate.change
TestUtils.Simulate[eventName](this[0]);
};
// set up chai jquery
chaiJquery(chai, chai.util, $);
export {renderComponent, expect};
It seems that react-router assumes navigator is in the global scope.
To resolve this error, you should add navigator to the global scope in your test setup phase:
global.navigator = {
userAgent: 'node.js'
};
Related
I tried as following but on change of localstorage useEffect is not getting called.
I have also tried adding dependency localStorage.getItem('quantity') but doesn't work.
import React from 'react';
import Categories from '../molecules/Categories';
import './SideBar.scss';
import { useState, useEffect } from 'react';
const SideBar = () => {
let [quantity, setQuantity] = useState(JSON.parse(localStorage.getItem('quantity')));
useEffect(() => {
console.log("in useEffect");
const onStorage = () => {
setQuantity(JSON.parse(localStorage.getItem('quantity')));
};
window.addEventListener('storage', onStorage);
return () => {
window.removeEventListener('storage', onStorage);
};
}, []);
return (
<div className='sidebar'>
<Categories />
<div>count:{quantity}</div>
</div>
)
}
export default SideBar;
Having an empty dependency array (as in your code above) will ensure that a useEffect runs only once, but I'm not sure why you would need this useEffect to run whenever storage is updated. This useEffect basically just adds an event listener, which does not need to be renewed after every storage update.
It looks like onStorage is what you actually want to run whenever storage is updated, but that is handled by the event listener that the useEffect adds. If you add a console.log inside onStorage, you should see that onStorage is running after every storage update.
Note, too, though, that a storage event will fire only if localStorage is updated by a different window/page; see this MDN article. If the same window is updating localStorage, then to trigger your event handler you will need to dispatch a storage event manually whenever you update the quantity in localStorage:
window.dispatchEvent(new Event('storage'));
I am in Browser trying to initialize Vuetify and I believe this used to work but now fails...
//In Node
app.use(serve("./node_modules/vuetify/dist"));
// On Client
<script type="module">
import * as Vuetify from '/vuetify/vuetify.js';
const global = window || global;
global.Vuetify = Vuetify;
Vue.use(Vuetify, {
...
});
</script>
When I run I get
Error in beforeCreate hook: "Error: Vuetify is not properly initialized, see https://vuetifyjs.com/getting-started/quick-start#bootstrapping-the-vuetify-object"
I tried changing to...
//In Node
app.use(serve("./node_modules/vuetify"));
// On Client
<script type="module">
import Vuetify from "/vuetify/lib";
const global = window || global;
global.Vuetify = Vuetify;
Vue.use(Vuetify, {
...
});
</script>
But it still fails this time with
GET https://localhost:3001/vuetify/lib net::ERR_ABORTED 404
I even tried changing to...
//In Node
app.use(serve("./node_modules/vuetify"));
// On Client
<script type="module">
import Vuetify from "/vuetify/lib/index.js";
const global = window || global;
global.Vuetify = Vuetify;
Vue.use(Vuetify, {
...
});
</script>
Then I get...
GET https://localhost:3001/vuetify/lib/components net::ERR_ABORTED 404
I think the js files are not proper ES6 definitions (IE no extensions mjs, cjs) but is there a way to get it to work?
For additional context this is how I load Vue
import Vue from '/vue/vue.esm.browser.js'
const global = window || global;
global.Vue = Vue;
This works fine
you could try importing the script in a script tag: https://cdnjs.com/libraries/vuetify
I think the link you want is this: https://cdnjs.cloudflare.com/ajax/libs/vuetify/2.0.17/vuetify.js
I'm making web slides using this awesome library called mdx-deck. I've written my own React components for my slide deck. Now I want to add react-redux to my slide deck so that all components share a common store. How can I do this?
I know I should use the <Provider /> component from react-redux, so I've been reading about mdx-deck custom provider and MDXProvider, but I haven't been able to see the <Provider /> in my DOM.
I'm using yarn start to serve my slide deck:
// package.json
{
"scripts": {
"start": "mdx-deck concat-slides.js",
}
}
The concat-slides.js script concatenates two slide decks that I have:
// concat-slides.js
import { slides as cv } from 'src/slides/cv.mdx';
import { slides as bootstrap } from 'src/slides/bootstrap.mdx';
import { distillLite } from 'src/slides/theme/index';
export const themes = [distillLite];
export const slides = [...cv, ...bootstrap]
And here's my attempt to add a custom provider (but I don't see a provider in my DOM tree?):
// src/slides/theme/index.tsx
import React from 'react'
import { createStore } from "redux";
import { Provider as ReduxProvider } from "react-redux";
export const store = createStore(() => {});
const Provider = (props:any) => (
<ReduxProvider store={store}>
{props.children}
</ReduxProvider>
)
export const distillLite = {
Provider,
}
I am using Laravel with Vue.js and Webpack / Laravel Mix.
I have Single File Components which should make use of Mixins.
The folder structure looks like this:
/app.js
/vue-components/Component.vue
/mixins/api/common.js
common.js defines a mixin like so:
export default {
// all content goes here
}
And when I import it from app.js and console.log it, it shows the data:
import industries from "./mixins/api/common";
console.log(industries); // this shows the content
Vue.component(
'some-component',
require("./vue-components/Component.vue")
);
Within Component.vue I use mixins: [ industries ], and that gives me the following error:
Component.vue?bb93:73 Uncaught ReferenceError: industries is not defined
Question 1:
Is it not possible to declare mixins globally and reference them from within a component?
To solve the issue I tried importing the mixin directly within the component instead of the global app.js file.
But import industries from "./mixins/api/common"; within Component.vue throws the following error while trying to compile with npm run:
This relative module was not found:
* ./mixins/api/common in ./node_modules/babel-loader/lib?{"cacheDirectory":true,"presets":[["env",{"modules":false,"targets":{"browsers":["> 2%"],"uglify":true}}]],"plugins":["transform-object-rest-spread",["transform-runtime",{"polyfill":false,"helpers":false}],"babel-plugin-syntax-dynamic-import","webpack-async-module-name"],"env":{"development":{"compact":false}}}!./node_modules/vue-loader/lib/selector.js?type=script&index=0!./resources/assets/js/vue-components/Component.vue
Question 2:
If I have to import the mixin from within the Single File Components, what should the path look like?
As in Vue Document, You can declare mixin globally
//- build your mixin
const mixin = {
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
}
Vue.mixin(mixin)
new Vue({
myOption: 'hello!'
})
// "hello!"
You can also import mixin to use for each component.
In above your Component.vue, import path is not correct.
You should do import industries from "../mixins/api/common"
i've been trying to execute a button that calls a function from a different component.
import ComponentB from './components/ComponentB '
import React, {Component} from 'react';
class ComponentA extends Component {
render() {
return
(
<button onClick={this.handleClick}>click me</button>
);
}
}
export default ComponentA;
this didn't work out. the button wasnt able to call the function. what am i doing wrong?
import React, {
Component
}
from 'react';
class ComponentB extends Component {
constructor() {
this.handleClick = this.handleClick.bind(this);
}
}
handleClick() {
console.log("hi hi hi");
}
}
export
default ComponentB;
You're importing ComponentB, but not using it, that's why is not working. In your case the best way is to implement the handleClick directly to your ComponentA, but this is not what you wanna do. If you're looking to share the same functions with different components, using redux together with react js will get the job done and will provided to the other components as well ;)