I've been playing with jasmine recently in order to start incorporating tests in my projects.
All seemed to work fine until, I wanted to automate the workflow with karma (previously Karma).
In my src directory, I have simple Calculator Object with a couple of simple methods:
function Calculator() { };
var current = 0;
Calculator.prototype.add = function() {
if (arguments.length < 2) {
// we only have one arguments
current += arguments[0];
return current;
} else {
// more than one arguments
for( var i in arguments )
current += arguments[i];
return current;
}
};
Calculator.prototype.substract = function() {
var currentValue = arguments[0];
for ( var i = 1; i < arguments.length; i++ )
currentValue -= arguments[i];
return currentValue;
};
Calculator.prototype.reset = function() {
window.current = 0;
}
Then in my spec file, I do have the following ( all tests passes without Karma ):
var calculator = new Calculator();
describe('Calculator', function() {
beforeEach(function() {
window.current = 0;
});
describe('When adding numbers', function() {
it('should store the current value at all times', function() {
expect(window.current).toBeDefined();
});
it('should add numbers', function() {
expect(window.calculator.add(5)).toEqual(5);
expect(window.calculator.add(10)).toEqual(15);
});
it('should add any number of numbers', function() {
expect(calculator.add(1, 2, 3)).toEqual(6);
expect(calculator.add(1, 2)).toEqual(9);
})
});
describe('When substracting numbers', function() {
it('should substract any number of numbers', function() {
expect(calculator.substract(5, 3)).toEqual(2);
});
});
it('should reset the current value back to zero', function() {
window.current = 20;
calculator.reset();
expect(window.current).toEqual(0);
calculator.add(5);
calculator.add(20);
expect(window.current).toEqual(25);
calculator.reset();
expect(window.current).toEqual(0);
});
});
When I run karma start, I get the following:
Chrome 28.0 (Mac) ERROR
Uncaught ReferenceError: Calculator is not defined
at /Users/roland/learning/jasmine/jasmine-standalone-1.3.1/spec/calculator_spec.js:1
Thank you for the help!
It seems like you're not loading the file that has Calculator or perhaps it's being loaded after the spec file. In your Karma configuration file, you'll want to do something like this:
files = [
'path/to/calculator.js',
JASMINE,
JASMINE_ADAPTER,
'path/to/calculator_spec.js'
];
Related
I'm experimenting with closures and classes in data variables and in the example below I'm getting undefined even though I placed a console.log() right before the function returns the result and it isn't undefined. It seems to work if it isn't attached to an event handler. Can someone tell me why is this happening and if there is a way to spot where exactly does the error happen? When debugging it goes from the console log straight to the error and I don't see how that makes sense.
To trigger the error run the snippet and click on the names.
The same functions in $('#Individuals').data('functions') can be chained and work fine when called in IndividualsList(), but not from the event listener, then the result becomes undefined.
$(document).ready(function() {
var thisWindow = $('#Individuals');
var randomNames = ['Sonia Small', 'Kurt Archer', 'Reese Mullins', 'Vikram Rayner', 'Jethro Kaye', 'Suhail Randolph', 'Kaydon Crouch', 'Jamaal Elliott', 'Herman Atkins', 'Sia Best', 'Kory Gentry', 'Fallon Sawyer', 'Zayyan Hughes', 'Ayomide Byers', 'Emilia Key', 'Jaxson Guerrero', 'Gracey Frazier', 'Millie Mora', 'Akshay Parker', 'Margareta Emiliana'];
var generatedIndividuals = [];
function generateIndividual(name) {
return {
IndividualName: name
};
}
function IndividualsList(element) {
var list = [];
this.add = function(thisIndividual) {
$('#Individuals').data('functions').init(element, list).add(thisIndividual);
}
this.refresh = function() {
$('#Individuals').data('functions').init(element, list).refresh();
}
this.sort = function(order) {
$('#Individuals').data('functions').init(element, list).sort(order);
}
}
thisWindow.data('functions', (function() {
var element = $();
var list = [];
return {
add: function(thisIndividual) {
list.push(thisIndividual);
return thisWindow.data('functions');
},
init: function(thisElement, thisList) {
element = thisElement;
list = thisList;
return thisWindow.data('functions');
},
refresh: function() {
var thisList = element.html('');
for (let i = 0; i < list.length; i++) {
thisList.append(
'<div>' + list[i].IndividualName + '</div>'
);
}
return thisWindow.data('functions');
},
sort: function(order) {
list.sort(function(a, b) {
if (a.IndividualName < b.IndividualName) return -1 * order;
if (a.IndividualName > b.IndividualName) return 1 * order;
return 0;
});
console.log(thisWindow.data('functions'));
return thisWindow.data('functions');
}
}
})());
for (let i = 0; i < 20; i++) {
let nameNum = Math.floor(Math.random() * randomNames.length);
let thisClient = generateIndividual(randomNames[nameNum]);
generatedIndividuals.push(thisClient);
}
(function() {
var targetElement = thisWindow.find('div.individuals-list');
var targetData = {}
targetElement.data('individualsList', new IndividualsList(targetElement));
targetData = targetElement.data('individualsList');
for (let i = 0; i < generatedIndividuals.length; i++) {
targetData.add(generatedIndividuals[i]);
}
targetData.refresh();
})();
thisWindow.on('click', '.individuals-list', function() {
var thisElem = $(this);
var order = parseInt(thisElem.data('order'));
thisWindow.find('div.individuals-list').data('individualsList').sort(order).refresh();
thisElem.data('order', order * (-1));
});
});
.individuals-list {
border: 1px solid;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="Individuals">
<div class="individuals-list" data-order="1"></div>
</div>
https://jsfiddle.net/Kethus/ymgwrLhj/
You are referring to the wrong sort() function, hence call it incorrectly so it returns undefined. Then you call refresh() on undefined that was returned from sort. Here's why:
In your IFFE, you use .data() to set the data = new IndvidualsList on thisWindow.find('div.individuals-list')
This code:
thisWindow.find('div.individuals-list').data('individualsList')
Returns that instantiated IndividualsList Object:
IndividualsList = $1
add: function(thisIndividual)
refresh: function()
sort: function(fieldName, order)
IndividualsList Prototype
Note the sort() function's definition. Sort in this object requires two parameters, fieldName and order; yet you call sort() and only pass order;
This indicates your expectation for the sort() function is incorrect or the wrong sort function is being made available at that line of code (in the click handler).
How to debug
Set a breakpoint at line 132 of the provided JavaScript in the
Fiddle.
Click a name in the list.
While at the breakpoint (execution paused), move to the console and run this in the console:
thisWindow.find('div.individuals-list').data('individualsList')
Note the sort() function definition in the list of functions
Next, in the console run this statement:
thisWindow.find('div.individuals-list').data('individualsList').sort(order)
Note the return is undefined <-- This is the issue
The returned value doesn't transfer from the closure to the instance that called it, the class has to be changed like so:
function IndividualsList(element) {
var list = [];
this.add = function(thisIndividual) {
return $('#Individuals').data('functions').init(element, list).add(thisIndividual);
}
this.refresh = function() {
return $('#Individuals').data('functions').init(element, list).refresh();
}
this.sort = function(order) {
return $('#Individuals').data('functions').init(element, list).sort(order);
}
}
The breakpoint could have been in one of IndividualsList()'s methods so it can be noticed that the closure returns the desired object while the method does not. Different names for either the functions or methods would help to reinforce that they are separate.
I would like to achieve module pattern in JS. I followed some posts in google and starting writing my own.
here is what I would like to achive
mns - will be the name space for my lib
math - will be one of the sub-module in mns
and I want all these modules in separate files.
here is mns.js file code (it is just for namespace no functions in it)
var mns = (function () {
return {};
})();
here is math.js code(i want this as a submodule for mns)
var submodule = (function (mns) {
var math = (function(){
var counter = 0;
var incrementCounter = function () {
return counter++;
}
var resetCounter = function () {
alert( "counter value prior to reset: " + counter );
counter = 0;
}
})();
mns.math = math;
return mns;
})(mns || {});
I am expecting application.js will call like below
mns.math.incrementCounter();
mns.math.resetCounter();
Right now, I am getting Cannot read property of undefined error when calling incrementCounter & resetCounter.
Please guide me in this.
All is looking good, except for the math module:
var math = (function(){
var counter = 0;
var incrementCounter = function () {
return counter++;
}
var resetCounter = function () {
alert( "counter value prior to reset: " + counter );
counter = 0;
}
})();
In order to use incrementCounter and resetCounter you have to return them in an object (So that math will be set to something).
Something like this should work:
var math = (function(){
var counter = 0;
var incrementCounter = function () {
return counter++;
}
var resetCounter = function () {
alert( "counter value prior to reset: " + counter );
counter = 0;
}
return {
incrememtCounter: incrememtCounter,
resetCounter: resetCounter
}
})();
Following is the way, I achieved it. Please suggest better approach
mns.js file
var mns = {};
math.js file
mns.math = (function(){
var counter = 0;
var incrementCounter = function () {
return counter++;
};
var resetCounter = function () {
alert( "counter value prior to reset: " + counter );
counter = 0;
};
return{
incrementCounter : incrementCounter,
resetCounter : resetCounter
}
})();
Rather than dealing with the semantics of creating closures, I recommend using Browserify to handle your module patterns for you. This allows you to separate everything into multiple files, as well as use the file name as an identifier for your modules. It helps to reduce the amount of boilerplate and makes the code easier to understand. It also mimics NodeJS modular CommonJS syntax which is nice as well.
I think what you're looking for is this...
// mns.js
(function(window) {
var mns = function() {
// your private code here...
return {
// public module interface...
}
}();
window.mns = mns;
})(window);
// math.js
(function(mns) {
var math = function() {
// your private code here...
return {
// public module interface...
}
}();
mns.math = math;
})(window.mns);
In your HTML page, you will need to load mns file first, and then math file. Or maybe you could use some tool to connect these files and load a unique minified file like mns.min.js. For me Gulp.js is a magnificent tool for that propose. Use some concat operation of the Gulp.js to join the file in an specific order, like I said.
If you want a real-world example, see this:
https://github.com/viniciusknob/queiroz.js/blob/master/dist/queiroz.js
OK, let's say on main.js, I have this:
var topMenu = require('./menu.js');
console.log(topMenu.getCount()); // 1
topMenu.increment();
console.log(topMenu.getCount()); // 2
and on menu.js, I have this:
exports.currentTab = 0;
var count = 1;
exports.increment = function() { count++; };
exports.getCount = function() { return count; };
exports.showSearch = function () {
$(".about-box").removeClass("active");
$(".tele-box").removeClass("active");
$(".tuts-box").addClass("active");
$(".search-div").toggle();
if (!$(".search-div").is(":visible")) {
$(".tuts-box").removeClass("active");
}
currentTab = 1;
}
module.exports = [
exports.onKeyPressing = document.onkeypress = function(evt) {
if (evt.keyCode == 49) {
console.log("Open 1st box");
showTeles();
}
if (evt.keyCode == 50) {
console.log("Open 2nd box");
showSearch();
}
if (evt.keyCode == 51) {
console.log("Open 3rd box");
showAbout();
}
}
];
My bundle.js compiles correctly. That, I do know. However, open visiting the page, I get Uncaught TypeError: undefined is not a function on line console.log(topMenu.getCount()); // 1 in main.js (bundle.js since it compiled correctly...)
However, when I remove this and what's inside:
module.exports = [
...
];
that is found near the bottom of menu.js, then it works and I get:
1
2
as expected. What is wrong with my file and how do I fix it? Also, I'm probably defining exports.showSearch() wrong. In all, can you locate the errors I am making while making this file? Thank you.
exports is an alias to module.exports. So, when you assign an array to module.exports in this part:
module.exports = [
...
];
You are overriding (clearing) the value in module.exports and hence exports. So all of these assignments are lost:
exports.currentTab = 0;
exports.increment = function() { count++; };
exports.getCount = function() { return count; };
exports.showSearch = function () {
// ...
}
And that's why you get Uncaught TypeError: undefined is not a function. There is no such function as getCount() attached to exports (and topMenu).
Update
I don't know what you want to achieve, but removing the assignment to module.exports might solve the problem:
exports.onKeyPressing = document.onkeypress = function(evt) {
// ...
}
Again, it depends on what you want to achieve.
I have an AngularJS service that looks like this (I've removed the unrelevant code):
myApp.factory('myService', ['$interval', function ($interval) {
return {
startWatch: function(onSuccess) {
var watcher = $interval(onSuccess, 250);
return watcher;
},
stopWatch: function(watcher) {
$interval.cancel(watcher);
}
};
}]);
To test this service, I have the following:
describe('myApp', function() {
beforeEach(function() {
module('myApp');
});
describe('myService', function () {
var myService = null;
beforeEach(inject(function (myService) {
myService = myService;
}));
it('should run successfully 3 times', function() {
var count = 0;
var w = myService.startWatch(function() {
count = count + 1;
console.log('fired ' + count + ' times.');
});
});
});
});
When the test gets ran, it just races through it one time. I do not understand how to actually test 1. the interval piece 2. the number of times it gets fired. There will be information in the service that changes over time. I want to test that the information is behaving as expected. For that reason, I'm trying to test a number of things over time. How do I do this?
Thank you for your help!
Try $interval.flush(millis):
describe('myApp', function() {
beforeEach(function() {
module('myApp');
});
describe('myService', function () {
var myService = null;
var $interval = null;
beforeEach(inject(function (myService) {
myService = myService;
$interval = _$interval_;
}));
it('should run successfully 3 times', function() {
var count = 0;
var w = myService.startWatch(function() {
count = count + 1;
});
$interval.flush(751); // greater than 250 * 3
expect(count).toEqual(3);
});
});
});
Remember to reference angular mock to your test: http://code.angularjs.org/1.2.0/angular-mocks.js .The version should be the same with your angular version.
I'm trying to get a sequence of tests to work in Qunit. I'm working with JQM and am using their testsuite, which includes a $.mobile.testHelper object, which I'm adding methods to.
Here is my code (with comments and logs):
// my test page is loaded inside an iframe
var frame = document.getElementsByTagName("iframe")[0];
var d = frame.contentDocument;
var w = frame.contentWindow;
var $i = w.$(d);
var $body = w.$("body");
// forcing $(body) as event target
$.testHelper.eventTarget = $body;
// sets "one" listener on "step" event and jumps to next method when triggered
$.testHelper.stepSequence = function (fns) {
$.testHelper.eventSequence("step", fns);
};
// run a test
$.testHelper.runTest = function (command, condition) {
console.log("RUNNING TEST...");
ok(condition, command);
};
// try 10x if a condition is met, wait 1000ms in between
$.testHelper.countDown = function (arr, command) {
var condition, is_done;
var ticker = 0;
var i = w.setInterval(function () {
switch (command) {
case "verifyAttribute":
condition = $i.find(arr[0]).eq(0).attr(arr[1]).indexOf(arr[2]) > -1;
break;
case "waitForElementPresent":
condition = $i.find(arr[0]).length > 0;
break;
}
if (condition) {
console.log("CONDITION PASSED, RUN TEST");
$.testHelper.runTest(command, condition);
w.clearInterval(i);
}
ticker += 1;
console.log(ticker);
if (ticker === 10) {
console.log("FAILED, RUN WITH undefined to fail test");
$.testHelper.runTest(command, condition);
w.clearInterval(i);
}
}, 1000);
};
// my test module
module("UI Basic Interaction");
asyncTest("${base_url}", function () {
expect(2);
// here is my sequence of methods
$.testHelper.stepSequence([
function () {
w.setTimeout(function () {
$body.trigger("step");
}, 800);
$.testHelper.countDown(["div#global-panel", undefined, undefined], "waitForElementPresent");
},
function () {
w.setTimeout(function () {
$body.trigger("step");
}, 800);
$("a:contains('Menu')").trigger("click");
},
function () {
w.setTimeout(function () {
$body.trigger("step");
}, 800);
$.testHelper.countDown(["div#global-panel", "class", "ui-panel-open"], "verifyAttribute");
},
function () {
w.setTimeout(function () {
$body.trigger("step");
}, 800);
$("h1:contains('My Account')").trigger("click");
},
function () {
start();
}
])
});
I need to trigger "step" AFTER the test conditions runs, but can't get it to work, so I'm using the no-nice setTimeout
My problem is that the first test passes, the 2nd test correctly starts the interval, while the UI renders, but when the element is found, QUNIT errors out with Expected 2 assertions, but 1 were run pretty much at the same time my console reports the condition to be true.
Question:
From the code above, is there an error in my test routine, that does not run runTest "fast" enough, because Qunit errors out? Also I would be happy for a nicer way to triggering "step".
Thanks!
Ok. After much meddling:
What was wrong:
My click selector was $(element:contains(...) which searched the document vs the iframe $i.find("eleme... fixed this.
I added a 2nd listener for test_runner, which triggers once a test runs. Only after all tests are run, I trigger start(). This way Qunit has to wait :-)
and the working code (see comments (1), (2), (3) for changes):
var frame = document.getElementsByTagName("iframe")[0];
var d = frame.contentDocument;
var w = frame.contentWindow;
var $i = w.$(d);
// (1) set counter for tests ran
// This allows to trigger start() after all tests are done
var test_log = 0;
var $body = w.$("body");
$.testHelper.eventTarget = $body;
$.testHelper.stepSequence = function (fns) {
$.testHelper.eventSequence("step", fns);
};
$.testHelper.runTest = function (command, condition) {
ok(condition, command);
$body.trigger("step");
// (2) When running a test, also trigger a runner on body to log no of tests
$body.trigger("test_runner");
};
$.testHelper.countDown = function (arr, command) {
var condition, is_done;
var ticker = 0;
var i = w.setInterval(function () {
switch (command) {
case "verifyAttribute":
condition = $i.find(arr[0]).eq(0).attr(arr[1]).indexOf(arr[2]) > -1;
break;
case "waitForElementPresent":
condition = $i.find(arr[0]).length > 0;
break;
}
if (condition) {
console.log("PASSED TEST");
$.testHelper.runTest(command, condition);
w.clearInterval(i);
}
ticker += 1;
if (ticker === 10) {
console.log("FAILED");
$.testHelper.runTest(command, condition);
w.clearInterval(i);
}
}, 1000);
};
module("UI Basic Interaction");
asyncTest("${base_url}", function () {
expect(2);
$.testHelper.stepSequence([
function () {
// (3) set a listener for tests ran
// once all tests are done, start()
$body.on("test_runner", function (e) {
test_log += 1;
if (test_log === 2) {
start();
}
});
$body.trigger("step");
},
function () {
$.testHelper.countDown(
["div#global-panel", undefined, undefined],
"waitForElementPresent"
);
},
function () {
$i.find("a:contains('Menu')").trigger("click");
$.testHelper.countDown(
["div#global-panel", "class", "ui-panel-open"],
"verifyAttribute"
);
},
function () {
$i.find("h1:contains('My Account')").trigger("click");
}
])
});
Viola. Works nicely.