Why is Qunit not catching my 2+ tests being run? - javascript

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.

Related

How to create a loop in an interval function (the right way)

I have been trying to replicate an example of a rotating text, but I wanted to make it infinite. However, I get it just once and I think it might be related with this logic:
Original link:
https://codepen.io/galefacekillah/pen/jWVdwQ
My first attempt, applying the recursion, was based on one example in the Mozilla docs:
let nIntervId;
function checkIntervalFinish() {
if (!nIntervId) {
nIntervId = setInterval(RotateText, 4000);
}
}
function RotateText() {
var visibleWord = document.getElementsByClassName('visible')[0],
nextWord = visibleWord.nextSibling;
if (nextWord.nodeType == 3) nextWord = nextWord.nextSibling;
if (!(nextWord == null)) {
visibleWord.setAttribute('class', 'hidden');
nextWord.setAttribute('class', 'visible');
} else {
clearInterval(nIntervId);
nIntervId = null;
}
}
checkIntervalFinish();
My second attempt was using a setTimeup. I also tried changing directly the setTimeup for the setInterval, but it didn't work. If I put a console.log, the interval function is executed infinitely, however, not this function. Although this works, it just executes once as well.
var test = function () {
// MY FUNCTION
var intervalID = setInterval(function () {
var visibleWord = document.getElementsByClassName('visible')[0],
nextWord = visibleWord.nextSibling;
if (nextWord.nodeType == 3) nextWord = nextWord.nextSibling;
if (!(nextWord == null)) {
visibleWord.setAttribute('class', 'hidden');
nextWord.setAttribute('class', 'visible');
} else {
clearInterval(intervalID);
}
}, 4000)
// END
setTimeout(test, 100);
};
test();
Can someone explain to me what is it that I am doing wrong? Some why, I think it is related to the null validation.

Send event when module was executed

I'm really stuck on this.. I need to send an event when both Load module and Hide module code was executed, and only then send the event. Ideas on how to achieve this?
// Load module
(
function() {
var s=document.createElement('script');
s.type='text/javascript';
s.async=true;
s.src='https://example.com/bundles.js';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}
)();
// Hide module
var inverval = setInterval(hideClass, 100);
function hideClass () {
if ($(".class").hide().length > 0) clearInterval(inverval);
}
// When both happend = Send a event to Google Analytics
DigitalData.push({'event':Module, 'eventLabel':'Page'});
If this is your only option, then perhaps there's something you are going about wrongly. Anyway, let's see ... Only when both events have taken place.
var HandleTwoEvents = function (key1, key2) {
this.count = 0;
this.pack = [];
$self = this;
this.startListening = function(fn) {
fn = fn || function () {}
window.addEventListener(key1, function (ev) {
if ($self.pack.indexOf(key1) < 0) {
$self.pack.push(key1);
$self.count++;
if ($self.count == 2) {
fn();
$self.count = 0;
}
}
console.log(key1, ev);
});
window.addEventListener(key2, function (ev) {
if ($self.pack.indexOf(key2) < 0) {
$self.pack.push(key2);
$self.count++;
if ($self.count == 2) {
fn();
$self.count = 0;
}
}
console.log(key2, ev);
});
}
}
Forgive me, i always use this function to create events
function createEvent(name, obj) {
var evt = document.createEvent("Event");
evt.initEvent(name, true, true);
evt.data = obj;
dispatchEvent(evt);
}
Now, to log both events ...
var both = new HandleTwoEvents("EventKeyOne", "EventKeyTwo");
both.startListening(function () {console.log("This means that both Events have taken place")});
Now, let's test ...
createEvent("EventKeyOne", {});
//key, data are the arguments ... function defined in startListening above does not execute, and upon inspection, both.count is seen to be 1
createEvent("EventKeyTwo", {});
//Now, function executes.
//It also works if "EventKeyTwo" is raised before "EventKeyOne"
Happy Coding!
PS: I'm sure there's a better way to handle the use of the $self variable, with some function binding, i guess. I've never been able to learn it.

Trouble with setInterval in an object's method

