ReactTestUtils.Simulate can't trigger event bind by addEventListener? - testing

Here is the example:
http://jsfiddle.net/hulufei/twr4thuh/7/
It just worked when bind onClick in virtual dom(like line 18), but If I comment line 18 and comment off line 8 to bind click with addEventListener, it failed.
So what's the problem?

TestUtils triggers events within react's synthetic event system, so the native event that addEventListener listens for is never going to be triggered. You will need to use the native click method on the element in your test:
var events = Events();
ReactTestUtils.renderIntoDocument(events);
events.refs.button.getDOMNode().click();
events.state.event.should.equal('click');
Additionally, you've misspelled clickHandler in your addEventListener definition.
jsfiddle
You can also simplify adding your event listener by reusing your prop definition:
componentDidMount: function () {
this.refs.button.getDOMNode().addEventListener('click', this.clickHandler);
},
Note:
Is there a reason why you want to use addEventListener instead of just passing an onClick attribute for your button? Unless there's a specific and good reason otherwise, i'd suggest doing things the react way when handling events for sanity :)
Edit
I originally mentioned that I did not know what TestUtils' SimulateNative.click did not trigger the event. I was wrong in thinking that it ever would since it would be simulating a native click event within the react even system. #thilo pointed me in the right direction :)

I had many problems while testing addEventListener, and I got the following conclusion.
You can create the events listener with pure javascript, jquery, but when running the tests with Jest I always had a problem.
The rendering of ReactTestUtils does not work directly with the document, and when we do:
For example, our events were added in the document, when rendering with ReactTestUtils it creates a div and renders it in the div, This way I could not get Simulate to trigger the call.
My first solution was to use jquery to create the listener and to test I did the render manually by appending the div in document.body, and triggered the events with the dispachEvent of javascript. But I thought the code was dirty, not the best way to work.
I made a sample code by adding the event and testing it with Jest, also have a test teaching to get all the listener that were created.
You can find the code here: https://github.com/LVCarnevalli/create-react-app/tree/master/src/components/datepicker
Component:
componentDidMount() {
ReactDOM.findDOMNode(this.datePicker.refs.input).addEventListener("change", (event) => {
const value = event.target.value;
this.handleChange(Moment(value).toISOString(), value);
});
}
Test:
it('change empty value date picker', () => {
const app = ReactTestUtils.renderIntoDocument(<Datepicker />);
const datePicker = ReactDOM.findDOMNode(app.datePicker.refs.input);
const value = "";
const event = new Event("change");
datePicker.value = value;
datePicker.dispatchEvent(event);
expect(app.state.formattedValue).toEqual(value);
});
Links:
window.addEventListener not triggered by simulated events: https://github.com/airbnb/enzyme/issues/426
Creating and triggering events: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events

Related

How to determine what causes components to rerender

I am having an issue where when I change a component in my app, many unrelated components seem to be rerendering too. When I use the Vue performance timings config, I see something like (all in the span of about 200ms)
I am trying to figure out what causes these components to rerender. I saw a tip about how to tell the cause of a rerender, but when I put this snippet* in all the rerendering components, I don’t get anything logged to the console.
So, how can I find what is causing all these components to rerender?
*The code I actually put looks like
public mounted() {
let oldData = JSON.parse(JSON.stringify(this.$data));
this.$watch(() => this.$data, (newData) => {
console.log(diff(oldData, newData));
oldData = JSON.parse(JSON.stringify(newData));
}, {
deep: true,
});
}
Using the F12 dev tools in Chrome, you can track down what is triggering your component to re-render. Add an updated hook to your component as below:
updated() {
if (!this.updateCnt)
this.updateCnt = 1;
if (this.updateCnt > 1) { // set to desired
debugger;
}
console.log(`Updated ${this.updateCnt++} times`);
}
}
Refresh your page in Chrome with F12 tools open and wait for breakpoint to be hit. In the Sources tab, you will see the call stack on the right, with your updated() function as the current stack frame. Look back up the call stack and eventually you should see the code that caused the update to trigger. In my case, it was reactiveSetter() in the vue runtime, which was triggered by me setting a property in a parent component.
The code you have above will only trigger if a component's own state changes, not a parent.

Nuxt/Vuejs Event to fire when page done rendering all the components

I need to fire an event when page is done with rendering all the components. I have tried page mounted event but this fires only for the first time, I need to fire an event when the route changes on client side and new component is done rendering.
How about using updated . I used that for data changing state . Hope you get some help from this tips
updated: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been re-rendered
})
}

Programatically assign handler for native event in Vue JS?

