Using JEST to unit test a component that has a keydown listener attached to the document.
How can I test this in JEST? How do I simulate the keydown event on the document? I need the event listener to be on the document since it is supposed to respond the keyboard action irrespective of the focussed element.
EDIT: The question here is about simulating the event on the document or the document.body. All the examples are regarding an actual DOM node, that works fine but the document does not.
Currently trying to do this:
TestUtils.Simulate.keyDown(document, {keyCode : 37}); // handler not invoked
I had the exact same problem and unfortunately couldn't find any details on how to solve this using TestUtils.Simulate. However this issue gave me the idea of simply calling .dispatchEvent with a KeyboardEvent inside my jest tests directly:
var event = new KeyboardEvent('keydown', {'keyCode': 37});
document.dispatchEvent(event);
You can find details on the KeyboardEvent here: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent
You can also replace the whole document.eventListener with mock function before you mount the component:
let events;
document.addEventListener = jest.fn((event, cb) => {
events[event] = cb;
});
And simulate event by calling events[event] after mounting, e.g.:
events.keydown({ keyCode: 37 })
Also, it's quite comfortable to do first part in beforeEach() function if you have many tests dealing with DOM events.
Following #Iris Schaffer answer,
If your code makes use of ctrl/alt/shift keys, you will need to init them, as well as mocking implementation of getModifierState method on KeyboardEvent
const keyboardEvent = new KeyboardEvent('keydown', { keyCode, shiftKey, altKey, ctrlKey });
jest.spyOn(keyboardEvent, 'getModifierState').mockImplementation((modifier) => {
switch (modifier) {
case 'Alt':
return altKey;
case 'Control':
return ctrlKey;
case 'Shift':
return shiftKey;
}
});
Related
I am trying to write a unit test that checks whether or not the effect of a focus event takes place. My actual test case is more complicated, but I have created a minimal reproduction with the following code:
it('testing input focus', async(() => {
let showDiv = false;
const template = `<div *ngIf="shouldShow" class='hidden-div'>
SHOW ME WHAT YOU GOT
</div>
<input (focus)="shouldShow = !shouldShow" name="input">`;
buildTestComponent(template, {shouldShow: showDiv}).then((fixture) => {
fixture.detectChanges();
const inputEl: HTMLInputElement = fixture.nativeElement.querySelector('input');
expect(fixture.nativeElement.querySelector('.hidden-div')).toBe(null);
inputEl.focus();
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('.hidden-div')).not.toBe(null);
});
}));
When I run this test with karma the test passes as long as I have focus on the chrome tab that is running the karma target. However, if the browser does not have focus the test fails (even if the browser is visible, but I click on another window) with error message:
Expected null not to be null.
I assume that when the Chrome tab doesn't have focus, the inputEl.focus() call doesn't actually get called but I don't know how to fix it. All other unit tests I have written pass regardless of browser focus. Has anyone run into this or have any ideas?
To trigger an event on an Angular element, you can use the built-in JavaScript ES6 method dispatchEvent with a subsequent call of Angular's change detection mechanism to get your DOM updated:
inputElement.dispatchEvent(new Event('focus'));
fixture.detectChanges();
A more elegant way to achieve the same thing is to use angular's wrapper method:
import { dispatchEvent } from '#angular/platform-browser/testing/src/browser_util'
dispatchEvent(inputElement, 'focus');
fixture.detectChanges();
An interesting one is when you want to set a value to your input element. You will need to first assign a string to the input's value property and then trigger an 'input' change event:
inputElement.value = 'abcd';
dispatchEvent(inputElement, 'input');
fixture.detectChanges();
Note: There are events that do not act the way you may expect. For example, dispatching a 'click' event will NOT give the focus to your input element! A workaround could be to first trigger a 'focus' event and then a 'click' event as follows:
dispatchEvent(inputElement, 'focus');
dispatchEvent(inputElement, 'input');
fixture.detectChanges();
All the available JavaScript events are here.
React doesn't provide an API that lets you pass in context to a created component class, so you have to write a wrapper component that provides the context.
Unfortunately, once you do this, you no longer have direct access to the component you are trying to test - unlike TestUtils.renderIntoDocument, functions like TestUtils.findRenderedComponentWithType don't return the actual rendered component instance, they only return the component constructor. Thus you can't call methods on the component, or set the component state to some known value before executing the test. The only thing you really have access to is the DOM node for your component, which is fine if all you want to do is black box testing, but for some kinds of components that's not sufficient.
I'm curious to know if anyone has come up with a solution for this. I've tried about a dozen different approaches, none of which work. (For example, I tried using 'ref' in my wrapper component, but it has the same problem - doesn't give you access to the real object.)
(Answering my own question)
Turns out the correct answer to all of this is to use enzyme, which replaces the standard React test utils - it offers a ton of features with a jQuery-like API, and best of all it completely supports component contexts. I've switched all of my tests over to it and it's great!
You can build a mock parent component like:
class MockContextContainer extends Component {
static propTypes = {
children: PropTypes.element.isRequired,
};
static childContextTypes = {
onSetTitle: PropTypes.func,
};
getChildContext() {
return {
onSetTitle: () => {},
};
}
render() {
return this.props.children;
}
}
Then use it in your test (in this case its a forgot password form example):
const cxtForgot = TestUtils.renderIntoDocument(
<MockContextContainer><ForgotPasswordForm /></MockContextContainer>
);
Which is what you may already be doing.
You can then do things like:
const input = TestUtils.findRenderedDOMComponentWithClass(
cxtForgot, 'ForgotPasswordForm-input'
);
// enter a valid email
input.value = 'abc#hotmail.com';
TestUtils.Simulate.change(input);
// no error class and button is now enabled
assert(!input.classList.contains('error'));
const button1 = TestUtils.findRenderedDOMComponentWithClass(
cxtForgot, 'primary-button'
);
assert(!button1.disabled);
the Simulate.change above can change the internal state of the component.
As for you question: "set the component state to some known value before executing the test", you can pass in different props to the component and have different tests for each scenario
I am writing an angular2 unit test for a component.
With fully using JQuery it's possible to find what event is bound to an element. However in Angular2, I am not sure it's possible or not
For example, the following code has a click event, which is a public function of a component
<button (click)="doLogin()" [disabled]="myDisabled">Login</button>
By reading DOM element, I can make it sure all properties and data binding is correct by running a test. Only thing that I don't know is, "event binding is correct or not" because the generated html is like the following
<button>Login</button>
I want to make it sure someone does not delete this event binding in the future by writing a test for it.
In summary, how do I know event is properly bound to DOM element?
EDIT:
Is there a way to know there is click event without actually clicking it?
You could use the approach below (calling the click:
it('should render list', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(MyList).then((componentFixture: ComponentFixture) => {
const element = componentFixture.nativeElement;
componentFixture.detectChanges();
expect(element.querySelectorAll('li').length).toBe(5);
document.getElementById('test').click();
});
}));
See this question for more details:
How can I trigger a JavaScript event click
I need to run some code after ember application got initialized. I don't want to invoke this code from App.ready to avoid tight coupling. It would be nice to have something like this:
App.on 'ready, -> console.log('do stuff')
But it won't work since Em.Application object is not subscribable and ready isn't really an event despite that docs said so
A simple way you can achieve this would be to extend your Application class with the Ember.Evented mixin:
App = Ember.Application.createWithMixins(Ember.Evented, {
ready: function() {
console.log('App ready');
this.trigger('appReady');
}
});
And you hook inside the ready event inside your app and trigger your custom event using this.trigger(...)
At this point you can use .on(...) to be notified when the event is triggered.
App.on('appReady', function() {
console.log('App already ready');
});
Example demo.
Hope it helps.
An other possibility may be to invoke your code from the resolve callback of the application.
App.then(function(app) {
console.log("App is resolved, so it's ready");
});
example stolen from #intuitivepixel ;) http://jsbin.com/AdOVala/66/edit
Edit/Note:
App.then() has been deprecated, see http://emberjs.com/deprecations/v1.x/#toc_code-then-code-on-ember-application:
As part of the Ember.DeferredMixin deprecation, using .then on an
Ember.Application instance itself has been deprecated.
You can use the ready hook or initializers to defer/advance readiness instead.
Update: here's a fiddle of my problem. The tests pass once, and fail the next time:
http://jsfiddle.net/samselikoff/hhk6u/4/
The problem is departments has events.on("userSet:company"), so both variables respond to the event.
This is a general question about unit testing. In my app, a certain event is fired, and several other pieces of my app listen for this event. I'd like to unit test each piece separately, since they are performing different functions; but to do this, I have to fire off the event in each test.
This causes problems, since the first test must fire off the event, triggering the listeners in the other tests. How can I keep my tests atomic while still testing multiple event listeners?
(I am using QUnit, but I think this is a more general unit-testing question).
Answer:
Jeferson is correct. One easy way to solve this, is to use events.once instead of events.on. This way you clean up your events from each test.
All your calls to async methods should be tested using "asyncTest" methods and making sure you wrap your calls in other functions that calls QUnit.start() when the assertions data are ready to be collected and analyzed.
I updated your JSFiddle with working code: http://jsfiddle.net/hhk6u/8/
The new code is:
QUnit.config.autostart = false;
QUnit.config.testTimeOut = 1000;
asyncTest('Some test that needs companies.', function() {
function getCompanies() {
var companies = new Companies();
ok(1);
start();
}
setTimeout(getCompanies, 500);
});
asyncTest('Some other async test that triggers a listener in companies.', function() {
var companies = new Companies();
events.trigger("userSet:company", { name: "Acme", id: 1 });
stop();
events.on('fetched:departments', function(response) {
console.log(response);
deepEqual(response, [1, 2, 3]);
start();
});
});
See my answer in this other question for more details:
Test fails then succeeds
Hope this helps you!