ngDescribe Won't Complete Second Spec - angularjs

I'm having trouble with a chained spec when using ngDescribe. The second spec never finishes and times out. If I comment out the first spec, the second spec completes successfully. So, I know each spec is valid and works.
Here is the whole test with both specs.
var baseNotes = window.__mocks__['base-data/notes'];
var baseUsers = window.__mocks__['base-data/users'];
var baseDepartments = window.__mocks__['base-data/departments'];
// Create object to describe all the mocks
var mock = {
app: {
// Mock out all required DB functionality
LocalDb: {
'service': {
dbs: [
{
notes: true
}
]
},
'getDbInfo': function($q) {
return $q.when()
},
'allDocs': function($q) {
return $q.when(baseNotes);
},
'putDoc': function($q) {
var note = {};
note._id = 'xyz123';
console.log("Here is the note now");
console.log(note);
return $q.when(note);
}
},
Users: {
getUsers: function($q) {
return $q.when(baseUsers)
}
},
Departments: {
getDepartments: function($q) {
return $q.when(baseDepartments);
}
}
}
};
ngDescribe
({
name: 'Process Generic Notes Requests',
modules: ['app'],
// Must include $rootScope so that deps.step() is available
inject: ['Notes', 'NoteFactory', '$rootScope'],
mock: mock,
tests: function(deps){
beforeEach(function() {
// Preload the service with notes
deps.Notes.notes = baseNotes;
});
it('should refresh notes from db', function(done) {
// remove the notes from the service
deps.Notes.notes = [];
expect(deps.Notes.notes.length).toEqual(0);
deps.Notes.getNotes(true).then(
function(result) {
expect(result.length).toEqual(3);
done();
}
);
deps.step();
});
it('should fetch notes from memory', function(done) {
deps.Notes.getNotes().then(
function(result) {
expect(result.length).toEqual(3);
done();
}
);
deps.step();
});
it('should get specific note', function(done) {
deps.Notes.getNote(baseNotes[0]._id).then(
function(result) {
expect(result._id).toEqual(baseNotes[0]._id);
done();
}
);
deps.step();
});
it('should get last note', function(done) {
deps.Notes.getLastNote().then(
function(result) {
expect(result._id).toEqual('1436880052000-34lekd');
done();
}
);
deps.step();
});
it('should summarize notes', function() {
var summary = deps.Notes.summarizeNotes();
expect(summary.total).toEqual(3);
expect(summary.finished).toEqual(2);
expect(summary.remaining).toEqual(1);
console.log("Done processing summarize at " + Date.now());
});
}
})({
name: 'Process Update Note Requests',
modules: ['app'],
// Must include $rootScope so that deps.step() is available
inject: ['Notes', 'NoteFactory', '$rootScope'],
mock: mock,
tests: function (deps) {
var newNote;
beforeEach(function () {
// Preload the service with notes
deps.Notes.notes = baseNotes;
// Create a new note
newNote = deps.NoteFactory.createNote();
});
it('should save a new note', function (done) {
console.log("Starting save a note at " + Date.now());
console.log("Here is the note about to be saved");
console.log(newNote);
deps.Notes.updateNote(newNote).then(
function (response) {
dump('Response from update note!');
dump(response);
expect(response._id).toEqual('xyz123');
done();
},
function (error) {
dump('Failed to update note!');
dump(error);
expect('good').toEqual('bad');
done();
}
);
deps.step();
});
}
});

Related

Fetch data did not render in template