I am trying to leverage a Vue mixin to add behavior when a native event happens. Using a mixin will allow me to share that across several components. Specifically, when a field component (or button, or checkbox, etc.) has focus, and the Escape key is pressed, the field loses focus.
A similar Stack Overflow question seemed to indicate I could listen for native events (see code comment about multiple events).
However, the Vue Documentation for programmatically adding an event listener using $on says that it will
Listen for a custom event on the current vm...
(Emphasis added)
Unsure if the custom event remark is absolute or based on the context, I have been experimenting. I have been trying to listen for the native keyup event (using the Vue alias keyup.esc) but have had no success. So I am wondering if it is indeed limited to custom events, and if so, why?
You can see my experiment in a code sandbox. The custom event works, the native does not.
The mixin looks like so:
// escape.mixin.js
export default {
created() {
// Custom event
this.$on("custom-event", function() {
console.log("Custom event handled by mixin");
});
// Native event
this.$on(["keyup.esc", "click"], function() {
alert("Native event handled!");
});
}
};
The main point of all this is to be able to add the behavior to a set of components by adding to how the event is handled, without overriding behavior that might also exist on the component. The secondary goal is to provide the behavior by simply adding the mixin, and not having to do component level wiring of events.
So a component script would look something like this:
// VText component
import escapeMixin from "./escape.mixin";
export default {
name: "VText",
mixins: [escapeMixin],
methods: {
onFocus() {
console.log("Has Focus");
this.$emit("custom-event");
}
}
};
Also, I was trying to avoid attaching the listener to the <input> element directly with vanilla JS because the Vue documentation suggested that letting Vue handle this was a good idea:
[When using v-on...] When a ViewModel is destroyed, all event listeners are automatically removed. You don’t need to worry about cleaning it up yourself.
Solution
skirtle's solution in the comment below did the trick. You can see it working in a code sandbox.
Or here's the relevant mixin:
export default {
mounted() {
this.$el.addEventListener("keyup", escapeBlur);
},
beforeDestroy() {
this.$el.removeEventListener("keyup", escapeBlur);
}
};
function escapeBlur(e) {
if (e.keyCode === 27) {
e.target.blur();
console.log("Lost focus");
}
}

Vuetify and require.js: How do I show a dynamic component?

I am creating a tab component that loads its v-tab-item components dynamically, given an array of configuration objects that consist of tabName, id, and tabContent which is a resource location for the component. I have it successfully loading the components. However, they don't actually initialize (or run their created() methods) until I switch tabs. I just get empty tabs with the correct labels. Using the DOM inspector initially shows just <componentId></componentId>, and then when I switch tabs, those tags are replaced with all of the component's content.
How do I get the dynamic components to initialize as soon as they are loaded?
EDIT: I created a CodePen here:
https://codepen.io/sgarfio/project/editor/DKgQON
But as this is my first CodePen, I haven't yet figured out how to reference other files in the project (i.e. what to set tabContent to so that require.js can load them up). I'm seeing "Access is denied" in the console, which makes it sound like it found the files but isn't allowed to access them, which is weird because all the files belong to the same project. So my CodePen doesn't even work as well as my actual project. But maybe it will help someone understand what I'm trying to do.
Also, after poking around a bit more, I found this:
http://michaelnthiessen.com/force-re-render/
that says I should change the key on the component and that will force the component to re-render. I also found this:
https://v2.vuejs.org/v2/guide/components-dynamic-async.html
Which has a pretty good example of what I'm trying to do, but it doesn't force the async component to initialize in the first place. That's what I need the async components to do - they don't initialize until I switch tabs. In fact they don't even show up in the network calls. Vue is simply generating a placeholder for them.
I got it working! What I ended up doing was to emit an event from the code that loads the async components to indicate that that component was loaded. The listener for that event keeps a count of how many components have been loaded (it already knows how many there should be), and as soon as it receives the right number of these events, it changes the value of this.active (v-model value for the v-tabs component, which indicates which tab is currently active) to "0". I tried this because as I noted before, the async components were loading/rendering whenever I switched tabs. I also have prev/next buttons to set this.active, and today I noticed that if I used the "next" button instead of clicking on a tab, it would load the async components but not advance the tab. I had already figured out how to emit an event from the loading code, so all I had to do at that point was capture the number of loaded components and then manipulate this.active.
I might try to update my CodePen to reflect this, and if I do I'll come back and comment accordingly. For now, here's a sample of what I ended up with. I'm still adding things to make it more robust (e.g. in case the configuration object contains a non-existent component URL), but this is the basic gist of it.
created: function() {
this.$on("componentLoaded", () => {
this.numTabsInitialized++;
if(this.numTabsInitialized == this.numTabs) {
// All tabs loaded; update active to force them to load
this.active = "0";
}
})
},
methods: {
loadComponent: function(config) {
var id = config.id;
var compPath = config.tabContent;
var self = this;
require([compPath], function(comp) {
Vue.component(id, comp);
self.$emit("componentLoaded");
});
}
}

Jquery .off vs dojo?

What is equivalent of jquery $.off(event) to remove event on element by passing event name in Dojo?
I tried :
dojo.disconnect(handle) // but I dont have an handle to event
How to get the handle or is there any better way to to it?
There is no out of the box solution as far as I know of, so you would have to implement one by yourself. However, this could be a dangerous feature, if you suddenly disconnect all event handlers of a specific type.
However, you could use the dojo/aspect module to intercept calls to the dojo/on module, for example:
aspect.around(arguments, 0, function(original) {
on.signals = [ ];
return function(dom, name, handler) {
console.log(arguments);
on.signals.push({
signal: original.apply(this, arguments),
name: name
});
};
}, true);
I didn't find a proper way to put an aspect around a function itself, rather than a function wrapped inside an object. So I used a dirty trick and used the arguments array and because the on module is my first argument, this will put an aspect around the dojo/on reference.
What happens is that when you bind an event handler using dojo/on, it will save it inside an array. Now you could write your own dojo/on::off() function, for example:
on.off = function(eventName) {
arrayUtils.forEach(on.signals, function(signal) {
if (signal.name === eventName) {
signal.signal.remove();
}
});
};
Now you can use:
on.off("click");
To disconnect all click event handlers.
A full example can be found on JSFiddle: http://jsfiddle.net/Lj5yG/ but this could probably be improved.