Qunit tests not matching up with actual jQuery plugin execution - qunit

I'm starting to write Qunit tests for my existing jQuery plugins. I've gotten through a half dozen configuration style tests just to make sure everything is wired up properly. Now I'm starting to test functionality. My plugin is a countdown style plugin for textarea fields. Here's the actual working version:
http://andymatthews.net/code/textCounter/
I'm writing a test that checks that the count displayed under the field is the correct value. For example, if a field allows 100 characters and 20 characters are in the field when the page loads, the label under the field should read 80.
Here's whats in qunit-fixture:
<div id="qunit-fixture">
<textarea id="myTextarea">Existing content</textarea>
<span id="theCounter"></span>
</div>
Here's the pertinent failing test
test('setup', function(){
console.log($('#theCounter'));
equal($('#theCounter').text(), 124, 'count correctly initialized');
});
And here's the existing contents of the plugin. There is more to it, but I'm only adding more code as existing tests pass muster.
(function($) {
$.fn.textCounter = function(o){
o = $.extend( {}, $.fn.textCounter.defaults, o );
return this.each(function(i, el){
var $e = $(el),
$target = $(o.target);
// predefined count minus existing content
$e.html(o.count - $target.text().length);
console.log( $e.html() );
});
}
$.fn.textCounter.defaults = {
count: 140,
alertAt: 20,
warnAt: 0,
target: '#myTextarea',
stopAtLimit: false
}
}(jQuery));
I'm assuming that since I'm not explicitly configuring the plugin on the test harness page that the plugin using the defaults. If that's the case then the counter should start with max of 140 characters minus the 16 characters already in the field and should display 124.
Here's the problem...when a sample HTML page runs, it correctly displays 124. When the test runs, it returns $('#theCounter').text() as an empty string. Viewing the console after running the sample page AND the test, the log statement above shows 124. But the unit test shows an empty string.
This isn't anything secret so I can provide all of the files if it helps. I'm sure it's probably some sort of scope problem but I'm not experienced enough with QUnit to know what's wrong.

I don't see any code calling your plugin. You might try the following...
test( "setup", function() {
equal(
$( "#theCounter" ).textCounter().text(),
124,
"Count correctly initialized"
);
});
or you can create a module and do that in the setup phase
var textCounter;
module( "Setup", {
setup: function() {
textCounter = $( "#theCounter" ).textCounter();
},
teardown: function() {
}
});
test( "setup", function() {
equal( textCounter.text(), 124, "Count correctly initialized" );
});
It is important to know that whatever markup is in qunit-fixture will be reset for each test.

Related

Protractor does not perceive a quick change

This is my protractor test:
it("should check email validity", function(){
var resetButton = element(by.id('reset-button'));
element(by.model('Contact.email')).sendKeys('nick');
element.all(by.css('.form-control-error')).each(function (elem, index) {
if (index===1) {
expect(elem.isPresent()).toBe(true);
element(by.model('Contact.email')).sendKeys('#gmail.com').then(
function(){
expect(elem.isPresent()).toBe(false);
}
)
}
});
});
Behind that code there is a form with some input texts. The second one includes the email.form-control-erroris an error message which appears whenever the email format is not correct. The first time expect(elem.isPresent()).toBe(true);passes the test, the second time it does not, even if the error message disappears from the UI. It seems that Protractor does not perceive the fast change; however, it should because it is inside a promise. Do you have any explanation for that?
You should make things more reliable by adding a wait for the element to become not present ("stale") after sending the keys:
element(by.model('Contact.email')).sendKeys('#gmail.com');
var EC = protractor.ExpectedConditions;
browser.wait(EC.stalenessOf(elem), 5000);
expect(elem.isPresent()).toBe(false);

Protractor - select element in repeater after $http call

