How to mock router state in ember + jasmine - javascript

In my jasmine test suite I want to navigate my router to the right state. Something like this:
describe("The Router", function(){
beforeEach(function(){
router = App.Router.create();
router.transitionTo('foo.bar');
}
});
but if I do so, I get an Cannot call method 'disconnectOutlet' of undefined error. That happens cuz I am calling
bar: Ember.Route.extend({
connectOutlets: function(router, context){
router.get('applicationController').connectOutlet('bla', 'blub');
}
}),
at this router transition. I tried somehow to init my applicationController like
applicationController = App.ApplicationController.create();
but it doesnt change the error. So how can I mock to be in the right router state?

How are you testing this with jasmine today? Are you going to the browse with each change or trying to run it from the command line with something like jasmine-phantom-node / jasmine-node?
One example might look something like the below. In the setup below I'm testing a view but the same mocking / spying ideas apply to the router example you have above.
require('static/script/vendor/filtersortpage.js');
require('static/script/app/person.js');
describe ("PersonApp.PersonView Tests", function(){
var sut, router, controller;
beforeEach(function(){
sut = PersonApp.PersonView.create();
router = new Object({send:function(){}});
controller = PersonApp.PersonController.create({});
controller.set("target", router);
sut.set("controller", controller);
});
it ("does not invoke send on router when username does not exist", function(){
var event = {'context': {'username':'', 'set': function(){}}};
var sendSpy = spyOn(router, 'send');
sut.addPerson(event);
expect(sendSpy).not.toHaveBeenCalledWith('addPerson', jasmine.any(String));
});
it ("invokes send on router with username when exists", function(){
var event = {'context': {'username':'foo', 'set': function(){}}};
var sendSpy = spyOn(router, 'send');
sut.addPerson(event);
expect(sendSpy).toHaveBeenCalledWith('addPerson', 'foo');
});
it ("does not invoke set context when username does not exist", function(){
var event = {'context': {'username':'', 'set': function(){}}};
var setSpy = spyOn(event.context, 'set');
sut.addPerson(event);
expect(setSpy).not.toHaveBeenCalledWith('username', jasmine.any(String));
});
it ("invokes set context to empty string when username exists", function(){
var event = {'context': {'username':'foo', 'set': function(){}}};
var setSpy = spyOn(event.context, 'set');
sut.addPerson(event);
expect(setSpy).toHaveBeenCalledWith('username', '');
});
});
Here is the jasmine test above in a real project if you want to see it with some context
Here is the view under test (to help make sense of the above jasmine tests)
PersonApp.PersonView = Ember.View.extend({
templateName: 'person',
addPerson: function(event) {
var username = event.context.username;
if (username) {
this.get('controller.target').send('addPerson', username);
event.context.set('username', '');
}
}
});

Related

Angular-Jasmine missing scopes in $httpbackend respond anonymous function

I am writing some end-to-end tests with Protractor for and Angular app. I am currently trying to mock some http responses using angular-mock and am running into a problem with scoping that I don't understand.
var protractor = require('protractor');
var ngMockE2E = require('ng-mock-e2e');
var testData = require('./e2e-data.json');
describe('DataEater', function() {
var $httpBackend = ngMockE2E.$httpBackend;
var appUrl = browser.baseUrl + 'scheduler/data-eater/';
var self = this;
self.testData2 = require('./e2e-data.json');
beforeEach(function() {
browser.get(appUrl);
ngMockE2E.addMockModule();
ngMockE2E.addAsDependencyForModule('dataEater');
$httpBackend.when('GET', '/scheduler/tasks/queue/')
.respond(function(method, url, data) {
console.log(testData);
console.log(self.testData2);
return [200, self.testData.history, {}];
});
Why are neither testData or testData2 defined? How can I get this data scoped properly so that I can return it as part of the response?
The problem might be that you need to explicitly pass the $scope to your controller. Try something like the following code:
beforeEach(
inject(function (_$controller_, _$rootScope_) {
myController = _$controller_('myControllerName', {
$scope: _$rootScope_.$new()
});
})
);

Express : Calling functions on app.param in multiple routes with same para name

I have 2 routes defined in 2 separate files but a parameter RecordId is same for both the routes:
I am expecting that :
1) whenever I call /api/CountryMaster/:RecordId,
only RecordByIdCtry function should be called. &
2) when I am calling /api/commonMaster/:MasterName/:RecordId,
only RecordByIdCmn function should be called.
However, both functions are getting called with the order being as set in javascript.
i.e.
require('../app/routes/commonMaster.server.routes.js')(app);
require('../app/routes/countryMaster.server.routes.js')(app);
How can i stop these & ensure that only one method is called.
//CountryMaster.js
var ctrl = require('../../app/controllers/CountryMaster.server.ctrl.js');
var users = require('../../app/controllers/user.server.ctrl.js');
module.exports = function (app)
{
app.route('/api/CountryMaster')
.get(users.requiresLogin,ctrl.list)
.post(users.requiresLogin,ctrl.create);
app.route('/api/CountryMaster/:RecordId')
.get(ctrl.read)
.put(users.requiresLogin, ctrl.hasAuthorization, ctrl.update)
.delete(users.requiresLogin, ctrl.hasAuthorization, ctrl.delete);
app.param('RecordId', ctrl.RecordByIdCtry);
}
//CommonMaster.js
var ctrl = require('../../app/controllers/commonMaster.server.ctrl.js');
var users = require('../../app/controllers/user.server.ctrl.js');
module.exports = function (app)
{
app.route('/api/commonMaster/:MasterName')
.get(users.requiresLogin,ctrl.list)
.post(users.requiresLogin,ctrl.create);
app.route('/api/commonMaster/:MasterName/:RecordId')
.get(ctrl.read)
.put(users.requiresLogin, ctrl.hasAuthorization, ctrl.update)
.delete(users.requiresLogin, ctrl.hasAuthorization, ctrl.delete);
app.param('MasterName', ctrl.MasterName);
app.param('RecordId', ctrl.RecordByIdCmn);
}
How can I ensure that only one method is called..
In your code, app is always the same app, so you're basically declaring two handlers for the same parameter, which isn't going to work.
You should use entirely separate routers instead:
// CountryMaster.js
...
module.exports = function(app) {
var router = require('express').Router();
router.route('/')
.get(...)
.post(...);
router.route('/:RecordId')
.get(...)
.put(...)
.delete(...);
// The magic:
router.param('RecordId', ctrl.RecordByIdCtry);
// Mount the router on `/api/CountryMaster`
app.use('/api/CountryMaster', router);
};
And similar for CommonMaster.js.

React-Flux: Error with AppDispatcher.register

I am trying to set up the most basic app in Flux-React. Its sole goal to is fire an Action, which gets sent through the Dispatcher to a Store that has registered with the Dispatcher. The store the logs the payload to Console.
Everything besides the Store is working well, but as soon as it hits AppDispatcher.register, Flux throws the following error:
Uncaught TypeError: Cannot set property 'ID_1' of undefined
Here is the code of the file causing the error, but I've put up the entire project at https://github.com/bengrunfeld/react-flux-dispatcher-error, and you can find the offending file in src/js/stores/AppStores.js
var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var AppConstants = require('../constants/AppConstants');
var assign = require('object-assign');
var CHANGE_EVENT = 'change';
var AppStore = assign({}, EventEmitter.prototype, {
emitChange: function() {
this.emit(CHANGE_EVENT);
}
});
AppDispatcher.register(function(payload){
console.log(payload);
return true;
})
module.exports = AppStore;
Because of the drought of documentation of biblical proportions for Facebook Flux, I didn't know that I was using code from previous versions.
In AppDispatcher.js, you need to define AppDispatcher in the following way using the new keyword:
var Dispatcher = require('flux').Dispatcher;
var assign = require('object-assign');
var AppDispatcher = assign(new Dispatcher(), {
handleViewAction: function(action) {
this.dispatch({
source: 'VIEW_ACTION',
action: action
});
}
});
module.exports = AppDispatcher;
Here is a link to a repository with the working code: https://github.com/bengrunfeld/react-flux-simple-app

Render a View of Backbone Model returns undefined

Lets say i have a JSON like this:
JSON example, my json is validated on jsonlint and it works.
json_object = {
"texts_model": {
"hello": "hola",
"icon_text": "Icon!"
},
"collection_vias": {
"text": "hola",
"icon_text": "Icon!"
}
};
I have made a Collection that parse the contents of the json and generates model and collections from this json.
App.TemplatesCollection = Backbone.Model.extend({
model: App.TemplateModel,
url: TEMPLATES_SERVICE,
initialize: function(){
this.fetch({
success: (function () {
console.log(' Success ');
}),
error:(function (e) {
//console.log(' Error: ' + e);
}),
complete:(function (e) {
console.log(' Fetch completado con exito. ');
})
});
},
//Here i generate all my models and collections.
parse: function(response){
App.texts = new App.TemplateModel(response.text_model);
App.vias = new App.ViasCollection(response.collection_vias);
return response;
},
//I was trying with the get function but i the only thing i got was undefined.
plain_texts: function(){
return( this.get('plain_texts') ) ;
}
});
And the view is like this:
App.TemplateView = Backbone.View.extend({
el: App.$main_content,
initialize: function(){
_.bindAll(this, 'render');
},
//Here i pass the template(html source) i want to render.
render: function(template){
var html = render(template, this.model.toJSON() );
App.$main_content.html(html);
return this;
}
});
And my start.js where they live all the declarations of my models and views:
//app
App = {
init: function(){
console.log('Iniciando...');
//variables y constantes
App.$main_content = $('#main-content-content');
App.$main_header = $('#main-header-content')
App.$main_navigation = $('#main-navigation-content');
//data
App.templates = new App.TemplatesCollection();
//views
App.templateView = new App.TemplateView({model: App.texts});
//router
App.router = new App.Router();
},
start: function(){
//init
App.init();
//router
Backbone.history.start();
}
}
And the router:
//router
App.Router = Backbone.Router.extend({
routes:{
"" : "index",
":web" : "url"
},
index: function(){
console.log("index");
//Here i do not know what to do, i mean do i have to instiate the View each time i go to index? or only render?
App.templateView = new App.TemplateView({model: App.texts});
App.templateView.render("sections/login/form");
},
url: function(web){
console.log(web);
}
});
//on document ready
$(function(){
App.start();
});
My problem is that when the html is loaded the only thing i have is:
"Uncaught TypeError: Cannot call method 'toJSON' of undefined "
But when i put this on the developer console:
App.templateView = new App.TemplateView({model: App.texts});
App.templateView.render("sections/login/form");
My view is rendered correctly.
Why my view isn't rendered on the load and only when i put my code on the developer console?
How can i render my model on the view on the router url?
Why do i have undefined on the html loaded on the developer console?
----EDIT---
All right,
I think i understand. Maybe I'm generating a problem of a thing that does not have to have a problem.
Now my Model is like this:
App.TemplatesCollection = Backbone.Model.extend({
model: App.TemplateModel,
url: TEMPLATES_SERVICE,
plain_texts: function(){
return this.get('texts') ;
},
initialize: function(){
this.fetch();
}
});
And the View:
App.TemplateView = Backbone.View.extend({
el: App.$main_content,
initialize: function(){
console.log(this.collection);
var ea = this.collection.get('texts');
console.log(ea);
},
render: function(template){
console.log(this.collection);
return this;
}
});
Now i see my collection inside my View.
But when i try to do this to get only the text version on my View:
var ea = this.collection.get('texts');
console.log(ea);
Im getting the error of undefined:
Uncaught TypeError: Cannot call method 'get' of undefined
Any idea about how can i resolve this?
I'm trying to solve this by myself. I do not want to look like im asking to develop my solution.
Thanks in advance.
It's a little hard to read, but at a quick glance: your App.texts = is in in the parse() function of your Collection. As a result, it gets called once the .fetch() on the collection is performed... until then, your App.texts is undefined!
If App.texts is undefined when you create the TemplateView, then the view's model will actually be undefined, and so, in the render, when the template engine you use is doing a toJSON(), it will say that it has an undefined value...
There may be other problems, but this one is the most glaring. Here is a quick&dirty fix: once the fetch() is done, your collection will trigger a reset event. That's your cue for doing the rendering. So, what you can do, is instead of passing the model to the View, you can pass the collection instead:
App.templateView = new App.TemplateView({collection: App.templates});
Now, in your View's initialize, you can do something like:
if(App.texts) {
//Your collection has already fetched and already went through parse()
this.model = App.texts;
this.render("sections/login/form");
} else {
//Your collection hasn't done the fetch yet
view = this;
this.collection.one("reset", function(){
view.model = App.texts;
view.render("sections/login/form");
});
}
If you give a collection as a param to a View's construction, it'll be stored in this.collection, same as with model. The idea here is to use the events to know when to do the rendering, and also let the view tell you when it's ready to render. You could also do something in your render() function to check if the model is defined!
To see if this analysis is correct, you can put a console.log(App.texts); in your index function in the router.
One way to make the code a bit more obvious is to initialize your App.texts and App.vias directly in your App's init. And give a reference to them to your AppTemplatesCollection if you really need to side-load them in the parse of AppTemplates' fetch(). The difference that makes is that you can bind to events from the App.vias collection ('add', 'remove', 'reset') or to the App.texts model ('change').
Another thing I noticed is that you have a collection of App.TemplateModel but you are still creating a App.texts where you put the result of the fetch into your own instance of App.TemplateModel? That doesn't seem right, maybe you have a reason for doing so, but in the most general case, the collection is suppose to handle the creation of the models, especially after a fetch!
The usual use case of the parse() method is to side-load data (other models/collection), change the format (from XML to something JS can understand) or to remove useless keys (for instance user: {id: ..., name: ... }, you'll return response.user so that Backbone can play with the correct hash directly). What you are doing here seems to fall out of this pattern so maybe it's a cause for worry?
In your code you have created collection as :
App.TemplatesCollection = Backbone.Model.extend({
//rest of the code
If you want to create a collection you need to extend Backbone.Collectionand not Backbone.Model.
App.TemplatesCollection = Backbone.Collection.extend({
//rest of the code

How to reduce repeated code in Backbone Router

In my routers initialize methods have the same code (the code is repeated 3 times!).
I got 3 routers, so if I want to refactor code (change name etc) I will have to jump to 3 separate files and apply changes on each file.
Here goes the code:
initialize: =>
# http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
#contentView = new Backbone.AppView(".js-content")
#searchView = new Backbone.AppView(".js-searchbox")
#sidebarView = new Backbone.AppView(".js-sidebar")
Is there some kind of technique to DRY this code?
Some kind of superclass?
I use coffeescript.
You need to create an Abstract Router that do the initialization of required views and then your specific Routers must extend it:
var BaseRouter = Backbone.Router.extend({
initialize : function(){
console.log('Native Router!');
this.contentView = new Backbone.AppView(".js-content");
this.searchView = new Backbone.AppView(".js-searchbox");
this.sidebarView = new Backbone.AppView(".js-sidebar");
}
});
var RouterOne = BaseRouter.extend({
initialize : function(){
BaseRouter.prototype.initialize.call(this);
//specific stuff
}
});
var RouterTwo = BaseRouter.extend({
initialize : function(){
BaseRouter.prototype.initialize.call(this);
//specific stuff
}
});
var router1 = new RouterOne();
var router2 = new RouterTwo();
It looks like the parts of your DOM that you are instantiating here can all be considered 'child views' of a 'parent view'. By this token, why don't you instantiate #pageView = new BB.AppView(...) and then in #pageView's render() method go ahead and instantiate these three 'child classes'?

Categories

Resources