I can't figure out why when I call the reset method of the object, the timer is still null. I simplified version of my object is below, followed by the jQuery that constructs a new object and runs the code. See UPPERCASE comments for my specific question points. Thanks!
var countdownTimer = {
// Default vars
milliseconds: 120000,
interval: 1000,
timer: false,
/* ... stuff ... */
countdown: function () {
var root = this;
var originalTime = root.milliseconds;
/* ... stuff */
// IN MY MIND THIS NEXT LINE SETS THE INSTANCE OF THIS OBJECT'S TIMER PROPERTY TO THE setIterval's ID. BUT DOESN'T SEEM TO BE CORRECT. WHY?
root.timer = setInterval(function () {
if (root.milliseconds < 1) {
clearInterval(root.timer); // THIS LINE SEEMS TO WORK
root.countdownComplete(); // callback function
return false;
}
root.milliseconds = root.milliseconds - root.interval;
/* .... stuff ... */
}, root.interval);
},
start: function (ms) {
if (ms) {
this.milliseconds = ms;
}
if(this.timer) {
clearInterval(this.timer); // NOT SURE IF THIS WORKS OR NOT
}
this.countdown();
},
reset: function (ms) {
var root = this;
if(root.timer) {
clearInterval(root.timer); // THIS DOES NOT WORK
} else {
console.log('timer not exist!!!!'); // ALWAYS END UP HERE. WHY?
}
/* .... stuff ... */
},
countdownComplete: function() { }
};
// Setting up click events to create instances of the countdownTimer
$(function () {
var thisPageCountdown = 4000;
$('[data-countdown]').on('click', '[data-countdown-start], [data-countdown-reset]', function () {
var $this = $(this);
var $wrap = $this.closest('[data-countdown]');
// create instance of countdownTimer
var myCountdown = Object.create(countdownTimer);
if ($this.is('[data-countdown-start]')) {
$this.hide();
$('[data-countdown-reset]', $wrap).css('display', 'block');
myCountdown.$wrap = $wrap;
myCountdown.start(thisPageCountdown);
// myCountdown.countdownComplete = function() {
// alert("Updated Callback!");
// };
}
if ($this.is('[data-countdown-reset')) {
$this.hide();
$('[data-countdown-start]', $wrap).css('display', 'block');
// RESET CALLED HERE BUT DOESN'T WORK RIGHT. SAYS myCountdown.timer IS STILL null. WHY?
myCountdown.reset(thisPageCountdown);
}
});
});
When you use var myCountdown = Object.create(countdownTimer); inside of your click function callback you are scoping it only to that callback and once the callback has executed it is garbage collected. You need to only create one instance of the countdownTimer, and it should be outside of your click event handler.
var thisPageCountdown = 4000;
// create instance of countdownTimer
var myCountdown = Object.create(countdownTimer);
$('[data-countdown]').on('click', '[data-countdown-start], [data-countdown-reset]', function () {
var $this = $(this);
var $wrap = $this.closest('[data-countdown]');
TL;DR You can fix your issue by avoiding use of the keyword this in static methods.
When you use the keyword this in a static javascript method, it refers to the item before the last dot from the call point. Example:
foo.bar(); // inside, this will refer to foo
foo.bar.foobar(); //inside, this will refer to foo.bar
var a = foo.bar.foobar();
a(); //this will refer to either null or window - oops
To prevent this behavior, you should always use the fully qualified name reference in static methods instead of relying on the this keyword.
Example from above:
reset: function (ms) {
//var root = this; // don't do this
if(countdownTimer.timer) {
clearInterval(countdownTimer.timer);
} else {
console.log('timer not exist!!!!');
}
/* .... stuff ... */
}

How to run basic test using karma (testacular)

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'
];

I want to not be able to run the same command twice very quickly

So I have this chunk of code here:
lockskipCommand = (function(_super) {
__extends(lockskipCommand, _super);
function lockskipCommand() {
return lockskipCommand.__super__.constructor.apply(this, arguments);
}
lockskipCommand.prototype.init = function() {
this.command = '/lockskip';
this.parseType = 'exact';
return this.rankPrivelege = 'bouncer';
};
lockskipCommand.prototype.functionality = function() {
data.lockBooth();
new ModerationForceSkipService();
return setTimeout((function() {
return data.unlockBooth();
}), 4500);
};
return lockskipCommand;
})(Command);
I want to be able to let it has some sort of cool down, so it can't be used quickly in a row. The reason I want this is to prevent from people being skipped, because that's what this chunk of code is for skipping people.
I hope this is enough information to get some help. Thanks!
You can use Underscore's debounce() method (with true as the third argument).
If you don't want to include Underscore for this simple task, you could do...
var debounceFn = function (fn, delay) {
var lastInvocationTime = Date.now();
delay = delay || 0;
return function () {
(Date.now() - delay > lastInvocationTime) && (lastInvocationTime = Date.now()) && fn && fn();;
};
};
jsFiddle.
What I need is a way to not be able to execute the command more than once in a row.
You could do something similar...
var onceFn = function (fn) {
var invoked = false;
return function () {
! invoked && (invoked = true) && fn && fn();
};
};
jsFiddle.

Categories

Resources