Testing RequireJS modules with QUnit - javascript

I'm trying to test a requirejs module that has two dependencies (jquery and another custom module).
myModule-Test.js
'use strict';
(function() {
var uut,
modulePath= "../../main/webapp/js/modules/myModule.js";
module("myModule object test suite", {
setup: function() {
QUnit.stop();
require.config({
map: {
"*": {
"jquery": "../../main/webapp/js/jquery/jquery-1.11.0.min.js",
"screenLabelsResolver": "../../main/webapp/js/modules/my-screen-labels-resolver"
}
}
});
require([modulePath], function(module) {
uut = module;
QUnit.start();
});
},
teardown: function() {
require.undef(modulePath);
require.config({
map: {
"*": {
"jquery": "jquery",
"screenLabelsResolver": "../../main/webapp/js/modules/my-screen-labels-resolver"
}
}
});
}
});
test("Given A Page I Expect The Global myModule Object To Exist", function() {
ok( uut !== undefined );
});
}());
I am using require.config to pass in the dependencies with stop() and a Start().
myModule.js
'use strict';
define(["jquery", "screenLabelsResolver"], function($, screenLabelsResolver) {
var metaTag = $("meta[name='application-name']"),
currentBrand = metaTag.attr("data-brand"),
currentWidth,
viewState,
sessionTimeoutValue = metaTag.attr("data-sessiontimeoutvalue"),
sessionTimeoutWarningValue = metaTag.attr("data-sessiontimeoutwarningvalue"),
screenLabels = {},
perceptionDate = metaTag.attr("data-todayatmidnight"),
currentViewportWidth = $(window).width(),
isViewState = metaTag.attr("data-isviewstate"),
isTouch = $("html").hasClass("touch")
return {
metaTag: function () {
return metaTag;
},
currentBrand: function(){
return currentBrand;
},
currentViewportWidth: function(){
return currentViewportWidth;
},
isViewState: function(){
return isViewState;
},
sessionTimeoutValue: function(){
return sessionTimeoutValue;
},
sessionTimeoutWarningValue: function(){
return sessionTimeoutWarningValue;
},
getPerceptionDate: function(){
return perceptionDate;
},
getOrientation: function () {
return ( window.orientation == -90 || window.orientation == 90 ) ? "landscape" : "portrait";
},
isTouch: function(){
return isTouch;
},
screenLabels: function() {
if (screenLabels = {}) {
screenLabels = screenLabelsResolver( metaTag.attr("data-viewstate") /* or however you want to get the current viewstate name */ );
}
return screenLabels;
}
};
});
I get the error "Uncaught TypeError: undefined is not a function" where I try to use jQuery ($) in line var metaTag = $("meta[name='application-name']").
Somehow, jQuery is not loaded properly by the time the call is made.
My question that is this the correct approach to test r.js modules with multiple dependencies? If so what's the fundamental error in the above code?
Many Thanks in advance.

Related

html element renders as variable name

While attempting to use React.js and maybe or most likely I am doing something wrong? But when I run React.Render() nothing visible renders, however. Viewing the DOM through Chromes console I can see something happen just nothing anything recognizes.
JSX
/* global React */
var notifying = {};
(function () {
'use strict';
notifying = React.createClass({
getInitialState: function () {
return { isSeen: false };
},
_handleDismissalClick: function () {
this.setState({ isSeen: this.isSeen ? false : true });
},
render: function () {
return ( <div className={'alert alert-success'} role={'alert'}> SOMETHING </div> );
}
});
})();
(function(){
'use strict';
React.render(<notifying />, document.querySelector('.__content'));
})();
JS
/* global React */
var notifying = {};
(function () {
'use strict';
notifying = React.createClass({displayName: "notifying",
getInitialState: function () {
return { isSeen: false };
},
_handleDismissalClick: function () {
this.setState({ isSeen: this.isSeen ? false : true });
},
render: function () {
return ( React.createElement("div", {className: 'alert alert-success', role: 'alert'}, " SOMETHING ") );
}
});
})();
(function(){
'use strict';
React.render(React.createElement("notifying", null), document.querySelector('.__content'));
})();
In the DOM the output is
<notifying data-reactid=".0"></notifying>
can anyone explain to me what I did wrong where so I can stop making this mistake?
You are creating a <notifying /> DOM element. Instead, you need to call the method to create an element, which is then passed to the render method.
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script>
<script type="text/jsx">
/* global React */
var notifying = {};
(function () {
'use strict';
notifying = React.createClass({
getInitialState: function () {
return { isSeen: false };
},
_handleDismissalClick: function () {
this.setState({ isSeen: this.isSeen ? false : true });
},
render: function () {
return ( <div className={'alert alert-success'} role={'alert'}> SOMETHING </div> );
}
});
})();
(function(){
'use strict';
React.render(React.createElement(notifying), document.querySelector('.__content'));
})();
</script>
<div class="__content"></div>

