Testing Vue Single File Components which use a Global Event Bus - unit-testing

I'm testing my Vue components for the first time using Mocha and Webpack and set everything up according to the docs.
However, in many of my components, I use a global event bus to communicate and emit events between components. For example, the following snippet is in the created hook of one of my single file components.
created() {
Event.$on("activate-edit-modal", (listing) => {
this.isModalActive = true; // show delete modal
this.listing = listing; // set listing as the emitted listing
});
},
Unfortunately, when I run the following test code in my test file (npm run test), I get the following error.
import { mount } from '#vue/test-utils';
import editModal from '../../../src/components/admin/editModal.vue';
const wrapper = mount(editModal);
console.log(wrapper);
Error Msg: I'm aware the error msg is referring to the created hook (in the code snippet above) and highlighting that "Event.$on" in that created hook is not a function.
WEBPACK Compiled successfully in 2331ms
MOCHA Testing...
[Vue warn]: Error in config.errorHandler: "TypeError: Event.$on is not
a function" TypeError: Event.$on is not a function
at VueComponent.created ...
How should I test my components that use a global event bus? Note that I am not interested in testing the event bus itself; however, I'm unaware on how I can proceed to test other aspects of the component with this error.
The global event bus "Event" that I use in all my components is declared in /src/main.js as shown below
import Vue from 'vue';
import App from './App.vue';
import router from "./router";
import store from "./store";
window.Event = new Vue();
let app = new Vue({
el: '#app',
router,
store,
render: h => h(App)
});

You're trying to reference a local event bus called Event. You should call the bus you registered on the window object, like this: window.Event.$on("activate-edit-modal"....
After you've ensured that your component is calling the bus registered on the window object as shown above, make sure you also add the following before you mount your component in the test file like so:
import Vue from 'vue';
window.Event = new Vue();
const wrapper = mount(adminResults);

Your global event bus "Event": where is it defined? I can't see it being imported anywhere into the component with the error. I suspect this is your problem.
Beware global event bus is a top five antipattern, according to one of the presentations at the recent vue conf. I much prefer a plain global javascript object as a global state store.

You can mock your event bus and assert that methods are called on it with correct parameters.
For instance, in the above scenario try window.Event = { $on: sinon.spy() }.
After mounting you should be able to assert that $on was called with correct parameters.
Here's documentation on Mocha and spies. https://github.com/mochajs/mocha/wiki/Spies
I'm not as familiar with mocha so I'm not exactly sure I've got the details correct.

Related

Issues with model when navigating while still being retrieved

Working with an older ember application (2.18.1). The following problem is repeated too many times to all fix in the time frame I got available right now.
The component is loading it's own data (setting this.get('model')) and all works fine.
However as the database is now a little slower the user sometimes click on one link, where the template render the component and it start loading it's data .
If the user click another link (to a route that does exactly the same) data from both the previous and the "new" component get loaded.
I can't reset the model when data get loaded, since the fetchRecord method that loads the data get called over and over with paging (as the user scroll down).
I'm sure I'm just not thinking of an obvious solution (did not work on Ember for a few years), any advise?
(ps: some of these components does not use paging, in mean time I'm going to clear out the model before setting it to what the api returns)
I'm afraid ember-data gives no support to abort a request, but you can handle that yourself directly on your component calling the endpoint through Ajax or fetch, and then pushing the payload or aborting requests using the lifecycle hooks. For example, you can trigger the abort() on the willDestroyElement hook.
import $ from 'jquery';
import Component from '#ember/component';
export default Component.extend({
init() {
this._super(...argument);
const xhr = $.get( "ajax/test.html", (data) => {
this.get('store').pushPayload(data);
});
this.set('xhr', xhr);
}
willDestroyElement() {
this._super(...argument);
this.get('xhr').abort()
}
});

How to test Ember error substate, with Ember CLI test runner?

I've set up an error substate route/controller/template according to http://guides.emberjs.com/v2.2.0/routing/loading-and-error-substates/. Manually browsing my app, I can trigger error conditions and get directed to the substate. Confirmed with Ember Inspector.
I'd like to automatically test the substate. However, Ember CLI's test runner fails any test when a route's model hook rejects. In other words, the test fails before I can navigate to the error substate.
How can I automatically test my error substate?
Ember: 2.2.0
Ember CLI: 1.13.13
Unfortunately it doesn't seem to be easy to do this in a clean manner.
In its internal tests, Ember uses bootApplication to the route which errors (see github) and is able to directly catch the error. Unfortunately if you try and do any form of try/catch or then/catch around a call to visit in your tests you will find it fails.
When you visit a link which results in an error substate from your acceptance test then Ember's defaultActionHandlers.error gets fired. By design it is not meant to be overridable. It calls logError which calls Ember.default.Logger.error.
So to test this substate we need to temporarily overwrite that method. We can also peek inside the ember container to access the currentRouteName like so (using sinon for the spying):
test('when there is an API error an error message is shown', function(assert) {
const emberLoggerError = Ember.Logger.error;
Ember.Logger.error = sinon.spy();
visit('/users/');
andThen(() => {
// This could be nicer and less private with `getOwner`
let { currentRouteName } = this.application.__container__.lookup('router:main');
assert.equal(currentRouteName, 'users.index_error', 'The current route name is correct');
assert.equal(Ember.Logger.error.callCount, 1, 'The error logger was called');
// Restore the Ember.Logger
Ember.Logger.error = emberLoggerError;
});
});
Things can get even more complicated though. If your visit happens inside a Promise (it did in our case because we were using ember-page-object for our tests) then you have more to deal with...
In a separate loop onerrorDefault of RSVP is triggered which calls Test.adapter.exception AND Ember.default.Logger.error (again!) - passing the stack. So in this case you need to stub and spy on Test.adapter.exception and expect Ember.default.Logger.error to have been called twice!

How to access a service in an ember acceptance test

Running ember 1.13.6 and ember-cli
I have an ember component which I am trying to acceptance test. It's state is very closely tied to the state of a service within my app and so I would like to directly access that service and change its properties from within my acceptance test.
I have been trying things along the lines of
this.application.__container__.lookup['service:side-bar'])
and
this.application.__container__.cache['service:side-bar'])
but I cannot seem to get the actual active service singleton which my app is using and which I could call get() and set() on.
if I try to use Ember.inject.service i get an obscure error Uncaught TypeError: Object.defineProperty called on non-object(…) which sort of sounds like a bug
I'm successfully getting at a service in 1.13.x by doing something like this:
let myService;
module("Acceptance | xxxxx", {
beforeEach() {
this.application = startApp()
myService = this.application.__container__.lookup('service:my-service');
}
});
Your problem might be that you're trying to use array notation (lookup['my-service']) rather than method invocation (lookup('my-service')).
Hope this helps!

