How to test route's willTransition action in Ember? - ember.js

How can I test this code in Ember? Explain me please the concept, in general.
// app/routes/products/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.createRecord('product');
},
actions: {
willTransition() {
this._super(...arguments);
this.get('controller.model').rollbackAttributes();
}
}
});
I have no idea how to make this. May be stub model in route? I found that store is not available in route test.
After Ruby and RSpec, all these new javascript world is confusing a little bit) But I'd like to learn it anyway.

In unit tests the idea is to stub all external dependencies. In ember you can do this:
// tests/unit/products/new/route-test.js
test('it should rollback changes on transition', function(assert) {
assert.expect(1);
let route = this.subject({
controller: Ember.Object.create({
model: Ember.Object.create({
rollbackAttributes() {
assert.ok(true, 'should call rollbackAttributes on a model');
}
})
})
});
route.actions.willTransition.call(route);
});
Basically you stub controller and model passing them to this.subject(), then call whatever function you are testing (in this case you have to use call or apply to call an action with the correct scope), and then assert that rollbackAttributes() was called.
assert.expect(1); at the start of a test tells QUnit to wait for exactly 1 assertion.

Related

Mocking component function for the purposes of component integration test in EmberJS

Let'a assume a simple EmberJS component:
//my-app/components/my-component.js
export default Ember.Component.extend({
classNames: ['cursor-pointer'],
doSth(){
// whatever, state of the component does not change
},
clickListener: Ember.on('click', function(){
this.doSth();
})
});
Now I would like to use integration tests to find out whether clicking the component reaches (delegates to) the doSth() method.
moduleForComponent('my-component', 'Integration | Component | stores parser previewer', {
integration: true
});
test('should call doSth() on click', function (assert) {
/* Line 3 - the place I tried to set mocked doSth() up */
this.render(hbs`{{my-component}}`);
this.$('.cursor-pointer').click(); // triggers clickListener() correctly
});
The problem is that I cannot substitute method doSth() with my mock.
Thus I never run into assert.ok() statement.
I have tried to insert following statements onto the line 3 in the test:
// idea 1
this.set('doSth', function(){
assert.ok(true);
});
// idea 2
this.doSth = function(){
assert.ok(true);
};
None of the 2 approaches (idea 1, idea 2) worked. this.subject() is also unavailable since it's an integration test, not a unit test.
Update:
Imagine doSth() be a function like the openUrlInANewTab() shown below that does not influence the component's state, nor the associated controller's nor route's state.
Example:
//my-app/components/my-component.js
...
import openUrlInANewTab from 'my-app/utils/new-tab-opener';
...
export default Ember.Component.extend({
...
doSth(url){
openUrlInANewTab(url);
}
while
// my-app/utils/new-tab-opener.js
export default function openUrlInANewTab(){
anchor = document.createElement("a");
...
anchor.click();
...
}
You can move doSth to service custom-service that you inject to your my-component. Then in test you can EASILY test this by injecting fake service.
tests/integration/components/my-component-test.js:
test('should call doSth() on click', function(assert) {
const customStub = Ember.Service.extend({
doSth() {
// whatever, state of the component does not change
console.log('Stubbed doSth fired');
assert.ok(true, 'Stubbed doSth fired');
}
});
this.register('service:custom-service', customStub);
this.inject.service('custom-service', { as: 'customService' });
this.render(hbs`{{my-component}}`);
this.$('.cursor-pointer').click(); // triggers clickListener() correctly
});
components/my-component.js:
import Ember from 'ember';
export default Ember.Component.extend({
customService: Ember.inject.service('custom-service'),
classNames: ['cursor-pointer'],
clickListener: Ember.on('click', function(){
this.get('customService').doSth();
console.log('Click listener');
})
});
Working demo.
Ember Guides.
As an alternative to what Daniel had proposed you might consider using a closure action in case of handling click within your component. What I mean is instead of delegating to one of your methods (what you had done and could not test) or to a service (what Daniel suggested) just trigger an action passed to your component. This way also provides you the opportunity to mock in your test. Moreover, this approach results in a more reactive component; since you inject the behavior to your component from the owner at runtime; which enables you to reuse your custom component within different contexts (different parent components/templates) easily. See the twiddle for an example of what I mean.

"Attempting to register an unknown factory" in model test

I have these models in an ember-cli app:
var PuzzleRound = DS.Model.extend({
year: DS.attr('number')
});
var Puzzle = DS.Model.extend({
puzzleRounds: DS.hasMany('puzzleRound', {async: true})
});
And here's my test from tests/unit/models/puzzle-test.js:
import {
moduleForModel,
test
} from 'ember-qunit';
import PuzzleRound from 'weather-roulette/models/puzzle-round';
moduleForModel('puzzle', 'Puzzle', {
// Specify the other units that are required for this test.
needs: ['model:puzzleRound']
});
test('it exists', function() {
var model = this.subject();
// var store = this.store();
ok(!!model);
});
I get this error when running ember test:
Attempting to register an unknown factory: `model:puzzleRound`
I'm using ember-cli 0.1.1, Ember.js 1.7.0, Ember Data 1.0.0-beta.11. Does anyone have anything I can try to fix this?
I just tried out this code with ember-cli 0.0.44 and I got the same error that you did.
I renamed both references to the puzzleRound model path to puzzle-round and then your test passed for me. So:
DS.Model.extend({
puzzleRounds: DS.hasMany('puzzle-round', {async: true})
});
and
moduleForModel('puzzle', 'Puzzle', {
needs: ['model:puzzle-round']
});
I knew that the hyphenated style was preferred over the camelCase style, but I'm not sure when this became mandatory. This requirement may be specific to ember-cli or ember-qunit.
I was looking for a solution similar to this one for awhile, and did not see any mention of my solution so I thought I would post here anyways. It's quite simple really: make sure that the controller you're referencing is actually there.
// my-ember-application/tests/unit/controllers/index/bar-test.js
moduleFor('controller:index/bar', 'My Bar Test', {
beforeEach() { .. }
});
test('it exists', function (assert) {
assert.ok(true);
});
This code would reference a controller at this location:
my-ember-application/app/controllers/index/bar.js

Ember Component Testing

I'm using Qunit and Karma for testing, but i cannot find the way to create Test for Ember component.
Here is my code for test:
test('Function',function(){
var test = App.MyComponent.create({
data:[{'a':'a'}]
});
var result = test.get('buildingComponent');
equal(result, 'done', "function crushed because" + result);
});
My component:
App.MyComponent = Ember.Component.extend({
buildingComponent:function(){
return 'done'
}.property('data')
});
So how can i test my component?
I had a similar issue testing a component and found a couple of insights in the Ember tests that let me test the component successfully.
The tests for Ember's TextField showed how to compile one-off view that includes a handlebars template that references the helper. This uses a locally created controller/view that is used to isolate the helper to test.
This almost worked directly for component testing, except I couldn't get the handlebars template to resolve the custom component handlebars helper name. I found a method for using components in a testing template handlebars in the tests for yield. The key is to reference the component in the controller and then insert the component using {{view myComponentNameOnTheController ... }}.
I modified Toran's JSBin to show this in action: http://jsbin.com/UNivugu/30/edit
var App = Ember.Application.create();
App.MyThingComponent = Ember.Component.extend({
template: Ember.Handlebars.compile('<button {{action "doSomething"}}>{{view.theText}}</button>'),
actions: {
doSomething: function(){
console.log('here');
this.set('didSomething', true);
}
}
});
/////////////////////////////
// start of your test file
var controller, wrapperView;
var compile = Ember.Handlebars.compile;
module('MyThingComponent', {
setup: function(){
controller = Ember.Controller.extend({
boundVar: "testing",
myComponent: App.MyThingComponent
}).create();
wrapperView = Ember.View.extend({
controller: controller,
template: compile("{{view myComponent theText=boundVar}}")
}).create();
Ember.run(function(){
wrapperView.appendTo("#qunit-fixture");
});
},
teardown: function(){
Ember.run(function(){
wrapperView.destroy();
});
}
});
test('bound property is used by component', function(){
equal(wrapperView.$('button').text(), "testing", "bound property from controller should be usedin component");
});
You could use the library/addon created by Ryan # https://github.com/rpflorence/ember-qunit using Qunit. A simple example (posted from the link above) -
// tell ember qunit what you are testing, it will find it from the
// resolver
moduleForComponent('x-foo', 'XFooComponent');
// run a test
test('it renders', function() {
expect(2);
// creates the component instance
var component = this.subject();
equal(component.state, 'preRender');
// appends the component to the page
this.append();
equal(component.state, 'inDOM');
});
It makes my life easier. Hope this helps.

Obversing route change to apply equivalent to onload

I'm trying to observe the route change to apply some common action once rendered. The idea is to have a feature similar to the onload but as we use a single-page app this needs to be triggered on each route changes. (could be scoped to the new view)
I found how to observe the currentPath changes:
App.ApplicationController = Ember.Controller.extend({
currentPathDidChange: function() {
prettyPrint()
}.observes('currentPath');
});
While this works good in some cases, it gets triggered when the route changes, but still to early to apply content changes as it seem to append before the content gets rendered.
Any idea on the best practice to achieve such goal?
Have you tried deferring the code with Ember.run.schedule? For instance,
App.ApplicationController = Ember.Controller.extend({
currentPathDidChange: function() {
Ember.run.schedule('afterRender', this, function() {
prettyPrint();
});
}.observes('currentPath')
});
Due to the deprecation of Controllers in Ember 1.x finding the url in the router would be a good way to future proof your apps. You can do this in ember-cli like so:
// similar to onLoad event behavior
export default Ember.Route.extend({
afterModel: function (model){
Ember.run.next(() => {
console.log(this.get('router.url'));
});
}
});
// hacky way to get current url on each transition
export default Ember.Route.extend({
actions: {
didTransition: function() {
Ember.run.next(() => {
console.log(this.get('router.url'));
});
}
}
});
This will log: /posts and /posts/3/comments ect.

Ember.js RC1 - controller 'needs' another that does not yet exist

My routing structure:
App.ready = function() {
App.Router.map(function() {
this.resource('contacts', function() {
this.resource('contact', function() {
});
});
});
}
Now in my contactsController I respond to and add action that transitions to the contact route. I would then like to call the add method on my contactController.
I have placed the needs: ['contact'] on my ContactController but then I get this message:
<App.ContactsController:ember197> needs controller:contact but it does not exist
When I use controllerFor (which is deprecated) I also get an error:
this.controllerFor('contact').add();
So Ember.js RC1 appears to only create the controllers (and other related instances) once one actually transitions to the appropriate route.
Is there a way around this.
So Ember.js RC1 appears to only create the controllers (and other related instances) once one actually transitions to the appropriate route.
Interesting - I had thought ember generated controllers earlier but guess not.
Is there a way around this?
Workaround is to define App.ContactController manually. Something like this will work:
App = Ember.Application.create({});
App.Router.map(function() {
this.resource('contacts', function() {
this.resource('contact', function() {
});
});
});
App.ContactController = Ember.Controller.extend({
add: function() {
alert('App.ContactController.add() was called!');
}
});
App.ContactsController = Ember.Controller.extend({
needs: ['contact'],
add: function() {
this.get('controllers.contact').add();
}
});
http://jsbin.com/osapal/1/edit