After a few months without looking at emberjs, I'm trying to go back into it now, and I'm therefore trying the new router. And I would like to test my routes.
Has anybody tried to write some routing tests with emberjs ?
Let's suppose the very basic following router :
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router, context) {
router.get('applicationController').connectOutlet({name: 'home'});
}
})
})
})
How do you test that loading the root.index route properly loads the HomeView ?
Here is the full test, using Jasmine & Sinon :
code:
describe("Given the Router", function(){
var router = null;
beforeEach(function(){
router = Router.create();
});
afterEach(function(){
router = null;
});
it("Should be defined", function(){
expect(router).toBeDefined();
});
it("Should have an root route", function(){
expect(router.get("root")).toBeDefined();
});
describe("its root route", function(){
var root = null;
beforeEach(function(){
root = router.get("root").create();
});
afterEach(function(){
root = null;
});
it("should have an index route", function(){
expect(root.get("index")).toBeDefined();
});
describe("its index route", function(){
var indexRoute = null;
beforeEach(function(){
indexRoute = root.get("index").create();
});
it ("should have route of /", function(){
expect(indexRoute.get("route")).toEqual("/");
});
it ("should connect the outlets to home", function(){
var fakeRouter = Em.Object.create({applicationController: {connectOutlet: function(){} } });
var connectOutletSpy = sinon.spy(fakeRouter.applicationController, "connectOutlet");
var methodCall = connectOutletSpy.withArgs({name:"home"});
indexRoute.connectOutlets(fakeRouter);
expect(methodCall.calledOnce).toBeTruthy();
});
});
});
});
Hope it helps.
Here is how Ember has already tested the connectOutlet for you:
https://github.com/emberjs/ember.js/blob/master/packages/ember-views/tests/system/controller_test.js
test("connectOutlet instantiates a view, controller, and connects them", function() {
var postController = Ember.Controller.create();
var appController = TestApp.ApplicationController.create({
controllers: { postController: postController },
namespace: { PostView: TestApp.PostView }
});
var view = appController.connectOutlet('post');
ok(view instanceof TestApp.PostView, "the view is an instance of PostView");
equal(view.get('controller'), postController, "the controller is looked up on the parent's controllers hash");
equal(appController.get('view'), view, "the app controller's view is set");
});
Other routing related tests can be found at https://github.com/emberjs/ember.js/tree/master/packages/ember-routing/tests
Related
I feel like this should be pretty straight-forward, but I'm unable to get the contents of a controller in a different view. Here is my code:
App.MapView = Ember.View.extend({
elementId: ['map-canvas'],
didInsertElement: function() {
var self = this;
var controller = this.get('controllers.markers');
}
});
If I console.log(controller) I get undefined.
In a controller I would do something like:
App.MarkersController = Ember.ArrayController.extend({
needs: ['map']
});
App.MapController = Ember.ObjectController.extend({
plot: function() {
var markers = this.get('controllers.markers');
}
});
You place the needs on the controller that needs another controller, and where you'll be accessing the other controller.
And from a view, in order to grab the controller you do this.get('controller') and the controllers object lives on the controller, so controller.controllers.markers
Additionally, the view is only created with the controller by default if ember creates it, if you are doing something like {{view App.MapView}} it isn't creating the MapController and associating it with it, it's using the controller that was in scope when you created the view.
App.MapView = Ember.View.extend({
elementId: ['map-canvas'],
didInsertElement: function() {
var self = this;
var controller = this.get('controller.controllers.markers');
}
});
App.MarkersController = Ember.ArrayController.extend({
});
App.MapController = Ember.ObjectController.extend({
needs: ['markers'],
plot: function() {
var markers = this.get('controllers.markers');
}
});
Check out this implementation of it:
http://emberjs.jsbin.com/ODuZibod/1/edit
Given the following Ember.js application (using Ember 1.0.0.rc.6.1 and Ember Data 0.13):
App = Ember.Application.create({ LOG_TRANSITIONS: true });
App.Store = DS.Store.extend();
App.Router.map(function() {
this.resource('promotions', function() {
this.resource('promotion', { path: '/:promotion_id' }, function() {
this.resource('entrants', function() {
this.resource('entrant', { path: '/:entrant_id' });
});
});
});
});
App.PromotionRoute = Ember.Route.extend({
model: function() {
return { id: 1, name: 'My Promotion' };
}
});
App.EntrantsIndexRoute = Ember.Route.extend({
model: function(params) {
console.warn('EntrantsIndexRoute', '\nparams:', params, '\nparams.promotion_id:', params.promotion_id, '\narguments:', arguments);
console.log('params should be:', { promotion_id: 1 });
console.log('The queried URL should be:', '/entrants?promotion_id=1');
return App.Entrant.find({promotion_id: params.promotion_id});
}
});
App.Entrant = DS.Model.extend({
name: DS.attr('string')
});
If you enter the url #/promotions/1/entrants, which should be a nested resource, the params is an empty object. How can I access promotion_id there? JSFiddle here, take a look at the console after clicking on "Click me": http://jsfiddle.net/Kerrick/4GufZ/
While you can't access the dynamic segments of the parent route, you still can retrieve the model for the parent route and get its ID, like this:
App.EntrantsIndexRoute = Ember.Route.extend({
model: function() {
var promotion_id = this.modelFor('promotion').id;
return App.Entrant.find({ promotion_id: promotion_id });
}
});
Or, if there is a has-many relation between promotion and entrants, you even might do:
App.EntrantsIndexRoute = Ember.Route.extend({
model: function() {
return this.modelFor('promotion').get('entrants');
}
});
Try this code:
App.EntrantsIndexRoute = Ember.Route.extend({
model: function() {
var promotion_id = this.modelFor('promotion').query.promotion_id;
return App.Entrant.find({ promotion_id: promotion_id });
}
});
I have the following controller:
var ProductsController = Ember.ArrayController.extend({
search: function(queryString) {
this.set('model', App.Product.find({query: queryString }));
}
});
and a text field:
var SearchFieldView = Ember.TextField.extend({
insertNewline: function() {
this.get('controller').search(this.get('value'));
}
});
Now I want to disable the text field when the controller loads a new model in the search function. Using something like disabledBinding: 'controller.content.isLoaded' in the view doesn't work.
var ProductsController = Ember.ArrayController.extend({
search: function(queryString) {
this.set('isLoadingData', true);
var products = App.Product.find({query: queryString });
this.set('model', products);
products.then(function() {
this.set('isLoadingData', false);
});
}
});
var SearchFieldView = Ember.TextField.extend({
attributeBindings: ['disabled'],
disabledBinding: 'controller.isLoadingData',
insertNewline: function() {
this.get('controller').search(this.get('value'));
}
});
Explanation:
Before doing a request set isLoadingData to true. ember-data find() uses the Promise API: Set the isLoadingData to false when the request has been completed successfully. You might want to handle the failed case. See RSVP.js for reference. Finally bind the disabled property of Ember.TextField to controller.isLoadingData.
A simpler way, as you have already tried:
var ProductsController = Ember.ArrayController.extend({
search: function(queryString) {
this.set('model', App.Product.find({query: queryString }));
}
});
var SearchFieldView = Ember.TextField.extend({
attributeBindings: ['disabled'],
disabled: function() {
return this.get('controller.model.isLoaded') === false;
}.property('controller.model.isLoaded'),
insertNewline: function() {
this.get('controller').search(this.get('value'));
}
});
If you want all the Ember.TextField to have the disabled property binding:
Ember.TextField.reopen({
attributeBindings: ['disabled']
});
this is a basic ember routing example that doesn't update the url to /posts correct when there is a child route in posts with dynamic segments. all the other routes (including the dynamic segments) correctly update the url. if i take out the dynamic segment child route (called 'show', under 'posts') then it updates the url correctly. here's the fiddle code : http://jsfiddle.net/inconduit/NbPpM/3/
and to view fiddle in action where it updates the urls, look here: http://fiddle.jshell.net/inconduit/NbPpM/3/show/#
to summarize - when you click 'Posts' the url should update to show /posts , but it does not.
here's the javascript:
App = Ember.Application.create({
ready: function() {
App.initialize(App.Router.create({ enableLogging: true }));
}
});
App.Post = Ember.Object.extend({
title: null,
body: null
});
App.posts = [];
App.posts.pushObject(App.Post.create({id:'0', title: "Test post 1", body: "How awesome is Ember.js"}));
App.posts.pushObject(App.Post.create({id:'1', title: "Test post 2", body: "I love working on awesome projects"}));
App.posts.pushObject(App.Post.create({id:'2', title: "Test post 3", body: "I like cats"}));
App.ApplicationController = Ember.ObjectController.extend();
App.ApplicationView = Ember.View.extend({
templateName: "application_view"
});
App.PostsController = Ember.ArrayController.extend();
App.PostsView = Ember.View.extend({
templateName: 'posts_view'
});
App.PostController = Ember.ObjectController.extend();
App.PostView = Ember.View.extend({
templateName: 'post_view'
});
App.AboutController = Ember.ObjectController.extend();
App.AboutView = Ember.View.extend({
templateName: 'about_view'
});
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
goToPostsIndex: Ember.Route.transitionTo('posts.index'),
goToAbout: Ember.Route.transitionTo('about'),
goToShowPost: Ember.Route.transitionTo('posts.index.show'),
index: Ember.Route.extend({
route: '/',
redirectsTo: "posts.index"
}),
posts: Ember.Route.extend({
route: '/posts',
connectOutlets: function (router) {
router.get('applicationController').connectOutlet('posts', App.posts);
},
index: Ember.Route.extend({
route: '/',
connectOutlets: function (router) {
router.get('applicationController').connectOutlet('posts', App.posts);
},
show: Ember.Route.extend({
route: '/:post_id',
connectOutlets: function (router, post) {
router.get('postsController').connectOutlet('post', post);
},
deserialize: function(router, params) {
var id = params.post_id,
i = 0;
for (i = 0; i < App.posts.length; i += 1) {
if (App.posts[i].id === id) {
return App.posts[i];
}
}
},
serialize: function(router, context) {
var rtnVal = {},
id = context.get('id');
if (context) {
rtnVal = {post_id: id};
}
return rtnVal;
}
})
}),
}),
about: Ember.Route.extend({
route: '/about',
connectOutlets: function (router) {
router.get('applicationController').connectOutlet('about');
}
})
})
});
`
Only leaf routes are meant to be navigable (i.e. the reason posts.index wasn't navigable is that it had a child route).
http://jsfiddle.net/NbPpM/8/
I moved posts.index.show to posts.show -- the way it is here is a common pattern, at least in my experience.
I'm trying to get the Ember.js routing examples working.
I have a fiddle running pretty well with a posts example.
The routes update with the id of the post and display the correct item.
The only problem is if you load the page cold with the post url, it doesn't work (doesn't show anything in the posts view).
Any idea?
Here's the fiddle: http://jsfiddle.net/bdennyw/GzYtc/5/
and the fiddle with the full url
http://jsfiddle.net/bdennyw/GzYtc/5/show/#/posts/1
Thanks
Edit: Including the js code below:
$(function () {
App.initialize(App.Router.create());
});
window.App = Ember.Application.create();
App.Post = Ember.Object.extend({
title: null,
body: null
});
App.posts = [];
App.posts.pushObject(App.Post.create({id:'0', title: "Test post 1", body: "How awesome is Ember.js"}));
App.posts.pushObject(App.Post.create({id:'1', title: "Test post 2", body: "I love working on awesome projects"}));
App.posts.pushObject(App.Post.create({id:'2', title: "Test post 3", body: "I like cats"}));
App.ApplicationController = Ember.ObjectController.extend();
App.ApplicationView = Ember.View.extend({
templateName: "application_view"
});
App.PostsController = Ember.ArrayController.extend();
App.PostsView = Ember.View.extend({
templateName: 'posts_view'
});
App.PostController = Ember.ObjectController.extend();
App.PostView = Ember.View.extend({
templateName: 'post_view'
});
App.AboutController = Ember.ObjectController.extend();
App.AboutView = Ember.View.extend({
templateName: 'about_view'
});
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
goToPostsIndex: Ember.State.transitionTo('posts.index'),
goToAbout: Ember.State.transitionTo('about'),
goToShowPost: Ember.State.transitionTo('posts.show'),
index: Ember.Route.extend({
route: '/',
redirectsTo: "posts.index"
}),
posts: Ember.Route.extend({
route: '/posts',
index: Ember.Route.extend({
route: '/',
connectOutlets: function (router) {
router.get('applicationController').connectOutlet('posts', App.posts);
}
}),
show: Ember.Route.extend({
route: '/:post_id',
connectOutlets: function (router, post) {
router.get('postsController').connectOutlet('post', post);
},
deserialize: function(router, params) {
var id = params.post_id,
i = 0;
for (i = 0; i < App.posts.length; i += 1) {
if (App.posts[i].id === id) {
return App.posts[i];
}
}
},
serialize: function(router, context) {
var rtnVal = {},
id = context.get('id');
if (context) {
rtnVal = {post_id: id};
}
return rtnVal;
},
}),
}),
about: Ember.Route.extend({
route: '/about',
connectOutlets: function (router) {
router.get('applicationController').connectOutlet('about');
}
})
})
});
By adding the enableLogging: true property to the router, you can see the routes taken by the router. So when loading the page, here is the trace:
STATEMANAGER: Entering root
Sending event 'navigateAway' to state root.
Sending event 'unroutePath' to state root.
Sending event 'routePath' to state root.
Entering root.posts
Sending event 'routePath' to state root.posts.
Entering root.posts.show
Sending event 'routePath' to state root.posts.show.
So It does'nt go through post.index which connect the outlet of your posts. So the view responsible of diplaying posts is not shown, and finally, the view responsible of displaying a post is not shown.
You could nest the show state as a child of the posts.index state, and it works, but I don't know if it's conceptually right.