This is one of question from a Backbone newbie.
So, I was trying to use this.model.bind and this.model.on('change', this.render), but it could not work for my model. I checked my console & my model function getStoreByName. This is returning an array, but render function is rendering before the fetch array, that's why I want to bind model to view when model change.
Here is how far I have gotten so far.
This is Backbone view:
var storeTemplate = Backbone.View.extend({
initialize: function () {
console.log('view inti is running');
this.template = template;
this.model = new storeModel();
this.model.getStoreByName();
this.stores = this.model.get('stores');
this.model.on('change', this.render);
console.log(this.stores);
console.log('ready to call this.render()');
this.render();
console.log('end to call this.render()');
console.log('view init end');
},
render: function () {
console.log('render is start');
this.logger.log(Logger.LOG_LEVELS.TRACE, "Entering render.");
console.log(this.model.stores);
this.$el.html(_.template(this.template, { stores: this.model.get('stores') }));
return this;
}
});
return storesTemplate;
});
and this is my Backbone Model
var store= Backbone.Model.extend({
initialize: function () {
console.log('init models is running');
this.stores = [];
this.set({ 'stores': this.stores});
console.log(this.get('stores'));
this.service = Service;
},
getStoreByName: function () {
console.log('getting store');
stores = [];
this.service.getStoreByName(function (xml) {
$(xml).find("element").each(
function () {
var store = {
"storeID": $(this).find("ID").text(),
"storeType": $(this).find("Type").text(),
"storeName": $(this).find("Name").text(),
};
if (xml !== null) {
stores.push(store);
}
else {
this.model.set({ stores: [] });
}
}
);
that.set('stores', store)
},
);
},
})
return store;
});
Try this.listenTo(this.model,'change:stores', this.render);
If that doesn't work use promises. Update model like this:
getStoreByName: function() {
var deferred = $.Deferred();
var stores = [];
return this.service.getStoreByName(function(xml) {
if (xml === null) {
return Deferred.resolve([]);
}
$(xml).find("element").each(function() {
var store = {
"storeID": $(this).find("ID").text(),
"storeType": $(this).find("Type").text(),
"storeName": $(this).find("Name").text(),
};
stores.push(store);
});
return Deferred.resolve(stores);
});
this.model.set('stores', stores);
return deferred.promise();
}
and in the view you can do this.model.getStoreByName().then(this.render);

How do I unit test further .then and .catch branching in an Angular controller?

