angular-ui/bootstrap: Testing a controller that uses a dialog - javascript

I've a controller that uses a Dialog from angular-ui/bootstrap:
function ClientFeatureController($dialog, $scope, ClientFeature, Country, FeatureService) {
//Get list of client features for selected client (that is set in ClientController)
$scope.clientFeatures = ClientFeature.query({clientId: $scope.selected.client.id}, function () {
console.log('getting clientfeatures for clientid: ' + $scope.selected.client.id);
console.log($scope.clientFeatures);
});
//Selected ClientFeature
$scope.selectedClientFeature = {};
/**
* Edit selected clientFeature.
* #param clientFeature
*/
$scope.editClientFeature = function (clientFeature) {
//set selectedClientFeature for data binding
$scope.selectedClientFeature = clientFeature;
var dialogOpts = {
templateUrl: 'partials/clients/dialogs/clientfeature-edit.html',
controller: 'EditClientFeatureController',
resolve: {selectedClientFeature: function () {
return clientFeature;
} }
};
//open dialog box
$dialog.dialog(dialogOpts).open().then(function (result) {
if (result) {
$scope.selectedClientFeature = result;
$scope.selectedClientFeature.$save({clientId: $scope.selectedClientFeature.client.id}, function (data, headers) {
console.log('saved.');
}, null);
}
});
};
});
I'm almost completely new to testing, and figured that maybe I need to test two things:
That a dialog opens when $scope.editClientFeature() is called
That $save is called successfully after a dialog is closed and returns a 'result'
My really messed up test now looks like this:
describe('ClientFeatureController', function () {
var scope, $dialog, provider;
beforeEach(function () {
inject(function ($controller, $httpBackend, $rootScope, _$dialog_) {
scope = $rootScope;
$dialog = _$dialog_;
//mock client
scope.selected = {};
scope.selected.client = {
id: 23805
};
$httpBackend.whenGET('http://localhost:3001/client/' + scope.selected.client.id + '/clientfeatures').respond(mockClientFeatures);
$controller('ClientFeatureController', {$scope: scope});
$httpBackend.flush();
});
});
it('should inject dialog service from angular-ui-bootstrap module', function () {
expect($dialog).toBeDefined();
console.log($dialog); //{}
});
var dialog;
var createDialog = function (opts) {
dialog = $dialog.dialog(opts);
};
describe('when editing a clientfeature', function () {
createDialog({});
console.log(dialog); //undefined
// var res;
// var d;
// beforeEach(function () {
// var dialogOpts = {
// template: '<div>dummy template</div>'
// };
// console.log(dialog);
// d = $dialog.dialog(dialogOpts);
// d.open();
// });
//
// it('should open a dialog when editing a client feature', function () {
// expect(d.isOpen()).toBe(true);
// });
});
});
The immediate problem now is that I'm unable to create/open a dialog. I get the following error:
Chrome 25.0 (Mac) ClientFeatureController when editing a clientfeature encountered a declaration exception FAILED
TypeError: Cannot call method 'dialog' of undefined
It would be great if someone has already written a test for a similar use case and can provide me with an example as I'm pretty lost.
Thanks,
Shaun

I was struggling with the same problem until right now, after trolling the the github repo i found the dialog tests and used that as a starting point :
var $dialog,$scope,$httpBackend;
beforeEach(module('ui.bootstrap.dialog'));
beforeEach(function(){
inject(function (_$dialog_, _$httpBackend_, $controller){
$dialog = _$dialog_;
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('/appServer/list')
.respond([{
id:1,
name:'test1'
},
{
id:2,
name:'test2'
},
{
id:3,
name:'test3'
}]);
//setup controller scope
scope = {};
ServerCtrl = $controller('ServerCtrl', {
$scope: scope,
$dialog:$dialog
});
});
});

I also prefer a proper mock. When it is not available, i patch the service
To test this:
$dialog.messageBox(title, msg, btns)
.open()
.then(function (result) {
if (result == 'ok') {
// block executed if user click OK
}
});
You can patch $dialog like this:
$dialog.messageBox = function (title, msg, btns) {
return {
open: function () {
return {
then: function (callback) {
callback('ok'); // 'ok' will be set to param result
}
}
}
}
};

Personally I try to mock all services out. If the ui-bootstrap project does not provide a $dialog mock, you should open a bug ticket there and ask them for one. However creating one is as easy.
The mock service should have fake methods that do nothing but return promises. It should also give you a method to flush all asynchronous methods to make it easier to do synchronous testing.