Asynchronous workflow testing in Jasmine 2.0

I have an AngularJS app where I need to test a workflow and guarantee that the correct values are set after an event is broadcasted.
In 1.3 I would do this:
it('should have the correct match workflow', function() {
// matchMaking event
runs(function() {
scope.$broadcast('matchMaking', gameId);
});
waitsFor(function() {
return (scope.match && scope.match.game);
}, 'A game should be defined', 3000);
runs(function() {
expect(scope.match.game).toBeDefined();
});
// matchCreate event
runs(function() {
scope.$broadcast('matchCreate', gameId, {}, {});
});
waitsFor(function() {
return scope.match.status === 'CREATED';
}, 'Match status should be \'CREATED\'', 3000);
runs(function() {
expect(scope.match.id).toBeDefined();
expect(scope.match.player).toBeDefined();
expect(scope.match.opponent).toBeDefined();
});
// matchPrepare event
runs(function() {
scope.$broadcast('matchPrepare');
});
waitsFor(function() {
return scope.match.status === 'PREPARED';
}, 'Match status should be \'PREPARED\'', 3000);
runs(function() {
expect(scope.match.id).toBeDefined();
});
// ... continues
});
With Jasmine 2.0, it seems that the only solution to test a workflow is to chain setTimeout functions inside each other (all expectations must be inside the same spec in order to use the same scope):
beforeEach(inject(function($rootScope, $compile) {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
scope = $rootScope;
element = angular.element('<pg-match-making></pg-match-making>');
$compile(element)($rootScope);
$rootScope.$digest();
}));
it('should have the correct match workflow', function(done) {
var timeoutTick = 100;
scope.$broadcast('matchMaking', gameId);
setTimeout(function(){
expect(scope.match.game).toBeDefined();
scope.$broadcast('matchCreate', gameId, {}, {});
setTimeout(function(){
expect(scope.match.status).toEqual('CREATED');
expect(scope.match.id).toBeDefined();
expect(scope.match.player).toBeDefined();
expect(scope.match.opponent).toBeDefined();
scope.$broadcast('matchPrepare');
setTimeout(function(){
expect(scope.match.status).toEqual('PREPARED');
expect(scope.match.id).toBeDefined();
// ... call done() on the last setTimeout()
}, timeoutTick);
}, timeoutTick);
}, 6000);
});
I ended up with a pile of 7 setTimeout which make the source code a lot harder to read and the test terribly slow to run.
Isn't there a better way to test a workflow with Jasmine 2.0?
I have a solution for your problem. I have build a small simple async test framework that works well with Jasmine 2.x, but it uses the jQuery Deferred object to schedule continuations.
function asyncWait(delay) {
return new $.Deferred(function () {
var _self = this;
setTimeout(function () {
_self.resolve();
}, delay || 0);
}).promise();
}
var Async = function(init) {
var d = new $.Deferred(init);
this.promise = d.promise();
d.resolve();
};
Async.prototype.continueWith = function (continuation, delay) {
var _self = this;
_self.promise.then(function () {
_self.promise = asyncWait(delay).then(continuation);
});
return _self;
};
Async.prototype.waitsFor = function (condition, timeout, pollInterval) {
pollInterval = pollInterval || 10;
timeout = timeout || 5000;
var _self = this,
wait_d = new $.Deferred(),
t = 0,
ln = function () {
if (condition()) {
wait_d.resolve();
return;
}
if (t >= timeout) {
wait_d.reject();
throw "timeout was reached during waitsFor";
}
t += pollInterval;
setTimeout(ln, pollInterval);
};
_self.promise.then(ln);
_self.promise = wait_d.promise();
return _self;
};
To use this code, wire up a Jasmine test and use a new instance of the Async class,
it("some async test workflow I want to run", function (done) {
new Async(function () {
//wire up the first async call here
var timeoutTick = 100;
scope.$broadcast('matchMaking', gameId);
}).continueWith(function () {
expect(scope.match.game).toBeDefined();
scope.$broadcast('matchCreate', gameId, {}, {})
}, 6000).continueWith(function () {
//more stuff here
}).waitsFor(function () {
// a latch function with timeout - maybe wait for DOM update or something
return $(".my-statefull-element").val() === "updated";
}, 1000).continueWith(done); //finish by waiting for done to be called
});
This code is not a 100% fool proof, but it works for me. Let me know if you have any issues with it.
With a little bit of extra javascript, you can make the jasmine behave similarly to what you had with 1.3.1, and you don't need to pull in any additional libraries. You just need to implement the polling function that you are missing. Here's a simplified example:
var value1 = false;
var value2 = false;
var value3 = false;
var test1 = function _test1() {
setTimeout( function() { value1 = true; }, 1000 );
}
var test2 = function _test2() {
setTimeout( function() { value2 = true; }, 5000 );
}
var test3 = function _test3() {
setTimeout( function() { value3 = true; }, 300000 );
}
var asyncCheckFn = function( done, waitFor, verify ) {
if ( waitFor() ) {
verify();
done();
} else {
console.log( 'checking...' );
setTimeout( function() { asyncCheckFn(done, waitFor, verify) }, 500);
}
};
describe('async test suite', function() {
it( 'works with short test', function( done ) {
test1();
asyncCheckFn( done, function() {
return value1;
}, function() {
expect( value1 ).toBe( true );
});
}, 3000 );
it( 'longer delay', function( done ) {
test2();
asyncCheckFn( done, function() {
return value2;
}, function() {
expect( value2 ).toBe( true );
});
}, 10000 );
it( 'fails', function( done ) {
test3();
asyncCheckFn( done, function() {
return value3;
}, function() {
expect( value3 ).toBe( true );
});
}, 3000 );
});
The asyncTestFn() performs the same task that the waitsFor() function used to do -- tests a condition until it is true. The overall timeout for the test is controlled by the last parameter passed to the it() function. Here's your example rewritten as a linear test instead of nested setTimeouts:
describe('should have the correct match workflow', function() {
var timerTick = 100;
// matchMaking event
it('defines the game', function(done) {
scope.$broadcast('matchMaking', gameId);
asyncCheckFn(done, function() {
return scope.match && scope.match.game;
}, function() {
expect(scope.match.game).toBeDefined();
});
}, 6000);
it('creates the match', function(done) {
scope.$broadcast('matchCreate', gameId, {}, {});
asyncCheckFn(done, function() {
return scope.match.status === 'CREATED';
}, function() {
expect(scope.match.id).toBeDefined();
expect(scope.match.player).toBeDefined();
expect(scope.match.opponent).toBeDefined();
});
}, timerTick);
it('prepares the match', function(done) {
scope.$broadcast('matchPrepare');
asyncCheckFn(done, function() {
return scope.match.status === 'PREPARED';
}, function() {
expect(scope.match.id).toBeDefined();
});
}, timerTick);
// ... continues
});
Hope this helps.
(I know this is a little old, but I came across the question when trying to solve a similar problem -- how to nest sequential, dependent tests (answer, you can't... ))
(samples tested with Jasmine 2.2)

How to allow JQuery event handler to access variables in module pattern

var MODULE = (function() {
var app = {
hi: "hi mom", // I can't access this. Oh' what do I do?
onSubmitClick: function() {
$('button').click(function() {
console.log(hi); // I want to access the above hi variable..eeek!
})
},
run: function() {
this.onSubmitClick()
}
}
return app
}());
var newApp = MODULE;
newApp.run();
hi is not a variable, it is a property of the object app
var MODULE = (function () {
var app = {
hi: "hi mom",
onSubmitClick: function () {
$('button').click(function () {
console.log(app.hi); //use the app object and access the property
})
},
run: function () {
this.onSubmitClick()
}
}
return app
}());
var newApp = MODULE;
newApp.run();
Demo: Fiddle
You can do something like this also.
var app = {
hi: "hi mom",
onSubmitClick: function (cthis) {
$('button').click(function () {
alert(cthis.hi); //use the app object and access the property
})
},
run: function () {
this.onSubmitClick(this);
//-------------------^ this is referring app
}
}
Demo

How to write test spec for an Angularjs service?

I have little Angular Service to store and retrieve data. How do I write Jasmine test spec for testing this service?
angular.module("myServices").factory('dataStore', [
function() {
var DATASTORE;
DATASTORE = {};
return {
get: function(id) {
if (DATASTORE[id] != null) {
return DATASTORE[id];
} else {
return null;
}
},
put: function(id, data) {
return DATASTORE[id] = data;
}
};
}
]);
The below spec doesn't working for me:
"use strict";
describe("Service: dataStore", function() {
var store;
store = null;
beforeEach(function() {
module("myServices").inject([
'dataStore', function(dataStore) {
return store = dataStore;
}
]);
});
it("should return null", function() {
expect(store.get('some')).toBe(null);
});
});
First of all you should load your module within a beforEach block. After that you may use the inject function - angular and jasmin will do the rest for you.
"use strict";
describe("Service: dataStore", function() {
var store;
beforeEach(module('myServices'));
beforeEach(inject(function(dataStore){
store = dataStore;
}));
it("should return null", function() {
expect(store.get('some')).toBe(null);
});
});
Reading this Testing Angular Services Documentation should get you started
As for your problem, inject the myServices module in the beforeEach block and the dataStore service in the it block.
beforeEach(module('myServices'));
it("should return null",
inject(function(dataStore) {
expect(dataStore.get('some')).toBe(null);
}));

requireJS this context change within the callback

I've the following code:
require([templateName], function(template, view){
console.log(this); // outputs Window object
this.renderTemplate(template, {
articles: this.collection.toJSON() // rises error
});
});
now to make this work I need to change 'this' context as it is outside of it. How to achieve it?
/// EDIT - full code
define(['jquery', 'backbone', 'underscore', 'article/collections/Articles', 'generic-view-decorator','error-handler-decorator'],
function($, Backbone, _, ArticlesCollection, GenericViewDecorator, ErrorHelperDecorator) {
var ArticleView = Backbone.View.extend({
initialize: function(config) {
this.initializeCollection(ArticlesCollection, this.buildResourceUrl({
domain : 'oskszu.vgnett.no',
path : 'vgnett-dt/'+config.config.section + '/json',
query : [{
key : 'limit',
val : config.config.limit
}]
}));
this.initializeErrorCapturing(this.collection);
},
render: function() {
if(!this.collection.length) {
this.trigger('error-600');
} else {
var templateName = 'text!widgets/widgets/article/templates/article.tpl';
require([templateName]).call(this, function(template){
console.log(this);
/*this.renderTemplate(template, {
articles: this.collection.toJSON()
});*/
}, this);
}
}
});
_.extend(ArticleView.prototype, GenericViewDecorator);
_.extend(ArticleView.prototype, ErrorHelperDecorator);
return ArticleView;
}
);
Ok, I've solved it in the following way (for the future generations ;) ):
render: function() {
if(!this.collection.length) {
this.trigger('error-600');
} else {
var templateName = 'text!widgets/widgets/article/templates/article.tpl';
require([templateName], (function(template){
console.log(this);
/*this.renderTemplate(template, {
articles: this.collection.toJSON()
});*/
}).call(this));
}
}

Categories