Uncaught Error: Assertion Failed: calling set on destroyed object

working in ember-cli testing. After all tests passed it returns extra two test with errors.
Uncaught Error: Assertion Failed: calling set on destroyed object
Source : '../dist/assets/vendor.js:13269'
this is one unit test configuration
import Ember from "ember";
import { test,moduleFor } from 'ember-qunit';
import startApp from '../helpers/start-app';
var App;
module('An Integration test',{
setup:function(){
App=startApp();
},
teardown: function() {
Ember.run(App, 'destroy');
}
});
This is either because in the result of a promise or any other deferred code you do not check the destroy status of an object, or because you didn't teardown something that has been setup and interact with DOM events or anything external to the core of Ember.
I used to have this especially on some jQuery plugins which I mapped to Ember, and during the tests the plugins were destroying too slowly and I was then either not using a run loop, or not checking the destroyed status of the Ember object I was manipulating.
You can do so with:
if ( !(obj.get('isDestroyed') || obj.get('isDestroying')) ) {
// do your destroying code setting stuff
}
Also think about destroying any jQuery plugins that might have been initialised in the code of your views (anything setup in didInsertElement should be teardown in willDestroyElement for example).
Ok i struggled with similar thing. So basically when you have "this.set()" inside a promise, it might happen that the promise takes too long to resolve, and the user already clicked away from that page, in this case you are trying to set something, that is already destroyed. I found the simplest solution to be just a simple check in the beginning of the promise.
if (this.isDestroyed) {
return;
}
this.set('...');
...
Edit: alternatively you can use Ember.trySet.
The issue is related to a promise not completely resolving and another test getting run immediately after.
You should give Ember Concurrency a try.
import { task, timeout } from 'ember-concurrency';
myFunction: task(function * () {
// do somethinng
yield timeout(1000); // wait for x milliseconds
// do something else
}).drop(),
I had a similar issue in an integration test. To resolve, in the integration test, I waited before performing the next action.
import wait from 'ember-test-helpers/wait';
wait().then(() => {
// perform action (which previously used to cause an exception)
});

Ember App Kit: Controller without Route is not recognized?

as I mentioned in several questions here I am migrating an already existing and running Ember project to use Ember App Kit and I ran into several problems... here's another "problem" which wasn't a problem before :)
I've got a NotificationCollectionController which is placed under app/controllers/notification/collection.js.
file 'app/controllers/notification/collection.js':
export default Ember.ArrayController.extend({
addNotification: function (options) {
// some code
},
notifyOnDOMRemove: function (notification) {
this.removeObject(notification);
}
});
As this is the controller for notifications which are rendered through a named outlet I didn't declare a route for it.
Within my ApplicationRoute I want to access this controller within a function
file: 'app/routes/application.js'
import BaseRoute from 'appkit/routes/base';
export default BaseRoute.extend({
addGlobalNotificationCollection: function () {
var controller = this.controllerFor('notificationCollection');
// some more code...
}
});
But as soon as the application starts and this piece of code gets called I traced down the following error:
"Assertion Failed: The controller named 'notificationCollection' could
not be found. Make sure that this route exists and has already been
entered at least once. If you are accessing a controller not
associated with a route, make sure the controller class is explicitly
defined."
What does it mean and why is it thrown? What do I have to do to make it run again?
I didn't recognize that the hint is already given at the Naming Conventions section of the Ember App Kit Webpage:
It says, that the naming convention for a Controller is, for example: stop-watch.js
And if it’s a route controller, we can declare nested/child controllers like such:
app/controllers/test/index.js
So I placed my NotifcationCollectionController in controllers/notification-collection.js and call it like Route#controllerFor('notification-collection') and everything works as expected :)