Ember.js: How to test actions in controller - ember.js

I would like to unit test a controller, which fires an event when a property changes.
The controller looks something like this:
App.MyController = Ember.Controller.extend({
...
observeFilterFieldChanges: function() {
console.log("observeFilterFieldChanges");
this.setActiveSortField();
this.send("queryChanged");
}.observes("sorting.fields.#each.active"),
...
});
And my test is the following:
test('changing sort field via sort.fields will trigger query changed', function () {
var queryChangedCalled = false;
var tmpListController = App.MyController.create({
actions: {
queryChanged: function () {
console.log("querychanged called from controller");
queryChangedCalled = true;
}
}
});
// trigger the change
tmpListController.set("sorting.fields.0.active", true);
stop();
// not sure if I need to wait for the run loop to finish
Ember.run.schedule('afterRender', this, function () {
start();
ok(queryChangedCalled, "querChangedCalled should be true");
});
});
This doesn't work because the action queryChanged in the controller is never called. (But the observer does get called)
What is the best way for testing if the event was sent?
Update for clarity:
The above code works great in the App. The sent action is nicely consumed in the Route. All I want to is unit test to protect me against future changes :)

The best way to test an action is to trigger it and assert whatever this.setActiveSortField(); changes.
If the observer fails, the activeSortField fails asserting.
If the actions fails, it should throw an error.
moduleFor('controller:mycontroller', 'MyController Controller');
test('changing sort field via sort.fields will trigger query changed', function(assert) {
assert.expect(1);
// grab an instance of `MyController`
var ctrl = this.subject();
Ember.run(function() {
ctrl.set('sorting.fields.0.active', true);
assert.equal(ctrl.get('activeSortField'), 'bar');
});
See the updated testing Guide: http://emberjs.com/guides/testing/testing-controllers/

Related

How do I trigger a function in an Ember component from the test component?

In the unit test ComponentA-test I would like to trigger the function load() in ComponentA, but I have problems doing so.
Before the assert.equal in the below code example I would like to add something simular to this.load() that I would have written in the component to trigger it. But I can't seem to find the right syntax for doing so.
test('should render some information', async function (assert)) {
await render(hbs`{{componentA}}`);
assert.euqal(".info").text().trim(), "info text");
}
There's a couple things you can do here but it depends on whether load is an action or not. There may be other ways to test this as well, but these are the most common methods I use.
If load is an action and is triggered by a DOM event (i.e., clicking a button) then you can trigger the function by performing that DOM event in your integration test:
test('should render some information', async function(assert) {
await render(hbs`{{componentA}}`);
// if a button has the action modifier:
// <button {{action "load"}}>Click me</button>
// then you can do this in your test to trigger the action:
await click('button');
// assertions go here
});
If it's simply a regular function in the component and you want to call this function manually on an instance of the component you could try doing so in a component unit test. The only gotcha here is that you won't be able to assert any DOM changes.
test('should do something', function(assert) {
const component = this.owner.factoryFor('component:componentA').create();
component.load();
// assertions go here
});
Update for Octane/Glimmer components: I learned today from this comment by an Ember core team member that they "don't currently support unit testing of Glimmer components"
Copying a suggested workaround in this comment which "let you gain access to the component instance from your rendering tests"
module('my-thing', function(hooks) {
setupRenderingTest(hooks);
test('something that wants to exercise an internal method', async function(assert) {
let component;
this.owner.register('component:my-thing', class extends MyThing {
constructor(owner, args) {
super(owner, args);
component = this;
}
});
await render(hbs`<MyThing />`);
// now I can interact with `component` without issue
});
});

Ember force template sync before longer operation

I have some actions that take some time and I wan't to indicate that to the user by showing a loading spinner. I know realized that sometimes the longer running action is directly triggered before the loading spinner is shown:
this.set('betRound.isLoading', true);
var _this = this;
Ember.run.sync();
Ember.run.later(function(){
_this.transitionToRoute('betround.edit', _this.get('betRound.content'));
}, 50);
I tried to achieve this by delaying the transition with 50ms, but on some slower mobile devices, the loading spinner that depends on "isLoading" is not shown.
I would go with some kind of callback mechanism, where you'd put your loading spinner in a view or component and notify the controller when DOM is ready. Here's an example of a Mixin you can attach to any view or component to make it send a notice after it loads.
App.LoadAware = Ember.Mixin.create({
didInsertElement: function () {
this._super();
var target = this.get("loadedNoticeTarget");
Ember.assert("We must have loadedNoticeTarget in LoadAware component or view", target);
target.send("loadAwareLoaded");
}
});
You would then apply it like this:
App.LoadingSpinnerComponent = Ember.Component.extend(App.LoadAware);
In your template:
{{#if isLoading}}
{{loading-spinner loadedNoticeTarget=this}}
{{/if}}
And then, in your controller:
App.IndexController = Ember.Controller.extend({
actions: {
goToSlowRoute: function () {
this.set("_waitingForSpinner", true);
this.set("isLoading", true);
},
loadAwareLoaded: function () {
Ember.assert("We must be waiting on spinner at this point", this.get("_waitingForSpinner"));
this.set("_waitingForSpinner", false);
this.transitionToRoute("slowRoute");
}
},
isLoading: false
});
In this example, you would initiate transition by sending goToSlowRoute message to the controller. Full JSBin here.

Emberjs Testing a Component's action delegate

I'm trying to test a simple component that uses action delegation. I want to test that a certain action is sent to the actionDelegate of a component. I was thinking of using Sinon to check that the action was being sent, but I can't work out how to replicate the structure that Ember uses to send its actions to its delegates/targets.
What structure would my sinon "spy delegate" object take in order for me to check that the component is delegating the event using "send" when the user clicks on a button?
I've created an example of the kind of thing I want to test at http://jsfiddle.net/L3M4T/ but it haven't a testing harness around it (it's kind of a big job to put a testing harness around a component just for a simple js fiddle - in fact it was quite a bit of a job to get this component into the shape I wanted to explain this problem).
Here's my component:
App.AppProfileComponent = Ember.Component.extend({
actionDelegate: null,
actions: {
hello: function(person) {
if(!Ember.isEmpty(this.get('actionDelegate'))) {
this.get('actionDelegate').send('hello', person);
}
}
}
});
And my inital try that didn't work was simply to write a test that had this fragment in it (using sinon & qunit):
visit("/").click("button").then(function() {
equal(actionDelegateSpy.called, true, "Action delegate should be called when button pressed");
});
I think it's pretty obvious why that didn't work, but since I've tried the following, which also didn't work:
var actionDelegateSpy = sinon.spy();
var actionDelegate = Ember.ObjectController.extend({
actions: {
"hello" : actionDelegateSpy
}
}).create();
then testing by passing in the actionDelegate defined above as the actionDelegate on the component for a test.
I fixed my own issue... Silly me:
test("delegates its hello action to actionDelegate", function() {
var actionDelegateSpy;
Ember.run(function() {
actionDelegateSpy = sinon.spy();
var actionDelegate = Ember.ObjectController.extend({
actions: {
"hello" : actionDelegateSpy
}
}).create();
controller.set('actionDelegate', actionDelegate);
});
visit("/").click("button")
.then(function() {
equal(actionDelegateSpy.called, true, "Action delegate should be called when hello button pressed");
});
});

Getting timeout error when testing ajax behavior within ember component with sinon and mocha

I am trying to test an ember component with mocha and sinon. I wanted to test one of the actions of the component which makes an ajax call by using sinon's "useFakeXMLHttpRequest". But this test is causing time-out error. I am using mocha test adapter for ember taken from https://github.com/teddyzeenny/ember-mocha-adapter, I couldn't find the js file in cloud so I have pasted in whole code - so it might look bit messy in the jsbin.
Here is a jsbin link to the issue : http://jsbin.com/usajOhE/1/
The code for the component is :
AS.QuestionViewComponent = Ember.Component.extend({
templateName: "components/question-view",
actions: {
makeAjaxCall: function() {
jQuery.ajax({
url: "/todo/items",
success: function(data) {
//callback(null, data);
}
});
}
}
});
The handle bar associated with the component is :
<a {{action "makeAjaxCall"}} class="test-link">Make ajax call</a>
And my test script is:
describe("Testing", function() {
var xhr, requests;
before(function() {
xhr = sinon.useFakeXMLHttpRequest();
requests = [];
xhr.onCreate = function(req) {
requests.push(req);
};
});
after(function() {
xhr.restore();
});
beforeEach(function() {
AS.reset();
visit("/");
});
it("shoud make ajax call", function() {
//TIMESOUT HERE
click($("a.test-link:first")).then(function() {
console.log(requests);
expect(requests.length).to.be(1);
});
});
});
Your help will be much appreciated. Thanks
Most likely it is because you have not responded to the fake ajax request. The ember-testing package counts the pending ajax requests made by jQuery (see pendingAjaxRequests here). If this stays at 1, the ember-testing wait() helper never resolves.
The ember-testing package increments this counter via ajaxStart and ajaxStop filters.
To clarify what's happening here: When you use the click() helper, this sends a click message to the element, and then defers to the wait() helper (a promise). The same applies for other helpers such as fillIn(), keyEvent() etc. You can see from the comments in the source for wait() that it will not progress on with the rest of your specs:
// 1. If the router is loading
// 2. *If there are pending Ajax requests
// 3. If there are scheduled timers or we are inside of a run loop
The fix:
Unfortunately, if you never make it to the then block of your test, you cannot fake a response via requests[0].respond(...).
Instead, I've solved this by using sinon's fake server:
var server;
beforeEach(function () {
server = sinon.fakeServer.create();
server.autoRespond = true;
server.autoRespondAfter = 1; // ms
App.reset();
});
afterEach(function () {
server.restore();
});
it("should make ajax call", function() {
// set up the fake response
server.responses[0].response = [200, { "Content-Type": "application/json" }, '{ "todos": [] }'];
visit('/')
.click($("a.test-link:first"))
.then(function() {
// should make it to here now
});
});
This pattern works fine when you are expecting a single, or a deterministic order of ajax requests going into your fake server. If you expect lots of requests (with different paths), you can use server.respondWith([regex], ...) to match certain urls to specific responses.
Another thing to note is that it's generally good practice to put the success part of your ajax call into an Ember.run:
jQuery.ajax({
url: "/todo/items",
success: function(data) {
Ember.run(function () {
//callback(null, data);
})
}
});

How do I make my items in my ArrayController listen to an event from a third party API?

I want my items in my ArrayController to listen to
FB.Event.subscribe('edge.create', function(response){
Ember.Instrumentation.instrument("facebook.like", response);
})
I'm making use of the a seperate itemController.
Like2win.ContestsController = Ember.ArrayController.extend({
itemController: "contest",
});
Like2win.ContestController = Ember.ObjectController.extend({
init: function() {
this._super();
instance = this;
Ember.subscribe("facebook.like", {
before: function(name, timestamp, payload) {
instance.send('onLike', payload);
},
after: function(name, timestamp, payload) {
//
}
})
},
For some reason only the last item in my array ends up listening to the event. I'm just starting out with Emberjs so I expect the answer to be simple.
Ember.Instrumentation is a simple software instrumentation api. It's purpose is performance profiling, tracing, not application level event dispatching.
You can see this api in action by setting Ember.STRUCTURED_PROFILE to true. This will start logging the render times for all templates rendered to the DOM by ember.
The specific issue you are having deals with how the Ember runloop works. The after hooks are only fired once with the last context given. This is done to ensure that multiple property changes of the same property do not result in re-rendering the DOM that many times. So the last property change on the runloop wins and the DOM updates with that property value.
What you really need to do is just translate the FB.Event of type edge.create into an application event that your app understands, something like `facebookLike', similar to what you have done above.
I would do this in the enter hook of your ContestRoute. Further exiting from the ContestRoute should probably unsubscribe from this event. So you probably need an unsubscribe in the exit hook.
enter: function() {
var self = this;
FB.Event.subscribe('edge.create', function(response) {
self.get('controller').send('facebookLike', response);
});
}
exit: function() {
// unsubscribe from edge.create events here
}
Then you can handle this event in your ContestController like so,
facebookLike: function(response) {
}