I'm starting out on protractor testing of my sails.js / AngularJS app. Simplified, I have a following kind of ng-repeat in my HTML:
<div ng-repeat="book in books">
book.name
</div>
My test clicks a button that sends a $http POST call to the server, creating another book, and upon success, adds another book to $scope.books. The problem is that the test fails since the book hasn't been created yet when the test checks for its existence. I know my locator/filter works since when running the test again (= the book exists before sending the call) the test succeeds.
Test:
element.all(by.buttonText('Save')).
filter(function(elem) {return elem.isDisplayed();}).
first().
click();
browser.sleep(500); // even this doesn't work :(
element.all(by.repeater('book in books')).filter(function(elem, index) {
return elem.getText().then(function(text) {
return text === "nameOfBook";
});
})
.then(function(books) {
expect(books[0].isPresent()).toEqual(true);
});
So far the answers I've run into seem to suggest that protractor should automatically wait for the $http call to be finished before continuing, but in my case it doesn't seem to be so.
I even tried sleep times of 4000ms or so in which I can already see the new element in the repeater, but the test still sees the books[0] as undefined. The works when running the test again so the problem should not be in the filter but somewhere else.
Any insights on the matter?
You can explicitly wait for the new book name to be present in the DOM:
var EC = protractor.ExpectedConditions;
var elm = element(by.xpath("//div[. = 'nameOfBook']"));
browser.wait(EC.presenceOf(elm), 10000);
You can also wait for the count of elements in a repeater to increase:
var countOfElementsToBe = function(elms, expectedValue) {
return elms.count().then(function (actualValue) {
return expectedValue === actualValue;
});
};
var books = element.all(by.repeater('book in books'));
books.count().then(function (booksCount) {
browser.wait(countOfElementsToBe(books, booksCount + 1), 10000);
});
(not tested)

How to reset the Flux stores between jasmine tests?

When running tests on stores with karma & jasmine (instead of jest), the store's state persists between tests. What's the best way to reset the state for every test?
With jest, every test is run in its own environment automatically. This post includes instructions for how to clear the require cache between tests for karma (copied below) to imitate jest, but I can't seem to get it to work.
//tests.webpack.js (suggested by linked article, modified to match my project structure)
// Create a Webpack require context so we can dynamically require our
// project's modules. Exclude test files in this context.
'use strict';
var projectContext = require.context('./path/to/js', true, /^(?!\.test).js$/);
// Extract the module ids that Webpack uses to track modules.
var projectModuleIds = projectContext.keys().map(function (module) {
return String(projectContext.resolve(module));
});
beforeEach(function () {
// Remove our modules from the require cache before each test case.
projectModuleIds.forEach(function (id) {
return delete require.cache[id];
});
});
My current file:
tests.webpack.js (current)
var context = require.context('./path/to/js', true, /\.test\.js$/);
context.keys().forEach(context);
Not sure if it helps, but here's a simplified version of the code from my tests that shows the store's state persisting between tests.
MissionStore.test.js
describe('MissionStore', function() {
beforeEach(function() {
this.MissionStore = rewire("./MissionStore");
this.registeredCallback = this.MissionStore.__get__("registeredCallback");
});
// A bunch of initialization tests without data
describe("set data", function(){
beforeEach(function(){
// Sets this.MissionStore.getMissions() to some sample data
this.registeredCallback(fetchMissions);
});
it('modifies mission data', function(){
var mission = this.MissionStore.getMissions()[0];
expect(mission.edit).not.toEqual(true);
// modify mission to add an edit flag = true
this.registeredCallback({
actionType: MissionConstants.MISSION_TOGGLE_EDIT_FLAG,
mission : mission
});
expect(this.MissionStore.getMissions()[0].edit).toEqual(true);
});
it('do something else', function(){
expect(this.MissionStore.getMissions()[0].edit).not.toEqual(true); // Failing test
console.log(this.MissionStore.getMissions()[0]);
// prints {..., edit: true}
});
});
});
In the 'do something else' test, I'm expecting the this.MissionStore state to be reset between tests so edit won't be equal to true anymore. How would I go about doing this?
Your /^(?!\.test).js$/ RegExp supposed to match every file ending with .js but not with .test.js is wrong, it will only ever match .js. It should be /^(?!.*\.test\.js$).*\.js$/. See Negative lookahead Regular Expression.
So my guess is that projectModuleIds is empty, and that you are not invalidating anything between require calls.

CasperJS form fill sometimes stays on the current page