I find it clearest to write my own mock of the dialog. Here's an example of mocking out a dialog to simulate "yes" being chosen.
Code under test
.controller('AdminListingCtrl', function AdminListingController($scope, $dialog, houseRepository) {
$scope.houses = houseRepository.query();
$scope.remove = function (house) {
var dlg = $dialog.messageBox('Delete house', 'Are you sure?', [
{label: 'Yep', result: 'yes'},
{label: 'Nope', result: 'no'}
]);
dlg.open().then(function (result) {
if (result == 'yes') {
houseRepository.remove(house.id);
$scope.houses = houseRepository.query();
}
});
};
}
Tests
describe('when deleting a house', function () {
var fakeDialog = {
open: function()
{
return {
then: function(callback) {
callback("yes");
}
};
}
};
beforeEach(inject(function($dialog) {
spyOn($dialog, 'messageBox').andReturn(fakeDialog);
}));
it('should call the remove method on the houseRepository', function () {
scope.remove({id: 99});
expect(houseRepository.remove).toHaveBeenCalledWith(99);
});
// etc
});

Related

JS works with .include but fails with .extend

I made this JS to add a functionality on a form (backend) that computes a field when the event click is triggered. So far the code recomputes when I use ".include" but the whole JS in all views fail since I'm using ".include". When I try to use extend my code does nothing. Looks like Odoo doesn't add the extended code to the JS engine so my question is, what am I doing wrong here? Is there something else I need to add so my code works as extended?
odoo.define('med_care.TestRenderer', function (require) {
"use strict";
var viewRegistry = require('web.view_registry');
var FormRenderer = require('web.FormRenderer');
var FormView = require('web.FormView');
var TestFormRenderer = FormRenderer.extend({
events: _.extend({}, FormRenderer.prototype.events, {
'click .sign_selector': '_onSignSelectorClicked',
}),
init: function (parent, state, params) {
this._super.apply(this, arguments);
this.fields = state.fields;
this._onSignSelectorClicked = _.debounce(this._onSignSelectorClicked, 300, true);
},
confirmChange: function (state, id, fields, e) {
var self = this;
if (state.model == 'med.test') {
return this._super.apply(this, arguments).then(function () {
self.canBeSaved(self.state.id);
});
}
},
_onSignSelectorClicked: function (event) {
this.state.data.telephone = '333';
if (this.state.model == 'med.test') {
var info_test = {
dataPointID: this.state.id,
changes: {telephone: '333'},
viewType: "form",
notifyChange: true
};
var odoo_event = this.trigger_up('field_changed', info_test);
this.confirmChange(this.state, this.state.id, "telephone",
odoo_event)
}
},
});
var TestFormView = FormView.extend({
config: _.extend({}, FormView.prototype.config, {
Renderer: TestFormRenderer,
}),
});
viewRegistry.add('test_form', TestFormView);
return TestFormView;
});

EmberJS: Refreshing a model?

Hello again everyone.
EDIT: I want to emphasize that I can find no docs on the solution for this.
I am using a route to perform a search query to my server. The server does all the data logic and such and returns a list of objects that match the keywords given. I am taking those results and feeding them to the model so that I can use the {{#each}} helper to iterate over each result.
The problem I am having is that the model does not want to refresh when the searchText (search input) changes. I've tried several things. I'm not worried about creating too many ajax requests as my server performs the search query in 2ms. Here's what I have now.
App.SearchView = Ember.View.extend({...
EDIT:
Thank you for the answer.
App.SearchView = Ember.View.extend({
didInsertElement: function () {
this._super();
Ember.run.scheduleOnce('afterRender', this, this.focusSearch);
},
focusSearch: function () {
$(".searchInput").focus().val(this.get("controller").get('searchTextI'));
}
});
App.SearchRoute = Ember.Route.extend({
model: function () {
return this.controllerFor('search').processSearch();
}
});
App.SearchController = Ember.ArrayController.extend({
searchTextI: null,
timeoutid: null,
processid: null,
updateSearch: function () {
if(this.get('timeoutid')) {clearTimeout(this.get('timeoutid')); }
var i = this.get('searchTextI');
var sc = this;
clearTimeout(this.get('processid'));
this.controllerFor('index').set('searchText', i); //set the search text on transition
if(i.length < 3) {
this.set('timeoutid', setTimeout(function () {
sc.controllerFor('index').set("transitioningFromSearch", true);
sc.transitionToRoute('index');
}, 1500));
} else {
var self = this;
this.set('processid', setTimeout(function() {
self.processSearch().then(function(result) {
self.set('content', result);
});
}, 1000));
}
}.observes('searchTextI'),
processSearch: function () {
return $.getJSON('http://api.*********/search', { 'token': guestToken, 'search_query': this.get('searchTextI') }).then(function(data) { if(data == "No Results Found.") { return []; } else { return data; } }).fail(function() { return ["ERROR."]; });
}
});
Don't observe anything within a route and don't define any computed properties. Routes are not the place for these. Apart from that, the model doesn't fire because controller is undefined.
One way to achieve what you want:
App.SearchRoute = Ember.Route.extend({
model: function () {
this.controllerFor('search').searchQuery();
}.observes('controller.searchText') //not triggering an ajax request...
});
App.SearchController = Ember.ArrayController.extend({
searchQuery: function() {
return $.getJSON('http://api.**************/search', { 'token': guestToken, 'search_query': t }).fail(function() {
return null; //prevent error substate.
});
}
onSearchTextChange: function() {
var controller = this;
this.searchQuery().then(function(result) {
controller.set('content', result);
});
}.observes('searchText')
});
Putting an observes on the model hook is not going to do anything. You should simply do what you were thinking of doing and say
processSearch: function () {
this.set('content', $.getJSON....);
}

Failed to load routed module requirejs? durandal bug?

I created an Asp.Net MVC and used nuget to add HotTowel (V2.0.1 of 9/11/2013). I created a couple of ViewModel, Models. However, I got the following error.
"Failed to load routed module (viewmodels/myVM). Details: Load timeout for modules: durandal/plugins/router\nhttp://requirejs.org/docs/errors.html#timeout"
Is it the problem of durandal/plugins/router? Or it can be caused by some code I added?
The error occurred at Scripts/durandal/system.js.
var logError = function(error) {
if(error instanceof Error){
throw error;
}
throw new Error(error);
};
The following is the VM code.
define(['services/datacontext', 'durandal/plugins/router', 'services/logger'],
// Remove the durandal/plugins/router and the functions will get rid of the error.
function (datacontext, router, logger) {
var title = 'Event';
var vm = {
activate: activate,
deactivate: deactivate,
refresh: refresh,
events: events,
title: title
};
return vm;
//#region Internal Methods
var events = ko.observableArray();
function activate() {
logger.log(title + ' View Activated', null, title, true);
return datacontext.getEventPartials(events);
}
var deactivate = function () {
events([]);
};
var refresh = function () {
return datacontext.getEventPartials(events, true);
};
//#endregion
});
The following is the call stack
logError [system.js] Line 92 Script
Anonymous function [router.js] Line 359 Script
[External Code]
Anonymous function [system.js] Line 260 Script
[External Code]
[Async Call]
....
Code at router.js,
isProcessing(true);
router.activeInstruction(instruction);
if (canReuseCurrentActivation(instruction)) {
ensureActivation(activator.create(), currentActivation, instruction);
} else {
system.acquire(instruction.config.moduleId).then(function(module) {
var instance = system.resolveObject(module);
ensureActivation(activeItem, instance, instruction);
}).fail(function(err){
system.error('Failed to load routed module (' + instruction.config.moduleId + '). Details: ' + err.message);
});
}
}
And previous one in system.js.
acquire: function() {
var modules,
first = arguments[0],
arrayRequest = false;
if(system.isArray(first)){
modules = first;
arrayRequest = true;
}else{
modules = slice.call(arguments, 0);
}
return this.defer(function(dfd) {
require(modules, function() {
var args = arguments;
setTimeout(function() {
if(args.length > 1 || arrayRequest){
dfd.resolve(slice.call(args, 0));
}else{
dfd.resolve(args[0]);
}
}, 1);
}, function(err){
dfd.reject(err);
});
}).promise();
},
Based on the comments I'd recommend to modify the vm code slightly, so that all variables that are returned via vm are defined before use. In addition 'plugins/router' is used instead of 'durandal/plugins/router'.
define(['services/datacontext', 'plugins/router', 'services/logger'],
// Remove the durandal/plugins/router and the functions will get rid of the error.
function (datacontext, router, logger) {
var title = 'Event';
var events = ko.observableArray();
var deactivate = function () {
events([]);
};
var refresh = function () {
return datacontext.getEventPartials(events, true);
};
var vm = {
activate: activate,
deactivate: deactivate,
refresh: refresh,
events: events,
title: title
};
return vm;
//#region Internal Methods
function activate() {
logger.log(title + ' View Activated', null, title, true);
return datacontext.getEventPartials(events);
}
//#endregion
});
BTW the name Internals methods is misleading as everything in that region is returned via vm. I prefer to work with named function instead, which get created before the return statement if they are returned and place them below the return statement in a Internal methods region if they are not returned.
define(['services/datacontext', 'plugins/router', 'services/logger'],
function( datacontext, router, logger ) {
var title = 'Event';
var events = ko.observableArray();
function deactivate () {
events([]);
}
function refresh () {
return datacontext.getEventPartials(events, true);
}
function activate () {
logger.log(title + ' View Activated', null, title, true);
return datacontext.getEventPartials(events);
}
return {
activate: activate,
deactivate: deactivate,
refresh: refresh,
events: events,
title: title
};
//#region Internal Methods
//#endregion
});

Custom modal Durandal

I have been trying to work with the durandal framework and must say I'm very pleased. I'm using the Movie tutorial by http://stephenwalther.com/ and have made some changes. With help of the Ryan Keeters Youtube video's. I have accomplished editing movies with modals.
But I can't get it to work to add new movies via a modal.
I have created a add.html and add.js but when the link for creating a new movie is clicked and the modal shows it shows the page i'm at in the middle (like a loop when I click further).
THis is my binding on show.html:
<a data-bind="click: viewAddMovieModal">Add Movie</a>
And this is the javascript:
define(function (require) {
var self = this;
var vm = {
activate: activate,
title: 'movies page',
movies: ko.observableArray([])
};
//return vm;
// self.moviesRepository = require("repositories/moviesRepository");
self.router = require('durandal/plugins/router');
self.system = require('durandal/system');
self.app = require('durandal/app');
self.movie = require('viewmodels/movie');
self.addmovie = require('viewmodels/add');
//self.Movies = ko.observableArray([]);
self.viewMovieModal = function (movie, element) {
self.app.showModal(movie).then(function (result) {
if (result) {
//self.app.showMessage(result.toString(), result.toString());
vm.movies.remove(movie);
}
}).fail(function (result) {
self.app.showMessage(result.toString(), "Something went wrong!");
});
};
self.viewAddMovieModal = function (addmovie,element) {
// self.app.showModal(addmovie).then(function (result) {
// self.app.showMessage(result.toString(), result.toString());
// });
self.app.showModal(null, element, addmovie);
};
init();
function init() {
vm.movies.push(new movie("Star wars", "Piet"));
vm.movies.push(new movie("Harry Potter", "Jan"));
vm.movies.push(new movie("Hangover", "Klaas"));
}
function activate() {
self.system.log("I get in the activate function!");
}
return {
activate: activate,
movie: movie,
addmovie:addmovie,
vm:vm,
viewMovieModal: viewMovieModal
};
});

javascript namespace issue in socket.io + sencha

This could be a very basic Javascript I know but I just can't get it..
Ext.regController('Chat', {
initSocketConnection: function() {
this.chatStore = new App.Store.Chat();
...
this.socket = io.connect(settings.get('server'), {port: 8080});
this.socket.on(
'message',
this.addMessageToChatStore
);
},
addMessageToChatStore: function(message) {
console.log(message);
console.log(this); << console shows the 'this' has become SocketNameSpace
this.chatStore.add(message); << this line error with "Undefined" chatStore
this.send(message);
},
Console print out shows that the "this" in the addMessageToChatStore function is "SocketNamespace"
How to I get rid of the error?
To generalize the problem. I think it's better to describe it as function chain calling dilemma.
A class has some local var that's instance of another class. When this var listens on certain events, it calls the parent's class's method. The problem is when this method is called, it's under the context of the other class and hence the scope has changed and the access to the original parent's class methods are denied.
In javascript, the this variable is determined by the caller. You could use a self executing function to ensure you're referencing the correct entity rather than using this:
Ext.regController('Chat', (function() {
var self = {
initSocketConnection: function() {
self.chatStore = new App.Store.Chat();
...
self.socket = io.connect(settings.get('server'), {port: 8080});
self.socket.on(
'message',
this.addMessageToChatStore
);
},
addMessageToChatStore: function(message) {
console.log(message);
self.chatStore.add(message);
self.send(message);
}
};
return self;
}()));
Update
Based on the full snippet, I'm not sure where the render function is defined, if render is a global function then self.viewChat = render({xtype : 'App.View.Chat'}); will suffice, otherwise it may be defined on this (the this that is defined for the call to index) so the following may suffice self.viewChat = this.render({xtype : 'App.View.Chat'});.
If I were a betting man, I'd go for the latter, so the code would be (with commented out code removed):
Ext.regController('Chat', (function() {
var self = {
index: function() {
if (!self.socket) {
self.initSocketConnection();
}
self.showChat();
},
/**
* init the socket connection to the node.js server
* using user settings
*
*/
initSocketConnection: function() {
self.chatStore = new App.Store.Chat();
self.chatStore.add({
user: "Figo",
message: "Welcome!"
});
self.configStore = Ext.StoreMgr.get('ConfigStore');
var settings = self.configStore.getAt(0);
self.socket = io.connect(settings.get('server'), {
port: 8080
});
// Event Listener
self.socket.on('connect', self.registerUser);
self.socket.on('message', self.addMessageToChatStore);
App.on('newMsg', self.sendMessageToServer);
},
sendMessageToServer: function(msg) {
self.socket.send(msg);
},
addMessageToChatStore: function(message) {
console.log(message);
console.log(this);
console.log(this.parent);
self.chatStore.add(message);
self.socket.send(message);
},
registerUser: function() {
self.configStore = Ext.StoreMgr.get('ConfigStore');
var settings = self.configStore.getAt(0);
var user = {
nickname: settings.get('nickname'),
gravatar: settings.get('gravatar')
};
console.log(user);
self.socket.send(user);
},
/**
* Show chat view
*/
showChat: function() {
if (!self.viewChat) {
self.viewChat = this.render({
xtype: 'App.View.Chat'
});
self.viewChat.query('#settingsButton')[0].on('tap', self.showConfig, self);
}
self.application.viewport.setActiveItem(
self.viewChat, {
type: 'slide',
direction: 'left'
});
},
/**
* Show config View
*/
showConfig: function() {
Ext.dispatch({
controller: 'Viewport',
action: 'showConfig'
});
}
};
return self;
}()));
For #Rich.okelly
Here's the error (highlighted in code)
Uncaught TypeError: Object #<Object> has no method 'render' app.all.js:438
self.showChat app.all.js:438
self.index app.all.js:351
Ext.util.Dispatcher.Ext.extend.dispatch sencha-touch-debug.js:10630
Ext.dispatch sencha-touch-debug.js:10667
Ext.regController.showChat app.all.js:293
fire sencha-touch-debug.js:979
Ext.util.Observable.Ext.extend.fireEvent sencha-touch-debug.js:595
And here's the full code (other code within the same project not shown):
Ext.regController('Chat',(function() {
var self = {
/**
* Index action
*
* #return {void}
*/
index: function() {
if (!self.socket) {
self.initSocketConnection();
}
self.showChat();
},
/**
* init the socket connection to the node.js server
* using user settings
*
*/
initSocketConnection: function() {
self.chatStore = new App.Store.Chat();
self.chatStore.add({user: "Figo", message: "Welcome!"});
self.configStore = Ext.StoreMgr.get('ConfigStore');
//this.configStore = new App.Store.Config()
var settings = self.configStore.getAt(0);
//this.socket = new App.util.Socketio(settings.get('server'), {port: 4000});
//this.socket.connect();
self.socket = io.connect(settings.get('server'), {port: 8080});
/*
this.socket.on('message', function (data) {
console.log(data);
this.parent.addMessageToChatStore(data);
this.emit('message', data);
});
this.socket.on('message',
this.addMessageToChatStore
);
*/
// Event Listener
self.socket.on(
'connect',
self.registerUser
);
self.socket.on(
'message',
self.addMessageToChatStore
);
App.on(
'newMsg',
self.sendMessageToServer
);
},
sendMessageToServer: function(msg){
self.socket.send(msg);
},
addMessageToChatStore: function(message) {
console.log(message);
console.log(this);
console.log(this.parent);
//if (!this.chatStore)
//this.chatStore = new App.Store.Chat();
self.chatStore.add(message);
//App.Controller.Chat.chatStore.add(message);
self.socket.send(message);
},
registerUser: function() {
self.configStore = Ext.StoreMgr.get('ConfigStore');
var settings = self.configStore.getAt(0);
var user = {
nickname: settings.get('nickname'),
gravatar: settings.get('gravatar')
};
console.log(user);
self.socket.send(user);
},
/**
* Show chat view
*/
showChat: function() {
if (!self.viewChat) {
self.viewChat = self.render({ << fails over here
xtype: 'App.View.Chat'
});
self.viewChat.query('#settingsButton')[0].on(
'tap',
self.showConfig,
self
);
}
self.application.viewport.setActiveItem(
self.viewChat,
{
type: 'slide',
direction: 'left'
}
);
},
/**
* Show config View
*/
showConfig: function() {
Ext.dispatch({
controller: 'Viewport',
action : 'showConfig'
});
}
};
return self;
}()));

Categories

Resources