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");
});
});
Related
I try to write my first unit test for a route.
project/files
actions: {
afterSave(savedFile){
// ... some code
let controller = this.controllerFor('project.files');
// ...
}
}
the test:
test('save file', function(assert) {
let route = this.subject();
console.log(route);
let project;
Ember.run(() => {
project = route.get('store').createRecord('project', {
id: '1',
name: 'test'
});
let afterSave = route.get('actions.afterSave');
afterSave(project);
});
assert.ok(true);
})
The problem that I am getting TypeError: Cannot read property 'controllerFor' of undefined.
It looks like this is undefined.
If you have a look at Testing Routes section from Ember Guides, you can see its suggestion is to separate the action and the function.
I can suggest it.
It uses send method of routes, such as: route.send('afterSave');
But if you only want to make run your code, call afterSave action from your test code such as: afterSave.bind(route)(project);. Ref: bind function (I don't suggest this. Also I don't suggest you to retrieve action such as: route.get('actions.afterSave'))
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.
I'm using ember-cli 0.0.35, and injecting a dependency onto my component via an initializer. It works great in development, but the property isn't present when I run tests. It appears that testing calls loadInitializers, but the dependency is not showing up on this.subject({});
I don't want to manually inject it for the tests. Is there a better way to handle this?
Initializer:
var FooServiceInitializer = {
name: 'foo',
initialize: function (container, application) {
application.inject('component:foo', 'foo', 'service:foo');
}
};
export default FooServiceInitializer;
Failing Test:
moduleForComponent('bar', 'Component: Bar', {
setup: function() {
App = startApp();
component = this.subject({});
},
teardown: function () {
Ember.run(App, App.destroy);
}
});
test('Properties: foo', function() {
// Make sure we injected the service
ok(component.foo, 'foo is injected');
});
As I said before, it really lends itself to an integration test since you are testing the container at this point (and not the mini container created by ic-ajax).
Your real test is along the lines of this
test("root lists 3 colors", function(){
var c = App.__container__.lookup('component:foo-bar');
ok(c.foo.blah);
});
If you feel guilty about using the container during testing (which you shouldn't) you can create a helper to avoid having to fix it all over the place when/if an api changes in the future.
Ember.Test.registerHelper('containerLookup',
function(app, look) {
return app.__container__.lookup(look);
}
);
Make sure you define this before
App.injectTestHelpers();
and then your test would look like
test("root lists 3 colors", function(){
var c = containerLookup('component:foo-bar');
ok(c.foo.blah);
});
http://emberjs.jsbin.com/doxigu/edit
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/
Sample code for my question is here.
It's a simple Ember app that displays the SearchView containing a TextField by default.
When the user enters some text and hits Enter, I want to transition to another state (displayUserProfile) passing the value entered in the textbox.
At first, in the Textbox's insertNewline callback, I called the transitionTo method of the application's router, passing the value as part of the parameter object:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
App.router.transitionTo('displayUserProfile', {
username: this.get('value')
});
}
});
That works fine, but then I noticed that pangratz's answer on a question about infinite scrolling, uses a different approach. Instead he invokes a method on the view's controller, which in turn calls a method on the controller's target (which is the router).
This changes my code to:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
Em.tryInvoke(this.get('controller'), 'displayUserProfile', this.get('value').w());
}
});
App.SearchController = Em.Object.extend({
displayUserProfile: function(username) {
this.get('target').transitionTo('displayUserProfile', {
username: username
});
}
});
My question is: which approach is better?
Calling transitionTo directly from the view or delegating it to the view's controller?
I would recommend a different approach. insertNewLine should trigger an action that is handled by the router, which will then transition its state.
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
this.get('controller.target').send('showUser', {username: this.get('value')});
}
});
App.Router = Ember.Router.extend({
...
foo: Ember.Router.extend({
showUser: function(router, evt) {
router.transitionTo('displayUserProfile', evt);
});
}
});
You should put the showUser handler at the top-most route where it is valid in your app.
This approach follows the general pattern of events in Ember apps that views handle DOM-level events and where appropriate, turn them into semantic actions that are handled by the router.
Personally I think the second approach is better.
The first thing is that it's a bad idea to access the router statically. Then for me, you have to keep the views logic-less, so delegating to controller seems a good choice.
In your case this is only a call to the router, but you can imagine processing some algorithms on the textfield value. If you do this proccessing in you view, this will lead to a view, mixing UI code, and logic code. View should handle only UI code.