How can I ask mocha/chai to wait for a modal to be shown before it expects the test to be complete?
I am wrapping bootstrap modal and emitting my own events. show works fine, but I need to wait for the modal to be shown for my next test. I can't get done() to work at all.
describe("Modal", function() {
describe("wrapModalEvents", function() {
it("should wrap the show.bs.modal event", function() {
var m = new Modal();
m.wrapModalEvents();
var res = {e:1};
m.on('show', function(){
res.e = 555;
});
m.modal();
expect(res.e).to.equal(555);
});
it("should wrap the shown.bs.modal event", function() {
var m = new Modal();
m.wrapModalEvents();
var res = {e:1};
m.on('shown', function(){
res.e = 123;
});
// need to wait at least one second
expect(res.e).to.equal(123);
});
});
});
Related
I'm new to writing tests and I'm trying to figure out how to test if after a click the right content is loaded. I'm testing a directive, but the loaded content in the center panel. I'm first firing a click event and then I try to compile the loaded content and check if the title is the one I expected (Test).
What I'm getting as an error is
"Cannot read property 'innerHTML' of undefined"
Any idea why and how to fix the test?
describe('someSelector', function () {
beforeEach(function () {
module(function($provide) {
$provide.decorator('$timeout', function($delegate) {
var flush = $delegate.flush;
var $timeout = jasmine.createSpy('$timeout').and.callFake($delegate);
$timeout.flush = flush;
return $timeout;
});
});
this.injectDependencies('$compile',
'$scope',
'$httpBackend',
'renderTemplateAndAppendToDom',
'renderTemplate',
'$timeout');
this.render = this.renderTemplateAndAppendToDom;
this.dirStr = '<div class="some-navigator" vx-view="history[history.currentIndex].naviView"></div>';
this.viewStr = '<center-panel></center-panel>';
});
describe('when navigating to an object', function () {
beforeEach(function () {
this.$httpBackend.resetExpectations();
});
it('should load the correct content', function () {
var dir = this.$compile(this.dirStr)(this.$scope);
dir.find('span.some-navigator-label [title="Test"]').click();
dir.remove();
var view = this.$compile(this.viewStr)(this.$scope);
var contentCheck = view.find('span.titlebar-text');
expect(contentCheck[0].innerHTML).toEqual('Test');
this.$scope.$destroy();
view.remove();
});
});
});
The problem that I have with this test is that sometimes it passes, sometimes it fails, and when it does the latter a "Failed: No element found using locator: By(css selector, .add.create_ServiceOrders)" message appears in the console. Idk what to do to fix it :(
describe('angularjs homepage', function() {
it('should greet the named user', function() {
//browser.ignoreSynchronization = true
browser.get('https://int.m-tech.com/hotsosmobile/app/Index?/login#/login');
browser.waitForAngular();
var input = element(by.model('loginInfo.login'));
input.sendKeys('xxx');
expect(input.getAttribute('value')).toBe('xxx');
var input = element(by.model('loginInfo.password'));
input.sendKeys('yyy');
expect(input.getAttribute('value')).toBe('yyy');
browser.waitForAngular();
browser.driver.actions().sendKeys(protractor.Key.ENTER).perform();
browser.waitForAngular();
var AddButton = element(by.css(".add.create_ServiceOrders" ));
browser.actions().mouseDown(AddButton).mouseUp().perform();
browser.actions().mouseMove(AddButton).click().perform();
browser.waitForAngular();
var AddButton = element(by.css(".icon-standard-issue-floors" ));
browser.actions().mouseDown(AddButton).mouseUp().perform();
browser.actions().mouseMove(AddButton).click().perform();
browser.waitForAngular();
.....
});
});
Base on my experience, I usually do separate it in another it function something like this.
var AddButton = element(by.css(".add.create_ServiceOrders" ));
it ( 'should pass', function () {
browser.actions().mouseDown(AddButton).mouseUp().perform();
});
it ( 'should pass', function () {
browser.actions().mouseMove(AddButton).click().perform();
});
You need to wait for element to be ready, before manipulating. Try this:
var AddButton = $(".add.create_ServiceOrders");
browser.wait(protractor.ExpectedConditions.visibilityOf(AddButton), 5000, 'Button should be visible');
browser.actions().mouseDown(AddButton).mouseUp().perform();
browser.actions().mouseMove(AddButton).click().perform();
...
I've got a problem with executing multiple tests(i've got two here). It seems like function deleteOneTask() stopped working and my tests started to fail each other. Before i started to use PageObject everything was ok.
Stacktrace:
TypeError: Cannot read property 'click' of undefined
at c:\Users\Денис\WebstormProjects\ProtractorTest\pages\angular.page.js:29:30
It refers to this line: this.deleteButton.click();
Here is my spec.js
'use strict';
var todoAppPage = require('../pages/angular.page');
describe('angularjs todo list', function () {
var page;
beforeEach(function () {
page = new todoAppPage();
page.get();
});
it('should add a todo task', function () {
page.addNewTask('my first task');
expect(page.todoList.count()).toEqual(1);
expect(page.todoList.get(0).getText()).toEqual('my first task');
page.deleteOneTask(); //here it won't work
});
it('should show correct number of undone tasks', function () {
page.addNewTask('my first task');
expect((page.counter).getText()).toEqual('1');
page.deleteOneTask(); //here it won't work
});
it('should show correct number of undone tasks2', function () {
var deleteButton = element.all(by.className('destroy')).get(0);
var viewArea = element(by.model('todo.completed'));
page.addNewTask('my first task');
expect((page.counter).getText()).toEqual('1');
element(by.id('footer')).element(by.linkText('All')).click();
browser.driver.actions().mouseMove(viewArea).perform().then(function () { //hover and delete single task
deleteButton.click();
});; //here it will work
});
});
Here is Page Object file
'use strict';
var todoAppPage = function() {
this.newTodo = element(by.model('newTodo'));
this.todoList = element.all(by.repeater('todo in todos'));
this.viewArea = element(by.model('todo.completed'));
this.deleteButton = element.all(by.className('destroy')).get(0);
this.categoryAll = element(by.id('footer')).element(by.linkText('All'));
this.counter = element(by.id('todo-count')).element(by.className('ng-binding'));
this.get = function() {
browser.get('#/');
};
this.addNewTask = function (taskName) {
this.newTodo.sendKeys(taskName);
this.newTodo.sendKeys(protractor.Key.ENTER);
};
this.deleteOneTask = function () {
this.categoryAll.click(); //go to 'All' category
browser.driver.actions().mouseMove(this.viewArea).perform().then(function () { //hover and delete single task
this.deleteButton.click();
});
};
};
module.exports = todoAppPage;
As mentioned in a comment, using a that = this will solve your problem. Eg.
this.deleteOneTask = function () {
var that = this;
this.categoryAll.click(); //go to 'All' category
browser.driver.actions().mouseMove(this.viewArea).perform().then(function () { //hover and delete single task
that.deleteButton.click();
});
That's the best solution I've seen but I'd be happy to see a better one too.
I want to write unit tests with QUnit and Sinon.Js. I have an application, where the user can click on a button and a modal dialog appers to handle downloading some files. The user can close the dialog and it triggers a method to run to reset some variables. My test code:
$(function() {
$.fn.copy_button = function(){};
ln_download_view = new DownloadModalView();
ln_download_view.modal = {'modal': function() {}};
var download_modal_dialog = $('.download-modal');
download_modal_dialog.modal = function(param){};
var modal_mock = sinon.mock(ln_download_view.modal);
var download_modal_dialog_mock = sinon.mock(download_modal_dialog);
//Should be inserted, because ln_download_view.modal is mocked
//The close button even handler
$('#btn_close_modal').click(function(){
download_modal_dialog.modal('hide');
});
//Dirty stuff to do after the window closes
//Basicly the click triggers this event handler
$('.download-modal').on('hide',function() {
window.clearInterval(window.periodicalTimer);
});
$('div .option-container').click(function() {
if(!$(this).hasClass("selected-option"))
{
$('div #option-presenting').toggleClass("selected-option");
$('div #option-editing-and-presenting').toggleClass("selected-option");
$('.image').toggle();
}
});
module("views");
test("Download modal dialog is displayed", function(){
var modal_triggered = modal_mock.expects("modal").once();
ln_download_view.handleDownloadClick();
ok(modal_triggered.verify());
});
test("Download modal dialog is closed",function(){
var modal_triggered = download_modal_dialog_mock.expects("modal");
$('#btn_close_modal').trigger('click');
ok(modal_triggered.verify());
});
});
What I do not understand is, how can I test/mock/stub this piece of code:
$('.download-modal').on('hide',function() {
window.clearInterval(window.periodicalTimer);
});
I do not have the deep understanding yet.
You can't mock/stub an anonymous function. But you can make a refactoring and stub/mock the named callback.
$('.download-modal').on('hide', onHide);
var onHide = function() {
window.clearInterval(window.periodicalTimer);
};
// ...
sinon.stub(onHide);
Here's my method for this:
In your before each, make a function that doesn't do anything:
var doNothing = function(){};
Then in your test, spy on that:
var spy = sinon.spy(this, 'doNothing');
Then call your method, passing in a callback that fires the doNothing method:
var self = this;
whatever.doSomethingAwesome(
{
finished: function(){
self.doNothing();
}
});
expect(spy.callCount).toEqual(1);
I have a $.getJSON request that does not run but the line of code right after the request does. If I remove all the code after the $.getJSON request the request will run. How do I get the request to run iterate over returned data then run code following the request.
var eventList = new Array();
$.getJSON('../index.php?/home/events', function(eventItems){
$.each(eventItems, function() {
var event = this;
var eventItem = new Array();
// format the date and append to span
eventItem[0] = formatMDYDate(formatTimeStamp(this.loc_datetime, false), 0);
// add shortdescription to div
eventItem[1] = this.shortdescription;
// check if longdescription exist
if (this.longdescription) {
// create new anchor element for "More Info" link on events
var link = $('<a></a>');
link.attr('href', '../index.php?/home/event_info');
link.addClass('popup');
link.html('More Info');
//link.bind('click', eventPopUp());
link.bind('click', function() {
var addressValue = event.id;
dialog = $('<div></div>').appendTo('body');
dialog.load('../index.php?/home/event_info',
{id: addressValue});
dialog.modal({
opacity: 80
});
return false;
});
eventItem[2] = link;
}
eventList.push(eventItem);
});
});
// removing the following lines of code will let the .getJSON request run
if (eventList.length > 0) {
write_Events(eventList);
}
I have no idea what is causing this issue, please help!
Asynchronous means that when you call it the JS runtime will not wait for it to finish before executing next line of code. Typically you need to use call backs in this case.
It's something like:
var a="start";
setTimeout(function(){
a="done";
dosomethingWithA(a);
},1000);
if(a=="done"){}//doesn't matter, a is not "done"
function dosomethingWithA(a){
// a is "done" here
}
In your case the code should look something like:
var eventList = new Array();
$.getJSON('../index.php?/home/events', function(eventItems){
$.each(eventItems, function() {
var event = this;
var eventItem = new Array();
// format the date and append to span
eventItem[0] = formatMDYDate(formatTimeStamp(this.loc_datetime, false), 0);
// add shortdescription to div
eventItem[1] = this.shortdescription;
// check if longdescription exist
if (this.longdescription) {
// create new anchor element for "More Info" link on events
var link = $('<a></a>');
link.attr('href', '../index.php?/home/event_info');
link.addClass('popup');
link.html('More Info');
//link.bind('click', eventPopUp());
link.bind('click', function() {
var addressValue = event.id;
dialog = $('<div></div>').appendTo('body');
dialog.load('../index.php?/home/event_info',
{id: addressValue});
dialog.modal({
opacity: 80
});
return false;
});
eventItem[2] = link;
}
eventList.push(eventItem);
});
processEventList();
});
function processEventList(){
// removing the following lines of code will let the .getJSON request run
if (eventList.length > 0) {
write_Events(eventList);
}
}
try
var eventList = new Array();
$.getJSON('../index.php?/home/events', function (eventItems) {
$.each(eventItems, function () {
//....
eventList.push(eventItem);
});
// removing the following lines of code will let the .getJSON request run
if (eventList.length > 0) {
write_Events(eventList);
}
});
Alternatively, you can use PubSub with jquery technique
var eventList = new Array();
$.getJSON('../index.php?/home/events', function (eventItems) {
$.each(eventItems, function () {
//....
eventList.push(eventItem);
});
//publisher
$(document).trigger('testEvent', eventList);
});
//subscriber
$(document).bind("testEvent", function (e, eventList) {
if (eventList.length > 0) {
write_Events(eventList);
}
});
For more detials http://www.codeproject.com/Articles/292151/PubSub-with-JQuery-Events
happy coding.. :)
$.getJSON is an asynchronous call. The callback will not execute until after the current function has executed completely. The code after the call will always run BEFORE the getJSON callback runs.
Its possible the write_Events function is throwing an error and stopping execution, which is why the callback is never running. Or it is actually running but you're not seeing evidence of it for whatever reason called by the extra code.
javascript code never wait for the response from the server and we need to stop the processing of javascript until we get the response from the server.
we can do this by using jquery.Deferred
You can also visit this tutorial.