I have a simple casperjs test to submit a search form on my homepage. Then I assert that the title on the landing page is correct.
Works fine on my computer (OSX 10.9.2) but on my colleague's laptops (a Win 7 and Win 8), the test fails randomly because casper "thinks" it is still on the search page.
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, { query: goodQuery }, true);
});
casper.then(function() {
// sometimes fails, says it's "My Project" main title
test.assertTitle('Search Result', 'Search result title is ok');
});
}
Introducing a casper.waitFor(3000) before checking the page title does not change the outcome. I've also tried to replace the then step by a waitForUrl, but it fails after 5 secs, saying it is still on the current page.
Plenty of other tests work fine on all computers but it's the only one with form submition.
Any hints on how to solve or properly work around this? I'd rather not simulate a click on the submit button (more coupling to the form internals) if possible (but it would be okay I guess).
Thanks
$ casperjs --version
1.1.0-beta3
$ phantomjs --version
1.9.7
EDIT: submitting the form and waitForUrldid not help. I found out that the test actually runs fine on its own, even on the Windows 7 machine. But when I run two tests:
01 search.js (the one described above)
02 menu.js (a simple one, merely containing assertExists)
'search.js' fails most of the time... and sometimes 'menu.js' fails instead! I suspect some mishandled concurrent access, although it consistently works on OSX. I must be doing something wrong. Both tests have the same structure:
casper.test.begin('Some test', function(test) {
casper.start(someUrl, function() {
// some test
});
casper.run(function() {
test.done();
});
});
Any clue?
Try :
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, {
query: goodQuery
},false);
this.click("your selector for submit button");
});
casper.then(function() {//you should use waitForUrl/Selector/Text() instead
// sometimes fails, says it's "My Project" main title
test.assertTitle('Search Result', 'Search result title is ok');
});
casper.run(function() {
this.test.comment('------ Tests over ----\n');
test.done();
});
});
It's better to submit the form by clicking. Sometimes (often) it doesn't pass putting the fill arg at true. Just put the correct selector for the submit button.
You should wait for an item to appear on the following page. I would change your code to the following:
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, { query: goodQuery }, true);
});
casper.waitForSelector('#someSelectorOnNextPage', function() {
test.assertTitle('Search Result', 'Search result title is ok');
});
}
I also experience same issue. Suprisingly adding empty then() handler fixes that in v1.1.0-beta3. I don't think this is expected behavior though:
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, { query: goodQuery }, true);
});
// Do nothing here, just call it as a placeholder
// Here http://localhost:8080/site sends us to the next endpoint
casper.then(function() {});
// Now this is the final page we actually want to assert
casper.then(function() {
test.assertTitle('Search Result', 'Search result title is ok');
});
}
EDIT:
Although question author says casper.waitForUrl() didn't work for them, it did work for me as an alternative solution.
What does look strange is that in verbose mode whatever returns a 301 status code along with Location Header is recognized as HTTP 200 response by Casper.
EDIT 2:
Well obviously it doesn't happen every time, but what I noticed is that Casper sometimes doubles the previous response (that's why I thought it recognizes some specific HTTP codes as 200 mistakenly and that's why author's code functioned as if it stayed on same page after form submission) and sometimes not.
waitForUrl() fixes that obviously but there is still some underneath issue in Casper which scares me a bit and I hope I will find some time to report it with all the dumps to Casper issue tracker.

Run multiple specs using jasmine and jstestdriver

I have written multiple spec files for unit testing various modules on the webpage. If i run them individually, one at a time they work fine. But when i try to run all the files in a sequence, only the first file in the spec folder works while all other tests fail. Any help would be appreciated.
Every spec file loads a static page using requirejs and renders them on the page. Once the page is rendered i check whether the title, text etc is proper or not. The spec files looks like this.
AboutSpec.js-->
require(["views/About", "nls/messages"], function (About, messages) {
beforeEach(function(){
var temp = new About();
temp.render();
});
describe("Test for About Page", function () {
it("Check For About Title", function () {
var aboutTitleText = $('.eight.columns h2').text();
expect(aboutTitleText).toEqual(messages["about_title"]);
});
});
});
FooterSpec.js-->
require(["views/Footer", "nls/messages"], function (Footer, messages) {
beforeEach(function(){
var temp = new Footer();
temp.render();
});
describe("Test for Footer Page", function () {
it("Check For Footer Content", function () {
var footerText = $('.five.columns h2').text();
expect(footerText).toEqual(messages["footer_content"]);
});
});
});
jstestDriver.conf-->
load:
- jasmine/lib/jasmine-1.3.1/jasmine.js
- jasmine/lib/adapter/JasmineAdapter.js
- js-src/javaScript/require.js
- js-src/javaScript/default.js
test:
- js-test/AboutSpec.js
- js-test/FooterSpec.js
When i run this setup, the About page does not render. Only the Footer page renders due to which all the test cases of about page fails.
We're facing the exact same problem, and I've spent--how many hours, now? Oh yeah, too many!-- trying to solve this problem.
Today I discovered Karma, a JsTD replacement from Google which runs every test in a fresh iframe. It also integrates with Jenkins. I'm in the process of installing it now and will report back on how it went.

Resources