(function() {
'use strict';
angular
.module('walletInformation')
.controller('WalletInformationController', WalletInformationController);
/* #ngInject */
function WalletInformationController(
$q,
$scope,
config,
logger,
session,
$interpolate,
flowstack,
profileService,
walletInformationFormlyService,
postMsg
) {
// jshint validthis: true
var vm = this;
var app = $scope.app;
var user = session.get('profile').profile || {};
vm.saveWalletInformation = saveWalletInformation;
vm.onClickCancel = onClickCancel;
vm.deleteWallet = deleteWallet;
vm.model = {
walletInformation : {
firstName: user.name.first,
lastName: user.name.last,
emailAddress: user.emailAddress,
phoneNumber: user.mobilePhone.phoneNumber,
keepMeUpdated: user.preferences.receiveEmailNotification
}
};
activate();
////////////////
/**
* #function activate
* #description init function for the controller
* Calls paymentCard to get the
* */
function activate() {
createFormFields();
logger.info('Activated the Wallet Information View.');
}
function createFormFields() {
vm.fields = walletInformationFormlyService.getFormlyFields($scope.app.text);
}
function saveWalletInformation() {
var updatedProfile = createLegacyProfileInformation();
profileService.updateProfileInformation(updatedProfile).then(onSuccess).catch(onError);
function onSuccess(response) {
session.set('profile', {
profile: response.profile
});
if (vm.model.walletInformation.currentPassword && vm.model.walletInformation.newPassword) {
changePasswordRequest();
} else {
flowstack.add('accountManagement');
flowstack.next();
}
}
function onError(error) {
logger.error('Verify save Wallet Information Error: ', error);
}
//changePassword
function changePasswordRequest() {
var updatedPassword = {
data: {
currentPassword: vm.model.walletInformation.currentPassword,
newPassword: vm.model.walletInformation.newPassword
}
};
profileService
.changePassword(updatedPassword)
.then(onChangePasswordSuccess, onChangePasswordError);
function onChangePasswordSuccess(response) {
flowstack.add('accountManagement');
flowstack.next();
logger.info('Change password request: ', response);
}
function onChangePasswordError(response) {
logger.error('Change Password Error: ', response);
}
}
}
function deleteWallet() {
var appText = app.text.walletInformation;
console.log(appText);
flowstack.add('confirmation');
flowstack.next({
data: {
title: appText.deleteWalletConfirmationTitle,
body: appText.deleteWalletConfirmationBody,
cancelButton: appText.deleteWalletConfirmationCancelButton,
confirmButton: appText.deleteWalletConfirmationConfirmButton,
onConfirm: function() {
profileService.deleteWallet().then(deleteProfileSuccess, deleteProfileFailure);
function deleteProfileSuccess(response) {
if (response.profile) {
logger.info('profile is successfully deleted.', response);
postMsg.send('closeSwitch');
}
}
function deleteProfileFailure(error) {
logger.error('something went wrong while deleting profile.', error);
}
},
onCancel: function() {
flowstack.add('walletInformation');
flowstack.next();
}
}
});
}
function createLegacyProfileInformation() {
var updatedProfile = user;
var formlyField = vm.model.walletInformation;
//Update the profile
updatedProfile.name = {
first: formlyField.firstName,
last: formlyField.lastName
};
updatedProfile.mobilePhone.phoneNumber = formlyField.phoneNumber;
updatedProfile.preferences.receiveEmailNotification = formlyField.keepMeUpdated;
return {
data: updatedProfile
};
}
function onClickCancel() {
flowstack.back();
}
}
})();
Coverage is saying that onSuccess of saveWalletInformation and changePasswordRequest isnt't covered but I'm not sure exactly how to test it, the only thing I've tested now is that the saveWalletInformation function is calling the profile service:
describe.only('WalletInformationController ---', function() {
var controller, scope;
var userProfile = {
profile: {
name: {
first: 'someonefirstname',
last: 'someoneslastname'
},
emailAddress: 'someone#something.com',
mobilePhone: {
countryCode: 'US+1',
phoneNumber: '123 212 2342'
},
preferences: {
receiveEmailNotification: true
}
}
};
beforeEach(function() {
bard.appModule('walletInformation');
bard.inject(
'$q',
'$controller',
'$rootScope',
'api',
'flowstack',
'logger',
'session',
'profileService'
);
session.set('profile', userProfile);
});
beforeEach(function() {
sandbox = sinon.sandbox.create();
scope = $rootScope.$new();
scope.app = {
text: {
global: {},
walletInformation: {
deleteWalletConfirmationTitle: 'Confirm Wallet Deletion?'
},
userInformation: {
emailValidation: 'Please enter valid email'
},
signin: {
rememberMeLabel: 'remember me'
}
}
};
controller = $controller('WalletInformationController', {
$scope: scope
});
loggerErrorStub = sandbox.stub(logger, 'error');
sandbox.stub(logger, 'info');
controller = $controller('WalletInformationController', {
$scope: scope
});
});
describe('saveWalletInformation method', function() {
beforeEach(function() {
// apiStub.restore();
sandbox.stub(profileService, 'updateProfileInformation', function() {
return $q.when(userProfile);
});
});
it('should saveWalletInformation successfully', function() {
controller.saveWalletInformation();
expect(profileService.updateProfileInformation).to.have.been.called;
});
it('should log error msg when saveWalletInformation call fails', function() {
profileService.updateProfileInformation.restore();
sandbox.stub(profileService, 'updateProfileInformation', function() {
return $q.reject();
});
controller.saveWalletInformation();
scope.$apply();
expect(loggerErrorStub).to.have.been.calledOnce;
});
it('should call changePasswordRequest when currentPassword and newPassword are set', function() {
sandbox.stub(profileService, 'changePassword', function() {
return $q.when({
success: true,
extensionPoint: null
});
});
sandbox.stub(flowstack, 'add');
sandbox.stub(flowstack, 'next');
controller.saveWalletInformation();
// scope.$apply();
// expect(flowstack.next).to.have.been.calledOnce;
// expect(flowstack.add).to.have.been.calledOnce;
expect(profileService.updateProfileInformation).to.have.been.called;
// expect(profileService.changePassword).to.have.been.called;
});
});
You can let the service call go a step further and instead of verifying that service function was called, as you do here:
expect(profileService.updateProfileInformation).to.have.been.called;
You can instead mock the response from the api call with $httpBackend:
httpBackend.expectGET('your/url/here').respond(200, {response object...});
or
httpBackend.expectGET('your/url/here').respond(500, {error object...});
Then you'll be hitting both the error and success functions when your tests run

Can't get test through the end with protractor

I have simple test doing login and trying to check if it's success:
describe('My Angular App', function () {
describe('visiting the main homepage', function () {
beforeEach(function () {
browser.get('/');
element(By.id("siteLogin")).click();
});
it('should login successfully', function() {
element(By.name("login_username")).sendKeys("test#email.com");
element(By.name("login_password")).sendKeys("testpass");
element(By.id("formLoginButton")).click().then(function() {
browser.getCurrentUrl().then(function(url){
expect(url).toContain("profile");
});
});
});
});
});
It goes well until that last part where I'm checking URL, and in Selenium Server I get:
INFO - Executing: [execute async script: try { return (function (rootSelector, callback) {
var el = document.querySelector(rootSelector);
try {
if (window.getAngularTestability) {
window.getAngularTestability(el).whenStable(callback);
return;
}
if (!window.angular) {
throw new Error('angular could not be found on the window');
}
if (angular.getTestability) {
angular.getTestability(el).whenStable(callback);
} else {
if (!angular.element(el).injector()) {
throw new Error('root element (' + rootSelector + ') has no injector.' +
' this may mean it is not inside ng-app.');
}
angular.element(el).injector().get('$browser').
notifyWhenNoOutstandingRequests(callback);
}
} catch (err) {
callback(err.message);
}
}).apply(this, arguments); }
catch(e) { throw (e instanceof Error) ? e : new Error(e); }, [body]])
and also I get:
Failures:
1) My Angular App visiting the main homepage should login successfully
Message:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
My protractor-conf.js:
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
baseUrl: 'http://localhost:8080/Mysite',
capabilities: {
'browserName': 'firefox' // muste use firefox because I can't get .click() to work in Chrome
},
specs: [
'spec-e2e/*.js'
],
framework: 'jasmine2',
jasmineNodeOpts: {
isVerbose: true,
showColors: true,
defaultTimeoutInterval: 30000
}
};
I appreciate any help on this one.
Thanks
Looks like there is a non-Angular page opened after a click. If this is the case, you need to turn the synchronization between Protractor and Angular off:
afterEach(function () {
browser.ignoreSynchronization = false;
});
it('should login successfully', function() {
element(By.name("login_username")).sendKeys("test#email.com");
element(By.name("login_password")).sendKeys("testpass");
browser.ignoreSynchronization = true;
element(By.id("formLoginButton")).click().then(function() {
expect(browser.getCurrentUrl()).toContain("profile");
});
});
Note that you don't need to explicitly resolve the promise returned by getCurrentUrl and can let the expect() do that for you implicitly.
You may also need to wait for the URL to change:
var urlChanged = function(desiredUrl) {
return browser.getCurrentUrl().then(function(url) {
return url == desiredUrl;
});
};
browser.wait(urlChanged("my desired url"), 5000);

Angularjs need to vary a parameter for each tick of angular-poller

I'm following the angular-poller demo here:
https://emmaguo.github.io/angular-poller/
I need to be able to pass an argument into the factory to construct a dynamic url so that 'greet' is called with a newTime arg.
How can I modify poller.get() to pass in 'newTime'?
poller1 = poller.get(greet, {action: 'jsonp_get', delay: 1000});
.factory('greet', function ($resource) {
return $resource('https://www.example.com?time=' + newTime,
{
},
{
jsonp_get: { method: 'JSONP' }
});
})
/-- Demo --/
angular.module('myApp', ['ngResource', 'emguo.poller'])
.factory('greet1', function ($resource) {
return $resource('https://angularjs.org/greet.php',
{
callback: 'JSON_CALLBACK',
name: 'Emma'
},
{
jsonp_get: { method: 'JSONP' }
});
})
.factory('greet2', function ($resource) {
return $resource('https://angularjs.org/greet.php',
{
callback: 'JSON_CALLBACK',
name: 'You'
},
{
jsonp_get: { method: 'JSONP' }
});
})
.controller('myController', function ($scope, poller, greet1, greet2) {
var poller1, poller2;
/*-- Automatically start poller1 on page load --*/
poller1 = poller.get(greet1, {action: 'jsonp_get', delay: 1000});
poller1.promise.then(null, null, function (data) {
$scope.data1 = data;
});
/*-- Actions --*/
$scope.stop = function () {
poller1.stop();
};
$scope.restart = function () {
poller1 = poller.get(greet1);
};
$scope.faster = function () {
poller1 = poller.get(greet1, {delay: 300});
};
$scope.slower = function () {
poller1 = poller.get(greet1, {delay: 1500});
};
$scope.startPoller2 = function () {
poller2 = poller.get(greet2, {action: 'jsonp_get', delay: 1000});
poller2.promise.then(null, null, function (data) {
$scope.data2 = data;
});
};
$scope.stopBoth = function () {
poller.stopAll();
};
$scope.restartBoth = function () {
poller.restartAll();
};
});
Didn't use angular-poller, but seems you should be able to get what you want with the property argumentsArray:
var poller1 = poller.get(greet, {
action: 'jsonp_get',
delay: 1000,
argumentsArray: [{
time: newTime
}]
});
The array argumentsArray, therefore the time, is evaluated when the poller is retrieved.
Edit: After clarification, it appears you need to vary a parameter for each tick of the polling, and not when you get the poller. You could solve that by adding the parameter in a request interceptor:
// define interceptor
app.factory('requestInterceptor', function() {
return {
'request': function(request) {
// if needed, wrap the following in a condition
request.params = request.params || {};
request.params.time = moment();
return request;
}
}
})
// register interceptor
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('requestInterceptor');
})

is invoking a service into another service in angular

I need to save data and then I need to dispaly the changes immediately afterwards.
That's why I Have a
updateSaisine which allows me to update data
getOneSaisine which allows me get the data and display them:
Which is the more correct way and for which reasons ?
Should I write:
$scope.saveSaisine = function() {
saisineSrv.updateSaisine($scope.currentSaisine.idSaisine, $scope.currentSaisine).
then(
function() {
$scope.errorMessages = [];
if ($scope.currentSaisine.idMotif) {
toaster.pop('success', 'Réponse', 'Success');
angular.element('#modalSaisine').modal('hide');
saisineSrv.getOneSaisine($scope.currentSaisine.idSaisine, $scope.currentSaisine).then(function(response) {
$scope.currentSaisine.dateModif = response.dateModif;
});
},
function error(response) {
$scope.errorMessages = response.data;
toaster.pop('error', 'Réponse', 'We have a problem');
}
);
};
OR
$scope.saveSaisine = function() {
saisineSrv.updateSaisine($scope.currentSaisine.idSaisine, $scope.currentSaisine).
then(
function() {
$scope.errorMessages = [];
if ($scope.currentSaisine.idMotif) {
toaster.pop('success', 'Réponse', 'Success');
angular.element('#modalSaisine').modal('hide');
},
function error(response) {
$scope.errorMessages = response.data;
toaster.pop('error', 'Réponse', 'We have a problem');
}
);
saisineSrv.getOneSaisine($scope.currentSaisine.idSaisine, $scope.currentSaisine).then(function(response) {
$scope.currentSaisine.dateModif = response.dateModif;
});
};
the first option is a correct way how you should refresh your data because these services are asynchronous thus in the second example you may don't get fresh data (the getOneSaisine can finish before updateSaisine).

Resources